]> Cypherpunks repositories - gostls13.git/commitdiff
runtime: improve performance of empty map with interface key type
authorcuiweixie <cuiweixie@gmail.com>
Fri, 9 Jun 2023 14:59:48 +0000 (22:59 +0800)
committerKeith Randall <khr@golang.org>
Wed, 9 Aug 2023 16:41:16 +0000 (16:41 +0000)
name                            old time/op    new time/op    delta
MegEmptyMapWithInterfaceKey-10    15.5µs ± 0%     0.0µs ± 0%  -99.97%  (p=0.000 n=20+16)

name                            old alloc/op   new alloc/op   delta
MegEmptyMapWithInterfaceKey-10     0.00B          0.00B          ~     (all equal)

name                            old allocs/op  new allocs/op  delta
MegEmptyMapWithInterfaceKey-10      0.00           0.00          ~     (all equal)

Change-Id: I46248223100e98b7877464da640075d272c14802
Reviewed-on: https://go-review.googlesource.com/c/go/+/502075
Reviewed-by: Keith Randall <khr@google.com>
Run-TryBot: xie cui <523516579@qq.com>
Reviewed-by: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
src/runtime/alg.go
src/runtime/map.go
src/runtime/map_benchmark_test.go
src/runtime/map_test.go

index a1f683f68af581a1172faa4fabb7cb6ed4b31d48..336058d159e0ddea38ad9be72d5ca0cbbf59c830 100644 (file)
@@ -193,6 +193,74 @@ func typehash(t *_type, p unsafe.Pointer, h uintptr) uintptr {
        }
 }
 
+func mapKeyError(t *maptype, p unsafe.Pointer) error {
+       if !t.HashMightPanic() {
+               return nil
+       }
+       return mapKeyError2(t.Key, p)
+}
+
+func mapKeyError2(t *_type, p unsafe.Pointer) error {
+       if t.TFlag&abi.TFlagRegularMemory != 0 {
+               return nil
+       }
+       switch t.Kind_ & kindMask {
+       case kindFloat32, kindFloat64, kindComplex64, kindComplex128, kindString:
+               return nil
+       case kindInterface:
+               i := (*interfacetype)(unsafe.Pointer(t))
+               var t *_type
+               var pdata *unsafe.Pointer
+               if len(i.Methods) == 0 {
+                       a := (*eface)(p)
+                       t = a._type
+                       if t == nil {
+                               return nil
+                       }
+                       pdata = &a.data
+               } else {
+                       a := (*iface)(p)
+                       if a.tab == nil {
+                               return nil
+                       }
+                       t = a.tab._type
+                       pdata = &a.data
+               }
+
+               if t.Equal == nil {
+                       return errorString("hash of unhashable type " + toRType(t).string())
+               }
+
+               if isDirectIface(t) {
+                       return mapKeyError2(t, unsafe.Pointer(pdata))
+               } else {
+                       return mapKeyError2(t, *pdata)
+               }
+       case kindArray:
+               a := (*arraytype)(unsafe.Pointer(t))
+               for i := uintptr(0); i < a.Len; i++ {
+                       if err := mapKeyError2(a.Elem, add(p, i*a.Elem.Size_)); err != nil {
+                               return err
+                       }
+               }
+               return nil
+       case kindStruct:
+               s := (*structtype)(unsafe.Pointer(t))
+               for _, f := range s.Fields {
+                       if f.Name.IsBlank() {
+                               continue
+                       }
+                       if err := mapKeyError2(f.Typ, add(p, f.Offset)); err != nil {
+                               return err
+                       }
+               }
+               return nil
+       default:
+               // Should never happen, keep this case for robustness.
+               return errorString("hash of unhashable type " + toRType(t).string())
+       }
+}
+
 //go:linkname reflect_typehash reflect.typehash
 func reflect_typehash(t *_type, p unsafe.Pointer, h uintptr) uintptr {
        return typehash(t, p, h)
index 7b954759f1a5c4d40438041a7c28227050fc29e0..5d4e470b9e0b70539659b02099833dfbb2b4b568 100644 (file)
@@ -407,8 +407,8 @@ func mapaccess1(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {
                asanread(key, t.Key.Size_)
        }
        if h == nil || h.count == 0 {
-               if t.HashMightPanic() {
-                       t.Hasher(key, 0) // see issue 23734
+               if err := mapKeyError(t, key); err != nil {
+                       panic(err) // see issue 23734
                }
                return unsafe.Pointer(&zeroVal[0])
        }
@@ -468,8 +468,8 @@ func mapaccess2(t *maptype, h *hmap, key unsafe.Pointer) (unsafe.Pointer, bool)
                asanread(key, t.Key.Size_)
        }
        if h == nil || h.count == 0 {
-               if t.HashMightPanic() {
-                       t.Hasher(key, 0) // see issue 23734
+               if err := mapKeyError(t, key); err != nil {
+                       panic(err) // see issue 23734
                }
                return unsafe.Pointer(&zeroVal[0]), false
        }
@@ -707,8 +707,8 @@ func mapdelete(t *maptype, h *hmap, key unsafe.Pointer) {
                asanread(key, t.Key.Size_)
        }
        if h == nil || h.count == 0 {
-               if t.HashMightPanic() {
-                       t.Hasher(key, 0) // see issue 23734
+               if err := mapKeyError(t, key); err != nil {
+                       panic(err) // see issue 23734
                }
                return
        }
index ef0747fcd8f675dee248c346e0b1a99bb9ff9c45..43d1accbb97cd67575a01280515bfeadd2465d2f 100644 (file)
@@ -168,6 +168,15 @@ func BenchmarkMegEmptyMap(b *testing.B) {
        }
 }
 
+func BenchmarkMegEmptyMapWithInterfaceKey(b *testing.B) {
+       m := make(map[any]bool)
+       key := strings.Repeat("X", 1<<20)
+       b.ResetTimer()
+       for i := 0; i < b.N; i++ {
+               _, _ = m[key]
+       }
+}
+
 func BenchmarkSmallStrMap(b *testing.B) {
        m := make(map[string]bool)
        for suffix := 'A'; suffix <= 'G'; suffix++ {
index 3675106d9cb9d733f20975779da0b5a49cb5318a..300e996de3e4fdceddcbbe2ee7add9fcd6c72074 100644 (file)
@@ -16,6 +16,7 @@ import (
        "strings"
        "sync"
        "testing"
+       "unsafe"
 )
 
 func TestHmapSize(t *testing.T) {
@@ -1256,3 +1257,165 @@ func TestMapInterfaceKey(t *testing.T) {
                panic("array not found")
        }
 }
+
+type panicStructKey struct {
+       sli []int
+}
+
+func (p panicStructKey) String() string {
+       return "panic"
+}
+
+type structKey struct {
+}
+
+func (structKey) String() string {
+       return "structKey"
+}
+
+func TestEmptyMapWithInterfaceKey(t *testing.T) {
+       var (
+               b    bool
+               i    int
+               i8   int8
+               i16  int16
+               i32  int32
+               i64  int64
+               ui   uint
+               ui8  uint8
+               ui16 uint16
+               ui32 uint32
+               ui64 uint64
+               uipt uintptr
+               f32  float32
+               f64  float64
+               c64  complex64
+               c128 complex128
+               a    [4]string
+               s    string
+               p    *int
+               up   unsafe.Pointer
+               ch   chan int
+               i0   any
+               i1   interface {
+                       String() string
+               }
+               structKey structKey
+               i0Panic   any = []int{}
+               i1Panic   interface {
+                       String() string
+               } = panicStructKey{}
+               panicStructKey = panicStructKey{}
+               sli            []int
+               me             = map[any]struct{}{}
+               mi             = map[interface {
+                       String() string
+               }]struct{}{}
+       )
+       mustNotPanic := func(f func()) {
+               f()
+       }
+       mustPanic := func(f func()) {
+               defer func() {
+                       r := recover()
+                       if r == nil {
+                               t.Errorf("didn't panic")
+                       }
+               }()
+               f()
+       }
+       mustNotPanic(func() {
+               _ = me[b]
+       })
+       mustNotPanic(func() {
+               _ = me[i]
+       })
+       mustNotPanic(func() {
+               _ = me[i8]
+       })
+       mustNotPanic(func() {
+               _ = me[i16]
+       })
+       mustNotPanic(func() {
+               _ = me[i32]
+       })
+       mustNotPanic(func() {
+               _ = me[i64]
+       })
+       mustNotPanic(func() {
+               _ = me[ui]
+       })
+       mustNotPanic(func() {
+               _ = me[ui8]
+       })
+       mustNotPanic(func() {
+               _ = me[ui16]
+       })
+       mustNotPanic(func() {
+               _ = me[ui32]
+       })
+       mustNotPanic(func() {
+               _ = me[ui64]
+       })
+       mustNotPanic(func() {
+               _ = me[uipt]
+       })
+       mustNotPanic(func() {
+               _ = me[f32]
+       })
+       mustNotPanic(func() {
+               _ = me[f64]
+       })
+       mustNotPanic(func() {
+               _ = me[c64]
+       })
+       mustNotPanic(func() {
+               _ = me[c128]
+       })
+       mustNotPanic(func() {
+               _ = me[a]
+       })
+       mustNotPanic(func() {
+               _ = me[s]
+       })
+       mustNotPanic(func() {
+               _ = me[p]
+       })
+       mustNotPanic(func() {
+               _ = me[up]
+       })
+       mustNotPanic(func() {
+               _ = me[ch]
+       })
+       mustNotPanic(func() {
+               _ = me[i0]
+       })
+       mustNotPanic(func() {
+               _ = me[i1]
+       })
+       mustNotPanic(func() {
+               _ = me[structKey]
+       })
+       mustPanic(func() {
+               _ = me[i0Panic]
+       })
+       mustPanic(func() {
+               _ = me[i1Panic]
+       })
+       mustPanic(func() {
+               _ = me[panicStructKey]
+       })
+       mustPanic(func() {
+               _ = me[sli]
+       })
+       mustPanic(func() {
+               _ = me[me]
+       })
+
+       mustNotPanic(func() {
+               _ = mi[structKey]
+       })
+       mustPanic(func() {
+               _ = mi[panicStructKey]
+       })
+}