Skip to content

Commit 23832ba

Browse files
committed
reflect: optimize for maps with string keys
Over 80% of all Go map types use a string as the key. The Go runtime already has a specialized implementation for such maps in runtime/map_faststr.go. However, the Go reflection implementation has not historically made use of that implementation. This CL plumbs the appropriate logic to be accessible from Go reflection so that it can benefit as well. name old time/op new time/op delta Map/StringKeys/MapIndex-4 4.65us ± 5% 2.95us ± 3% -36.50% (p=0.016 n=4+5) Map/StringKeys/SetMapIndex-4 7.47us ± 5% 5.27us ± 2% -29.40% (p=0.008 n=5+5) Map/Uint64Keys/MapIndex-4 3.79us ± 3% 3.75us ± 2% ~ (p=0.548 n=5+5) Map/Uint64Keys/SetMapIndex-4 6.13us ± 3% 6.09us ± 1% ~ (p=0.746 n=5+5) Change-Id: I5495d48948d8caf2d004a03ae1820ab5f8729670 Reviewed-on: https://go-review.googlesource.com/c/go/+/345486 Trust: Joe Tsai <[email protected]> Run-TryBot: Joe Tsai <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Keith Randall <[email protected]>
1 parent a50225a commit 23832ba

File tree

3 files changed

+108
-6
lines changed

3 files changed

+108
-6
lines changed

src/reflect/all_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7050,6 +7050,53 @@ func BenchmarkNew(b *testing.B) {
70507050
})
70517051
}
70527052

7053+
func BenchmarkMap(b *testing.B) {
7054+
type V *int
7055+
value := ValueOf((V)(nil))
7056+
stringKeys := []string{}
7057+
mapOfStrings := map[string]V{}
7058+
uint64Keys := []uint64{}
7059+
mapOfUint64s := map[uint64]V{}
7060+
for i := 0; i < 100; i++ {
7061+
stringKey := fmt.Sprintf("key%d", i)
7062+
stringKeys = append(stringKeys, stringKey)
7063+
mapOfStrings[stringKey] = nil
7064+
7065+
uint64Key := uint64(i)
7066+
uint64Keys = append(uint64Keys, uint64Key)
7067+
mapOfUint64s[uint64Key] = nil
7068+
}
7069+
7070+
tests := []struct {
7071+
label string
7072+
m, keys, value Value
7073+
}{
7074+
{"StringKeys", ValueOf(mapOfStrings), ValueOf(stringKeys), value},
7075+
{"Uint64Keys", ValueOf(mapOfUint64s), ValueOf(uint64Keys), value},
7076+
}
7077+
7078+
for _, tt := range tests {
7079+
b.Run(tt.label, func(b *testing.B) {
7080+
b.Run("MapIndex", func(b *testing.B) {
7081+
b.ReportAllocs()
7082+
for i := 0; i < b.N; i++ {
7083+
for j := tt.keys.Len() - 1; j >= 0; j-- {
7084+
tt.m.MapIndex(tt.keys.Index(j))
7085+
}
7086+
}
7087+
})
7088+
b.Run("SetMapIndex", func(b *testing.B) {
7089+
b.ReportAllocs()
7090+
for i := 0; i < b.N; i++ {
7091+
for j := tt.keys.Len() - 1; j >= 0; j-- {
7092+
tt.m.SetMapIndex(tt.keys.Index(j), tt.value)
7093+
}
7094+
}
7095+
})
7096+
})
7097+
}
7098+
}
7099+
70537100
func TestSwapper(t *testing.T) {
70547101
type I int
70557102
var a, b, c I

src/reflect/value.go

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1515,15 +1515,21 @@ func (v Value) MapIndex(key Value) Value {
15151515
// considered unexported. This is consistent with the
15161516
// behavior for structs, which allow read but not write
15171517
// of unexported fields.
1518-
key = key.assignTo("reflect.Value.MapIndex", tt.key, nil)
15191518

1520-
var k unsafe.Pointer
1521-
if key.flag&flagIndir != 0 {
1522-
k = key.ptr
1519+
var e unsafe.Pointer
1520+
if key.kind() == String && tt.key.Kind() == String {
1521+
k := *(*string)(key.ptr)
1522+
e = mapaccess_faststr(v.typ, v.pointer(), k)
15231523
} else {
1524-
k = unsafe.Pointer(&key.ptr)
1524+
key = key.assignTo("reflect.Value.MapIndex", tt.key, nil)
1525+
var k unsafe.Pointer
1526+
if key.flag&flagIndir != 0 {
1527+
k = key.ptr
1528+
} else {
1529+
k = unsafe.Pointer(&key.ptr)
1530+
}
1531+
e = mapaccess(v.typ, v.pointer(), k)
15251532
}
1526-
e := mapaccess(v.typ, v.pointer(), k)
15271533
if e == nil {
15281534
return Value{}
15291535
}
@@ -2121,6 +2127,25 @@ func (v Value) SetMapIndex(key, elem Value) {
21212127
v.mustBeExported()
21222128
key.mustBeExported()
21232129
tt := (*mapType)(unsafe.Pointer(v.typ))
2130+
2131+
if key.kind() == String && tt.key.Kind() == String {
2132+
k := *(*string)(key.ptr)
2133+
if elem.typ == nil {
2134+
mapdelete_faststr(v.typ, v.pointer(), k)
2135+
return
2136+
}
2137+
elem.mustBeExported()
2138+
elem = elem.assignTo("reflect.Value.SetMapIndex", tt.elem, nil)
2139+
var e unsafe.Pointer
2140+
if elem.flag&flagIndir != 0 {
2141+
e = elem.ptr
2142+
} else {
2143+
e = unsafe.Pointer(&elem.ptr)
2144+
}
2145+
mapassign_faststr(v.typ, v.pointer(), k, e)
2146+
return
2147+
}
2148+
21242149
key = key.assignTo("reflect.Value.SetMapIndex", tt.key, nil)
21252150
var k unsafe.Pointer
21262151
if key.flag&flagIndir != 0 {
@@ -3252,12 +3277,21 @@ func makemap(t *rtype, cap int) (m unsafe.Pointer)
32523277
//go:noescape
32533278
func mapaccess(t *rtype, m unsafe.Pointer, key unsafe.Pointer) (val unsafe.Pointer)
32543279

3280+
//go:noescape
3281+
func mapaccess_faststr(t *rtype, m unsafe.Pointer, key string) (val unsafe.Pointer)
3282+
32553283
//go:noescape
32563284
func mapassign(t *rtype, m unsafe.Pointer, key, val unsafe.Pointer)
32573285

3286+
//go:noescape
3287+
func mapassign_faststr(t *rtype, m unsafe.Pointer, key string, val unsafe.Pointer)
3288+
32583289
//go:noescape
32593290
func mapdelete(t *rtype, m unsafe.Pointer, key unsafe.Pointer)
32603291

3292+
//go:noescape
3293+
func mapdelete_faststr(t *rtype, m unsafe.Pointer, key string)
3294+
32613295
//go:noescape
32623296
func mapiterinit(t *rtype, m unsafe.Pointer, it *hiter)
32633297

src/runtime/map.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1324,17 +1324,38 @@ func reflect_mapaccess(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {
13241324
return elem
13251325
}
13261326

1327+
//go:linkname reflect_mapaccess_faststr reflect.mapaccess_faststr
1328+
func reflect_mapaccess_faststr(t *maptype, h *hmap, key string) unsafe.Pointer {
1329+
elem, ok := mapaccess2_faststr(t, h, key)
1330+
if !ok {
1331+
// reflect wants nil for a missing element
1332+
elem = nil
1333+
}
1334+
return elem
1335+
}
1336+
13271337
//go:linkname reflect_mapassign reflect.mapassign
13281338
func reflect_mapassign(t *maptype, h *hmap, key unsafe.Pointer, elem unsafe.Pointer) {
13291339
p := mapassign(t, h, key)
13301340
typedmemmove(t.elem, p, elem)
13311341
}
13321342

1343+
//go:linkname reflect_mapassign_faststr reflect.mapassign_faststr
1344+
func reflect_mapassign_faststr(t *maptype, h *hmap, key string, elem unsafe.Pointer) {
1345+
p := mapassign_faststr(t, h, key)
1346+
typedmemmove(t.elem, p, elem)
1347+
}
1348+
13331349
//go:linkname reflect_mapdelete reflect.mapdelete
13341350
func reflect_mapdelete(t *maptype, h *hmap, key unsafe.Pointer) {
13351351
mapdelete(t, h, key)
13361352
}
13371353

1354+
//go:linkname reflect_mapdelete_faststr reflect.mapdelete_faststr
1355+
func reflect_mapdelete_faststr(t *maptype, h *hmap, key string) {
1356+
mapdelete_faststr(t, h, key)
1357+
}
1358+
13381359
//go:linkname reflect_mapiterinit reflect.mapiterinit
13391360
func reflect_mapiterinit(t *maptype, h *hmap, it *hiter) {
13401361
mapiterinit(t, h, it)

0 commit comments

Comments
 (0)