aboutsummaryrefslogtreecommitdiff
path: root/src/runtime
diff options
context:
space:
mode:
Diffstat (limited to 'src/runtime')
-rw-r--r--src/runtime/export_test.go7
-rw-r--r--src/runtime/hashmap.go11
-rw-r--r--src/runtime/map_test.go139
-rw-r--r--src/runtime/runtime-gdb_test.go8
4 files changed, 140 insertions, 25 deletions
diff --git a/src/runtime/export_test.go b/src/runtime/export_test.go
index 599ac2d84a..385c569ed8 100644
--- a/src/runtime/export_test.go
+++ b/src/runtime/export_test.go
@@ -377,11 +377,16 @@ func (rw *RWMutex) Unlock() {
rw.rw.unlock()
}
-func MapBuckets(m map[int]int) int {
+func MapBucketsCount(m map[int]int) int {
h := *(**hmap)(unsafe.Pointer(&m))
return 1 << h.B
}
+func MapBucketsPointerIsNil(m map[int]int) bool {
+ h := *(**hmap)(unsafe.Pointer(&m))
+ return h.buckets == nil
+}
+
func LockOSCounts() (external, internal uint32) {
g := getg()
if g.m.lockedExt+g.m.lockedInt == 0 {
diff --git a/src/runtime/hashmap.go b/src/runtime/hashmap.go
index f537098854..dee5dd5816 100644
--- a/src/runtime/hashmap.go
+++ b/src/runtime/hashmap.go
@@ -281,7 +281,16 @@ func makemap64(t *maptype, hint int64, h *hmap) *hmap {
return makemap(t, int(hint), h)
}
-// makemap implements a Go map creation make(map[k]v, hint)
+// makehmap_small implements Go map creation for make(map[k]v) and
+// make(map[k]v, hint) when hint is known to be at most bucketCnt
+// at compile time and the map needs to be allocated on the heap.
+func makemap_small() *hmap {
+ h := new(hmap)
+ h.hash0 = fastrand()
+ return h
+}
+
+// makemap implements Go map creation for make(map[k]v, hint).
// If the compiler has determined that the map or the first bucket
// can be created on the stack, h and/or bucket may be non-nil.
// If h != nil, the map can be created directly in h.
diff --git a/src/runtime/map_test.go b/src/runtime/map_test.go
index 0529cb8e86..6ed655de0a 100644
--- a/src/runtime/map_test.go
+++ b/src/runtime/map_test.go
@@ -596,33 +596,132 @@ func TestIgnoreBogusMapHint(t *testing.T) {
}
}
+var mapSink map[int]int
+
+var mapBucketTests = [...]struct {
+ n int // n is the number of map elements
+ noescape int // number of expected buckets for non-escaping map
+ escape int // number of expected buckets for escaping map
+}{
+ {-(1 << 30), 1, 1},
+ {-1, 1, 1},
+ {0, 1, 1},
+ {1, 1, 1},
+ {8, 1, 1},
+ {9, 2, 2},
+ {13, 2, 2},
+ {14, 4, 4},
+ {26, 4, 4},
+}
+
func TestMapBuckets(t *testing.T) {
// Test that maps of different sizes have the right number of buckets.
+ // Non-escaping maps with small buckets (like map[int]int) never
+ // have a nil bucket pointer due to starting with preallocated buckets
+ // on the stack. Escaping maps start with a non-nil bucket pointer if
+ // hint size is above bucketCnt and thereby have more than one bucket.
// These tests depend on bucketCnt and loadFactor* in hashmap.go.
- for _, tt := range [...]struct {
- n, b int
- }{
- {8, 1},
- {9, 2},
- {13, 2},
- {14, 4},
- {26, 4},
- } {
- m := map[int]int{}
- for i := 0; i < tt.n; i++ {
- m[i] = i
+ t.Run("mapliteral", func(t *testing.T) {
+ for _, tt := range mapBucketTests {
+ localMap := map[int]int{}
+ if runtime.MapBucketsPointerIsNil(localMap) {
+ t.Errorf("no escape: buckets pointer is nil for non-escaping map")
+ }
+ for i := 0; i < tt.n; i++ {
+ localMap[i] = i
+ }
+ if got := runtime.MapBucketsCount(localMap); got != tt.noescape {
+ t.Errorf("no escape: n=%d want %d buckets, got %d", tt.n, tt.noescape, got)
+ }
+ escapingMap := map[int]int{}
+ if count := runtime.MapBucketsCount(escapingMap); count > 1 && runtime.MapBucketsPointerIsNil(escapingMap) {
+ t.Errorf("escape: buckets pointer is nil for n=%d buckets", count)
+ }
+ for i := 0; i < tt.n; i++ {
+ escapingMap[i] = i
+ }
+ if got := runtime.MapBucketsCount(escapingMap); got != tt.escape {
+ t.Errorf("escape n=%d want %d buckets, got %d", tt.n, tt.escape, got)
+ }
+ mapSink = escapingMap
}
- if got := runtime.MapBuckets(m); got != tt.b {
- t.Errorf("no hint n=%d want %d buckets, got %d", tt.n, tt.b, got)
+ })
+ t.Run("nohint", func(t *testing.T) {
+ for _, tt := range mapBucketTests {
+ localMap := make(map[int]int)
+ if runtime.MapBucketsPointerIsNil(localMap) {
+ t.Errorf("no escape: buckets pointer is nil for non-escaping map")
+ }
+ for i := 0; i < tt.n; i++ {
+ localMap[i] = i
+ }
+ if got := runtime.MapBucketsCount(localMap); got != tt.noescape {
+ t.Errorf("no escape: n=%d want %d buckets, got %d", tt.n, tt.noescape, got)
+ }
+ escapingMap := make(map[int]int)
+ if count := runtime.MapBucketsCount(escapingMap); count > 1 && runtime.MapBucketsPointerIsNil(escapingMap) {
+ t.Errorf("escape: buckets pointer is nil for n=%d buckets", count)
+ }
+ for i := 0; i < tt.n; i++ {
+ escapingMap[i] = i
+ }
+ if got := runtime.MapBucketsCount(escapingMap); got != tt.escape {
+ t.Errorf("escape: n=%d want %d buckets, got %d", tt.n, tt.escape, got)
+ }
+ mapSink = escapingMap
}
- m = make(map[int]int, tt.n)
- for i := 0; i < tt.n; i++ {
- m[i] = i
+ })
+ t.Run("makemap", func(t *testing.T) {
+ for _, tt := range mapBucketTests {
+ localMap := make(map[int]int, tt.n)
+ if runtime.MapBucketsPointerIsNil(localMap) {
+ t.Errorf("no escape: buckets pointer is nil for non-escaping map")
+ }
+ for i := 0; i < tt.n; i++ {
+ localMap[i] = i
+ }
+ if got := runtime.MapBucketsCount(localMap); got != tt.noescape {
+ t.Errorf("no escape: n=%d want %d buckets, got %d", tt.n, tt.noescape, got)
+ }
+ escapingMap := make(map[int]int, tt.n)
+ if count := runtime.MapBucketsCount(escapingMap); count > 1 && runtime.MapBucketsPointerIsNil(escapingMap) {
+ t.Errorf("escape: buckets pointer is nil for n=%d buckets", count)
+ }
+ for i := 0; i < tt.n; i++ {
+ escapingMap[i] = i
+ }
+ if got := runtime.MapBucketsCount(escapingMap); got != tt.escape {
+ t.Errorf("escape: n=%d want %d buckets, got %d", tt.n, tt.escape, got)
+ }
+ mapSink = escapingMap
}
- if got := runtime.MapBuckets(m); got != tt.b {
- t.Errorf("hint n=%d want %d buckets, got %d", tt.n, tt.b, got)
+ })
+ t.Run("makemap64", func(t *testing.T) {
+ for _, tt := range mapBucketTests {
+ localMap := make(map[int]int, int64(tt.n))
+ if runtime.MapBucketsPointerIsNil(localMap) {
+ t.Errorf("no escape: buckets pointer is nil for non-escaping map")
+ }
+ for i := 0; i < tt.n; i++ {
+ localMap[i] = i
+ }
+ if got := runtime.MapBucketsCount(localMap); got != tt.noescape {
+ t.Errorf("no escape: n=%d want %d buckets, got %d", tt.n, tt.noescape, got)
+ }
+ escapingMap := make(map[int]int, tt.n)
+ if count := runtime.MapBucketsCount(escapingMap); count > 1 && runtime.MapBucketsPointerIsNil(escapingMap) {
+ t.Errorf("escape: buckets pointer is nil for n=%d buckets", count)
+ }
+ for i := 0; i < tt.n; i++ {
+ escapingMap[i] = i
+ }
+ if got := runtime.MapBucketsCount(escapingMap); got != tt.escape {
+ t.Errorf("escape: n=%d want %d buckets, got %d", tt.n, tt.escape, got)
+ }
+ mapSink = escapingMap
}
- }
+ })
+
}
func benchmarkMapPop(b *testing.B, n int) {
diff --git a/src/runtime/runtime-gdb_test.go b/src/runtime/runtime-gdb_test.go
index 03194bcd58..476f9a791f 100644
--- a/src/runtime/runtime-gdb_test.go
+++ b/src/runtime/runtime-gdb_test.go
@@ -76,7 +76,7 @@ import "fmt"
import "runtime"
var gslice []string
func main() {
- mapvar := make(map[string]string,5)
+ mapvar := make(map[string]string, 13)
mapvar["abc"] = "def"
mapvar["ghi"] = "jkl"
strvar := "abc"
@@ -198,8 +198,10 @@ func testGdbPython(t *testing.T, cgo bool) {
t.Fatalf("info goroutines failed: %s", bl)
}
- printMapvarRe := regexp.MustCompile(`\Q = map[string]string = {["abc"] = "def", ["ghi"] = "jkl"}\E$`)
- if bl := blocks["print mapvar"]; !printMapvarRe.MatchString(bl) {
+ printMapvarRe1 := regexp.MustCompile(`\Q = map[string]string = {["abc"] = "def", ["ghi"] = "jkl"}\E$`)
+ printMapvarRe2 := regexp.MustCompile(`\Q = map[string]string = {["ghi"] = "jkl", ["abc"] = "def"}\E$`)
+ if bl := blocks["print mapvar"]; !printMapvarRe1.MatchString(bl) &&
+ !printMapvarRe2.MatchString(bl) {
t.Fatalf("print mapvar failed: %s", bl)
}