Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit fbfc203

Browse files
committed
cmd/compile: specialize map creation for small hint sizes
Handle make(map[any]any) and make(map[any]any, hint) where hint <= BUCKETSIZE special to allow for faster map initialization and to improve binary size by using runtime calls with fewer arguments. Given hint is smaller or equal to BUCKETSIZE in which case overLoadFactor(hint, 0) is false and no buckets would be allocated by makemap: * If hmap needs to be allocated on the stack then only hmap's hash0 field needs to be initialized and no call to makemap is needed. * If hmap needs to be allocated on the heap then a new special makehmap function will allocate hmap and intialize hmap's hash0 field. Reduces size of the godoc by ~36kb. AMD64 name old time/op new time/op delta NewEmptyMap 16.6ns ± 2% 5.5ns ± 2% -66.72% (p=0.000 n=10+10) NewSmallMap 64.8ns ± 1% 56.5ns ± 1% -12.75% (p=0.000 n=9+10) Updates #6853 Change-Id: I624e90da6775afaa061178e95db8aca674f44e9b Reviewed-on: https://go-review.googlesource.com/61190 Run-TryBot: Martin Möhrmann <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Keith Randall <[email protected]>
1 parent 1e83f88 commit fbfc203

File tree

12 files changed

+343
-172
lines changed

12 files changed

+343
-172
lines changed

src/cmd/compile/internal/gc/builtin.go

Lines changed: 127 additions & 123 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/cmd/compile/internal/gc/builtin/runtime.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,12 @@ func panicnildottype(want *byte)
9191
func ifaceeq(tab *uintptr, x, y unsafe.Pointer) (ret bool)
9292
func efaceeq(typ *uintptr, x, y unsafe.Pointer) (ret bool)
9393

94+
func fastrand() uint32
95+
9496
// *byte is really *runtime.Type
9597
func makemap64(mapType *byte, hint int64, mapbuf *any) (hmap map[any]any)
9698
func makemap(mapType *byte, hint int, mapbuf *any) (hmap map[any]any)
99+
func makemap_small() (hmap map[any]any)
97100
func mapaccess1(mapType *byte, hmap map[any]any, key *any) (val *any)
98101
func mapaccess1_fast32(mapType *byte, hmap map[any]any, key any) (val *any)
99102
func mapaccess1_fast64(mapType *byte, hmap map[any]any, key any) (val *any)

src/cmd/compile/internal/gc/reflect.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -252,8 +252,8 @@ func hmap(t *types.Type) *types.Type {
252252
makefield("flags", types.Types[TUINT8]),
253253
makefield("B", types.Types[TUINT8]),
254254
makefield("noverflow", types.Types[TUINT16]),
255-
makefield("hash0", types.Types[TUINT32]),
256-
makefield("buckets", types.NewPtr(bmap)), // Used in walk.go for makemap.
255+
makefield("hash0", types.Types[TUINT32]), // Used in walk.go for OMAKEMAP.
256+
makefield("buckets", types.NewPtr(bmap)), // Used in walk.go for OMAKEMAP.
257257
makefield("oldbuckets", types.NewPtr(bmap)),
258258
makefield("nevacuate", types.Types[TUINTPTR]),
259259
makefield("extra", types.Types[TUNSAFEPTR]),

src/cmd/compile/internal/gc/ssa.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1574,6 +1574,12 @@ func (s *state) expr(n *Node) *ssa.Value {
15741574
return v
15751575
}
15761576

1577+
// map <--> *hmap
1578+
if to.Etype == TMAP && from.IsPtr() &&
1579+
to.MapType().Hmap == from.Elem() {
1580+
return v
1581+
}
1582+
15771583
dowidth(from)
15781584
dowidth(to)
15791585
if from.Width != to.Width {

src/cmd/compile/internal/gc/subr.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -927,6 +927,14 @@ func convertop(src *types.Type, dst *types.Type, why *string) Op {
927927
return OCONVNOP
928928
}
929929

930+
// src is map and dst is a pointer to corresponding hmap.
931+
// This rule is needed for the implementation detail that
932+
// go gc maps are implemented as a pointer to a hmap struct.
933+
if src.Etype == TMAP && dst.IsPtr() &&
934+
src.MapType().Hmap == dst.Elem() {
935+
return OCONVNOP
936+
}
937+
930938
return 0
931939
}
932940

src/cmd/compile/internal/gc/walk.go

Lines changed: 53 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1467,28 +1467,63 @@ opswitch:
14671467
na = typecheck(na, Etop)
14681468
init.Append(na)
14691469
}
1470-
} else {
1471-
// h = nil
1472-
h = nodnil()
14731470
}
14741471

1475-
// When hint fits into int, use makemap instead of
1476-
// makemap64, which is faster and shorter on 32 bit platforms.
1477-
fnname := "makemap64"
1478-
argtype := types.Types[TINT64]
1472+
if Isconst(hint, CTINT) && hint.Val().U.(*Mpint).CmpInt64(BUCKETSIZE) <= 0 {
1473+
// Handling make(map[any]any) and
1474+
// make(map[any]any, hint) where hint <= BUCKETSIZE
1475+
// special allows for faster map initialization and
1476+
// improves binary size by using calls with fewer arguments.
1477+
// For hint <= BUCKETSIZE overLoadFactor(hint, 0) is false
1478+
// and no buckets will be allocated by makemap. Therefore,
1479+
// no buckets need to be allocated in this code path.
1480+
if n.Esc == EscNone {
1481+
// Only need to initialize h.hash0 since
1482+
// hmap h has been allocated on the stack already.
1483+
// h.hash0 = fastrand()
1484+
rand := mkcall("fastrand", types.Types[TUINT32], init)
1485+
hashsym := hmapType.Field(4).Sym // hmap.hash0 see reflect.go:hmap
1486+
a := nod(OAS, nodSym(ODOT, h, hashsym), rand)
1487+
a = typecheck(a, Etop)
1488+
a = walkexpr(a, init)
1489+
init.Append(a)
1490+
n = nod(OCONVNOP, h, nil)
1491+
n.Type = t
1492+
n = typecheck(n, Erv)
1493+
} else {
1494+
// Call runtime.makehmap to allocate an
1495+
// hmap on the heap and initialize hmap's hash0 field.
1496+
fn := syslook("makemap_small")
1497+
fn = substArgTypes(fn, t.Key(), t.Val())
1498+
n = mkcall1(fn, n.Type, init)
1499+
}
1500+
} else {
1501+
if n.Esc != EscNone {
1502+
h = nodnil()
1503+
}
1504+
// Map initialization with a variable or large hint is
1505+
// more complicated. We therefore generate a call to
1506+
// runtime.makemap to intialize hmap and allocate the
1507+
// map buckets.
14791508

1480-
// Type checking guarantees that TIDEAL hint is positive and fits in an int.
1481-
// See checkmake call in TMAP case of OMAKE case in OpSwitch in typecheck1 function.
1482-
// The case of hint overflow when converting TUINT or TUINTPTR to TINT
1483-
// will be handled by the negative range checks in makemap during runtime.
1484-
if hint.Type.IsKind(TIDEAL) || maxintval[hint.Type.Etype].Cmp(maxintval[TUINT]) <= 0 {
1485-
fnname = "makemap"
1486-
argtype = types.Types[TINT]
1487-
}
1509+
// When hint fits into int, use makemap instead of
1510+
// makemap64, which is faster and shorter on 32 bit platforms.
1511+
fnname := "makemap64"
1512+
argtype := types.Types[TINT64]
14881513

1489-
fn := syslook(fnname)
1490-
fn = substArgTypes(fn, hmapType, t.Key(), t.Val())
1491-
n = mkcall1(fn, n.Type, init, typename(n.Type), conv(hint, argtype), h)
1514+
// Type checking guarantees that TIDEAL hint is positive and fits in an int.
1515+
// See checkmake call in TMAP case of OMAKE case in OpSwitch in typecheck1 function.
1516+
// The case of hint overflow when converting TUINT or TUINTPTR to TINT
1517+
// will be handled by the negative range checks in makemap during runtime.
1518+
if hint.Type.IsKind(TIDEAL) || maxintval[hint.Type.Etype].Cmp(maxintval[TUINT]) <= 0 {
1519+
fnname = "makemap"
1520+
argtype = types.Types[TINT]
1521+
}
1522+
1523+
fn := syslook(fnname)
1524+
fn = substArgTypes(fn, hmapType, t.Key(), t.Val())
1525+
n = mkcall1(fn, n.Type, init, typename(n.Type), conv(hint, argtype), h)
1526+
}
14921527

14931528
case OMAKESLICE:
14941529
l := n.Left

src/runtime/export_test.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,11 +377,16 @@ func (rw *RWMutex) Unlock() {
377377
rw.rw.unlock()
378378
}
379379

380-
func MapBuckets(m map[int]int) int {
380+
func MapBucketsCount(m map[int]int) int {
381381
h := *(**hmap)(unsafe.Pointer(&m))
382382
return 1 << h.B
383383
}
384384

385+
func MapBucketsPointerIsNil(m map[int]int) bool {
386+
h := *(**hmap)(unsafe.Pointer(&m))
387+
return h.buckets == nil
388+
}
389+
385390
func LockOSCounts() (external, internal uint32) {
386391
g := getg()
387392
if g.m.lockedExt+g.m.lockedInt == 0 {

src/runtime/hashmap.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,16 @@ func makemap64(t *maptype, hint int64, h *hmap) *hmap {
281281
return makemap(t, int(hint), h)
282282
}
283283

284-
// makemap implements a Go map creation make(map[k]v, hint)
284+
// makehmap_small implements Go map creation for make(map[k]v) and
285+
// make(map[k]v, hint) when hint is known to be at most bucketCnt
286+
// at compile time and the map needs to be allocated on the heap.
287+
func makemap_small() *hmap {
288+
h := new(hmap)
289+
h.hash0 = fastrand()
290+
return h
291+
}
292+
293+
// makemap implements Go map creation for make(map[k]v, hint).
285294
// If the compiler has determined that the map or the first bucket
286295
// can be created on the stack, h and/or bucket may be non-nil.
287296
// If h != nil, the map can be created directly in h.

src/runtime/map_test.go

Lines changed: 119 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -596,33 +596,132 @@ func TestIgnoreBogusMapHint(t *testing.T) {
596596
}
597597
}
598598

599+
var mapSink map[int]int
600+
601+
var mapBucketTests = [...]struct {
602+
n int // n is the number of map elements
603+
noescape int // number of expected buckets for non-escaping map
604+
escape int // number of expected buckets for escaping map
605+
}{
606+
{-(1 << 30), 1, 1},
607+
{-1, 1, 1},
608+
{0, 1, 1},
609+
{1, 1, 1},
610+
{8, 1, 1},
611+
{9, 2, 2},
612+
{13, 2, 2},
613+
{14, 4, 4},
614+
{26, 4, 4},
615+
}
616+
599617
func TestMapBuckets(t *testing.T) {
600618
// Test that maps of different sizes have the right number of buckets.
619+
// Non-escaping maps with small buckets (like map[int]int) never
620+
// have a nil bucket pointer due to starting with preallocated buckets
621+
// on the stack. Escaping maps start with a non-nil bucket pointer if
622+
// hint size is above bucketCnt and thereby have more than one bucket.
601623
// These tests depend on bucketCnt and loadFactor* in hashmap.go.
602-
for _, tt := range [...]struct {
603-
n, b int
604-
}{
605-
{8, 1},
606-
{9, 2},
607-
{13, 2},
608-
{14, 4},
609-
{26, 4},
610-
} {
611-
m := map[int]int{}
612-
for i := 0; i < tt.n; i++ {
613-
m[i] = i
624+
t.Run("mapliteral", func(t *testing.T) {
625+
for _, tt := range mapBucketTests {
626+
localMap := map[int]int{}
627+
if runtime.MapBucketsPointerIsNil(localMap) {
628+
t.Errorf("no escape: buckets pointer is nil for non-escaping map")
629+
}
630+
for i := 0; i < tt.n; i++ {
631+
localMap[i] = i
632+
}
633+
if got := runtime.MapBucketsCount(localMap); got != tt.noescape {
634+
t.Errorf("no escape: n=%d want %d buckets, got %d", tt.n, tt.noescape, got)
635+
}
636+
escapingMap := map[int]int{}
637+
if count := runtime.MapBucketsCount(escapingMap); count > 1 && runtime.MapBucketsPointerIsNil(escapingMap) {
638+
t.Errorf("escape: buckets pointer is nil for n=%d buckets", count)
639+
}
640+
for i := 0; i < tt.n; i++ {
641+
escapingMap[i] = i
642+
}
643+
if got := runtime.MapBucketsCount(escapingMap); got != tt.escape {
644+
t.Errorf("escape n=%d want %d buckets, got %d", tt.n, tt.escape, got)
645+
}
646+
mapSink = escapingMap
614647
}
615-
if got := runtime.MapBuckets(m); got != tt.b {
616-
t.Errorf("no hint n=%d want %d buckets, got %d", tt.n, tt.b, got)
648+
})
649+
t.Run("nohint", func(t *testing.T) {
650+
for _, tt := range mapBucketTests {
651+
localMap := make(map[int]int)
652+
if runtime.MapBucketsPointerIsNil(localMap) {
653+
t.Errorf("no escape: buckets pointer is nil for non-escaping map")
654+
}
655+
for i := 0; i < tt.n; i++ {
656+
localMap[i] = i
657+
}
658+
if got := runtime.MapBucketsCount(localMap); got != tt.noescape {
659+
t.Errorf("no escape: n=%d want %d buckets, got %d", tt.n, tt.noescape, got)
660+
}
661+
escapingMap := make(map[int]int)
662+
if count := runtime.MapBucketsCount(escapingMap); count > 1 && runtime.MapBucketsPointerIsNil(escapingMap) {
663+
t.Errorf("escape: buckets pointer is nil for n=%d buckets", count)
664+
}
665+
for i := 0; i < tt.n; i++ {
666+
escapingMap[i] = i
667+
}
668+
if got := runtime.MapBucketsCount(escapingMap); got != tt.escape {
669+
t.Errorf("escape: n=%d want %d buckets, got %d", tt.n, tt.escape, got)
670+
}
671+
mapSink = escapingMap
617672
}
618-
m = make(map[int]int, tt.n)
619-
for i := 0; i < tt.n; i++ {
620-
m[i] = i
673+
})
674+
t.Run("makemap", func(t *testing.T) {
675+
for _, tt := range mapBucketTests {
676+
localMap := make(map[int]int, tt.n)
677+
if runtime.MapBucketsPointerIsNil(localMap) {
678+
t.Errorf("no escape: buckets pointer is nil for non-escaping map")
679+
}
680+
for i := 0; i < tt.n; i++ {
681+
localMap[i] = i
682+
}
683+
if got := runtime.MapBucketsCount(localMap); got != tt.noescape {
684+
t.Errorf("no escape: n=%d want %d buckets, got %d", tt.n, tt.noescape, got)
685+
}
686+
escapingMap := make(map[int]int, tt.n)
687+
if count := runtime.MapBucketsCount(escapingMap); count > 1 && runtime.MapBucketsPointerIsNil(escapingMap) {
688+
t.Errorf("escape: buckets pointer is nil for n=%d buckets", count)
689+
}
690+
for i := 0; i < tt.n; i++ {
691+
escapingMap[i] = i
692+
}
693+
if got := runtime.MapBucketsCount(escapingMap); got != tt.escape {
694+
t.Errorf("escape: n=%d want %d buckets, got %d", tt.n, tt.escape, got)
695+
}
696+
mapSink = escapingMap
621697
}
622-
if got := runtime.MapBuckets(m); got != tt.b {
623-
t.Errorf("hint n=%d want %d buckets, got %d", tt.n, tt.b, got)
698+
})
699+
t.Run("makemap64", func(t *testing.T) {
700+
for _, tt := range mapBucketTests {
701+
localMap := make(map[int]int, int64(tt.n))
702+
if runtime.MapBucketsPointerIsNil(localMap) {
703+
t.Errorf("no escape: buckets pointer is nil for non-escaping map")
704+
}
705+
for i := 0; i < tt.n; i++ {
706+
localMap[i] = i
707+
}
708+
if got := runtime.MapBucketsCount(localMap); got != tt.noescape {
709+
t.Errorf("no escape: n=%d want %d buckets, got %d", tt.n, tt.noescape, got)
710+
}
711+
escapingMap := make(map[int]int, tt.n)
712+
if count := runtime.MapBucketsCount(escapingMap); count > 1 && runtime.MapBucketsPointerIsNil(escapingMap) {
713+
t.Errorf("escape: buckets pointer is nil for n=%d buckets", count)
714+
}
715+
for i := 0; i < tt.n; i++ {
716+
escapingMap[i] = i
717+
}
718+
if got := runtime.MapBucketsCount(escapingMap); got != tt.escape {
719+
t.Errorf("escape: n=%d want %d buckets, got %d", tt.n, tt.escape, got)
720+
}
721+
mapSink = escapingMap
624722
}
625-
}
723+
})
724+
626725
}
627726

628727
func benchmarkMapPop(b *testing.B, n int) {

src/runtime/runtime-gdb_test.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ import "fmt"
7676
import "runtime"
7777
var gslice []string
7878
func main() {
79-
mapvar := make(map[string]string,5)
79+
mapvar := make(map[string]string, 13)
8080
mapvar["abc"] = "def"
8181
mapvar["ghi"] = "jkl"
8282
strvar := "abc"
@@ -198,8 +198,10 @@ func testGdbPython(t *testing.T, cgo bool) {
198198
t.Fatalf("info goroutines failed: %s", bl)
199199
}
200200

201-
printMapvarRe := regexp.MustCompile(`\Q = map[string]string = {["abc"] = "def", ["ghi"] = "jkl"}\E$`)
202-
if bl := blocks["print mapvar"]; !printMapvarRe.MatchString(bl) {
201+
printMapvarRe1 := regexp.MustCompile(`\Q = map[string]string = {["abc"] = "def", ["ghi"] = "jkl"}\E$`)
202+
printMapvarRe2 := regexp.MustCompile(`\Q = map[string]string = {["ghi"] = "jkl", ["abc"] = "def"}\E$`)
203+
if bl := blocks["print mapvar"]; !printMapvarRe1.MatchString(bl) &&
204+
!printMapvarRe2.MatchString(bl) {
203205
t.Fatalf("print mapvar failed: %s", bl)
204206
}
205207

0 commit comments

Comments
 (0)