1
0
mirror of https://github.com/golang/go synced 2024-09-29 00:24:30 -06:00

runtime: improve performance of empty map with interface key type

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>
This commit is contained in:
cuiweixie 2023-06-09 22:59:48 +08:00 committed by Keith Randall
parent cd589c8a73
commit d32b4798f8
4 changed files with 246 additions and 6 deletions

View 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)

View 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
}

View 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++ {

View 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]
})
}