| // Copyright 2024 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| //go:build !goexperiment.swissmap |
| |
| package runtime_test |
| |
| import ( |
| "internal/abi" |
| "internal/goarch" |
| "runtime" |
| "slices" |
| "testing" |
| ) |
| |
| func TestHmapSize(t *testing.T) { |
| // The structure of hmap is defined in runtime/map.go |
| // and in cmd/compile/internal/reflectdata/map.go and must be in sync. |
| // The size of hmap should be 56 bytes on 64 bit and 36 bytes on 32 bit platforms. |
| var hmapSize = uintptr(2*8 + 5*goarch.PtrSize) |
| if runtime.RuntimeHmapSize != hmapSize { |
| t.Errorf("sizeof(runtime.hmap{})==%d, want %d", runtime.RuntimeHmapSize, hmapSize) |
| } |
| } |
| |
| func TestLoadFactor(t *testing.T) { |
| for b := uint8(0); b < 20; b++ { |
| count := 13 * (1 << b) / 2 // 6.5 |
| if b == 0 { |
| count = 8 |
| } |
| if runtime.OverLoadFactor(count, b) { |
| t.Errorf("OverLoadFactor(%d,%d)=true, want false", count, b) |
| } |
| if !runtime.OverLoadFactor(count+1, b) { |
| t.Errorf("OverLoadFactor(%d,%d)=false, want true", count+1, b) |
| } |
| } |
| } |
| |
| func TestMapIterOrder(t *testing.T) { |
| sizes := []int{3, 7, 9, 15} |
| if abi.OldMapBucketCountBits >= 5 { |
| // it gets flaky (often only one iteration order) at size 3 when abi.MapBucketCountBits >=5. |
| t.Fatalf("This test becomes flaky if abi.MapBucketCountBits(=%d) is 5 or larger", abi.OldMapBucketCountBits) |
| } |
| for _, n := range sizes { |
| for i := 0; i < 1000; i++ { |
| // Make m be {0: true, 1: true, ..., n-1: true}. |
| m := make(map[int]bool) |
| for i := 0; i < n; i++ { |
| m[i] = true |
| } |
| // Check that iterating over the map produces at least two different orderings. |
| ord := func() []int { |
| var s []int |
| for key := range m { |
| s = append(s, key) |
| } |
| return s |
| } |
| first := ord() |
| ok := false |
| for try := 0; try < 100; try++ { |
| if !slices.Equal(first, ord()) { |
| ok = true |
| break |
| } |
| } |
| if !ok { |
| t.Errorf("Map with n=%d elements had consistent iteration order: %v", n, first) |
| break |
| } |
| } |
| } |
| } |
| |
| const bs = abi.OldMapBucketCount |
| |
| // belowOverflow should be a pretty-full pair of buckets; |
| // atOverflow is 1/8 bs larger = 13/8 buckets or two buckets |
| // that are 13/16 full each, which is the overflow boundary. |
| // Adding one to that should ensure overflow to the next higher size. |
| const ( |
| belowOverflow = bs * 3 / 2 // 1.5 bs = 2 buckets @ 75% |
| atOverflow = belowOverflow + bs/8 // 2 buckets at 13/16 fill. |
| ) |
| |
| 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}, |
| {bs, 1, 1}, |
| {bs + 1, 2, 2}, |
| {belowOverflow, 2, 2}, // 1.5 bs = 2 buckets @ 75% |
| {atOverflow + 1, 4, 4}, // 13/8 bs + 1 == overflow to 4 |
| |
| {2 * belowOverflow, 4, 4}, // 3 bs = 4 buckets @75% |
| {2*atOverflow + 1, 8, 8}, // 13/4 bs + 1 = overflow to 8 |
| |
| {4 * belowOverflow, 8, 8}, // 6 bs = 8 buckets @ 75% |
| {4*atOverflow + 1, 16, 16}, // 13/2 bs + 1 = overflow to 16 |
| } |
| |
| 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 map.go. |
| 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 := runtime.Escape(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) |
| } |
| } |
| }) |
| 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 := runtime.Escape(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) |
| } |
| } |
| }) |
| 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 := runtime.Escape(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) |
| } |
| } |
| }) |
| 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 := runtime.Escape(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) |
| } |
| } |
| }) |
| } |