| | |
| | |
| | |
| |
|
| | package maps_test |
| |
|
| | import ( |
| | "fmt" |
| | "internal/abi" |
| | "internal/runtime/maps" |
| | "math" |
| | "testing" |
| | "unsafe" |
| | ) |
| |
|
| | func TestCtrlSize(t *testing.T) { |
| | cs := unsafe.Sizeof(maps.CtrlGroup(0)) |
| | if cs != abi.MapGroupSlots { |
| | t.Errorf("ctrlGroup size got %d want abi.MapGroupSlots %d", cs, abi.MapGroupSlots) |
| | } |
| | } |
| |
|
| | func TestMapPut(t *testing.T) { |
| | m, typ := maps.NewTestMap[uint32, uint64](8) |
| |
|
| | key := uint32(0) |
| | elem := uint64(256 + 0) |
| |
|
| | for i := 0; i < 31; i++ { |
| | key += 1 |
| | elem += 1 |
| | m.Put(typ, unsafe.Pointer(&key), unsafe.Pointer(&elem)) |
| |
|
| | if maps.DebugLog { |
| | fmt.Printf("After put %d: %v\n", key, m) |
| | } |
| | } |
| |
|
| | if m.Used() != 31 { |
| | t.Errorf("Used() used got %d want 31", m.Used()) |
| | } |
| |
|
| | key = uint32(0) |
| | elem = uint64(256 + 0) |
| |
|
| | for i := 0; i < 31; i++ { |
| | key += 1 |
| | elem += 1 |
| | got, ok := m.Get(typ, unsafe.Pointer(&key)) |
| | if !ok { |
| | t.Errorf("Get(%d) got ok false want true", key) |
| | } |
| | gotElem := *(*uint64)(got) |
| | if gotElem != elem { |
| | t.Errorf("Get(%d) got elem %d want %d", key, gotElem, elem) |
| | } |
| | } |
| | } |
| |
|
| | |
| | func TestMapSplit(t *testing.T) { |
| | m, typ := maps.NewTestMap[uint32, uint64](0) |
| |
|
| | key := uint32(0) |
| | elem := uint64(256 + 0) |
| |
|
| | for i := 0; i < 2*maps.MaxTableCapacity; i++ { |
| | key += 1 |
| | elem += 1 |
| | m.Put(typ, unsafe.Pointer(&key), unsafe.Pointer(&elem)) |
| |
|
| | if maps.DebugLog { |
| | fmt.Printf("After put %d: %v\n", key, m) |
| | } |
| | } |
| |
|
| | if m.Used() != 2*maps.MaxTableCapacity { |
| | t.Errorf("Used() used got %d want 31", m.Used()) |
| | } |
| |
|
| | key = uint32(0) |
| | elem = uint64(256 + 0) |
| |
|
| | for i := 0; i < 2*maps.MaxTableCapacity; i++ { |
| | key += 1 |
| | elem += 1 |
| | got, ok := m.Get(typ, unsafe.Pointer(&key)) |
| | if !ok { |
| | t.Errorf("Get(%d) got ok false want true", key) |
| | } |
| | gotElem := *(*uint64)(got) |
| | if gotElem != elem { |
| | t.Errorf("Get(%d) got elem %d want %d", key, gotElem, elem) |
| | } |
| | } |
| | } |
| |
|
| | func TestMapDelete(t *testing.T) { |
| | m, typ := maps.NewTestMap[uint32, uint64](32) |
| |
|
| | key := uint32(0) |
| | elem := uint64(256 + 0) |
| |
|
| | for i := 0; i < 31; i++ { |
| | key += 1 |
| | elem += 1 |
| | m.Put(typ, unsafe.Pointer(&key), unsafe.Pointer(&elem)) |
| |
|
| | if maps.DebugLog { |
| | fmt.Printf("After put %d: %v\n", key, m) |
| | } |
| | } |
| |
|
| | key = uint32(0) |
| | elem = uint64(256 + 0) |
| |
|
| | for i := 0; i < 31; i++ { |
| | key += 1 |
| | m.Delete(typ, unsafe.Pointer(&key)) |
| | } |
| |
|
| | if m.Used() != 0 { |
| | t.Errorf("Used() used got %d want 0", m.Used()) |
| | } |
| |
|
| | key = uint32(0) |
| | elem = uint64(256 + 0) |
| |
|
| | for i := 0; i < 31; i++ { |
| | key += 1 |
| | elem += 1 |
| | _, ok := m.Get(typ, unsafe.Pointer(&key)) |
| | if ok { |
| | t.Errorf("Get(%d) got ok true want false", key) |
| | } |
| | } |
| | } |
| |
|
| | func TestTableClear(t *testing.T) { |
| | m, typ := maps.NewTestMap[uint32, uint64](32) |
| |
|
| | key := uint32(0) |
| | elem := uint64(256 + 0) |
| |
|
| | for i := 0; i < 31; i++ { |
| | key += 1 |
| | elem += 1 |
| | m.Put(typ, unsafe.Pointer(&key), unsafe.Pointer(&elem)) |
| |
|
| | if maps.DebugLog { |
| | fmt.Printf("After put %d: %v\n", key, m) |
| | } |
| | } |
| |
|
| | m.Clear(typ) |
| |
|
| | if m.Used() != 0 { |
| | t.Errorf("Clear() used got %d want 0", m.Used()) |
| | } |
| |
|
| | key = uint32(0) |
| | elem = uint64(256 + 0) |
| |
|
| | for i := 0; i < 31; i++ { |
| | key += 1 |
| | elem += 1 |
| | _, ok := m.Get(typ, unsafe.Pointer(&key)) |
| | if ok { |
| | t.Errorf("Get(%d) got ok true want false", key) |
| | } |
| | } |
| | } |
| |
|
| | |
| | |
| | func TestTableKeyUpdate(t *testing.T) { |
| | m, typ := maps.NewTestMap[float64, uint64](8) |
| |
|
| | zero := float64(0.0) |
| | negZero := math.Copysign(zero, -1.0) |
| | elem := uint64(0) |
| |
|
| | m.Put(typ, unsafe.Pointer(&zero), unsafe.Pointer(&elem)) |
| | if maps.DebugLog { |
| | fmt.Printf("After put %f: %v\n", zero, m) |
| | } |
| |
|
| | elem = 1 |
| | m.Put(typ, unsafe.Pointer(&negZero), unsafe.Pointer(&elem)) |
| | if maps.DebugLog { |
| | fmt.Printf("After put %f: %v\n", negZero, m) |
| | } |
| |
|
| | if m.Used() != 1 { |
| | t.Errorf("Used() used got %d want 1", m.Used()) |
| | } |
| |
|
| | it := new(maps.Iter) |
| | it.Init(typ, m) |
| | it.Next() |
| | keyPtr, elemPtr := it.Key(), it.Elem() |
| | if keyPtr == nil { |
| | t.Fatal("it.Key() got nil want key") |
| | } |
| |
|
| | key := *(*float64)(keyPtr) |
| | elem = *(*uint64)(elemPtr) |
| | if math.Copysign(1.0, key) > 0 { |
| | t.Errorf("map key %f has positive sign", key) |
| | } |
| | if elem != 1 { |
| | t.Errorf("map elem got %d want 1", elem) |
| | } |
| | } |
| |
|
| | |
| | func TestTablePutDelete(t *testing.T) { |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | |
| | m, typ := maps.NewTestMap[uint32, uint32](16) |
| |
|
| | key := uint32(0) |
| | elem := uint32(256 + 0) |
| |
|
| | for { |
| | key += 1 |
| | elem += 1 |
| |
|
| | m.Put(typ, unsafe.Pointer(&key), unsafe.Pointer(&elem)) |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | fullKeyPtr := m.KeyFromFullGroup(typ) |
| | if fullKeyPtr != nil { |
| | |
| | key = *(*uint32)(fullKeyPtr) |
| | elem = 256 + key |
| | break |
| | } |
| | } |
| |
|
| | |
| | |
| | m.Delete(typ, unsafe.Pointer(&key)) |
| |
|
| | |
| | |
| | tabWant := m.TableFor(typ, unsafe.Pointer(&key)) |
| | growthLeftWant := tabWant.GrowthLeft() |
| |
|
| | m.Put(typ, unsafe.Pointer(&key), unsafe.Pointer(&elem)) |
| |
|
| | tabGot := m.TableFor(typ, unsafe.Pointer(&key)) |
| | growthLeftGot := tabGot.GrowthLeft() |
| |
|
| | if tabGot != tabWant { |
| | |
| | |
| | t.Errorf("Put(%d) grew table got %v want %v map %v", key, tabGot, tabWant, m) |
| | } |
| |
|
| | if growthLeftGot != growthLeftWant { |
| | t.Errorf("GrowthLeft got %d want %d: map %v tab %v", growthLeftGot, growthLeftWant, m, tabGot) |
| | } |
| | } |
| |
|
| | func TestTableIteration(t *testing.T) { |
| | m, typ := maps.NewTestMap[uint32, uint64](8) |
| |
|
| | key := uint32(0) |
| | elem := uint64(256 + 0) |
| |
|
| | for i := 0; i < 31; i++ { |
| | key += 1 |
| | elem += 1 |
| | m.Put(typ, unsafe.Pointer(&key), unsafe.Pointer(&elem)) |
| |
|
| | if maps.DebugLog { |
| | fmt.Printf("After put %d: %v\n", key, m) |
| | } |
| | } |
| |
|
| | got := make(map[uint32]uint64) |
| |
|
| | it := new(maps.Iter) |
| | it.Init(typ, m) |
| | for { |
| | it.Next() |
| | keyPtr, elemPtr := it.Key(), it.Elem() |
| | if keyPtr == nil { |
| | break |
| | } |
| |
|
| | key := *(*uint32)(keyPtr) |
| | elem := *(*uint64)(elemPtr) |
| | got[key] = elem |
| | } |
| |
|
| | if len(got) != 31 { |
| | t.Errorf("Iteration got %d entries, want 31: %+v", len(got), got) |
| | } |
| |
|
| | key = uint32(0) |
| | elem = uint64(256 + 0) |
| |
|
| | for i := 0; i < 31; i++ { |
| | key += 1 |
| | elem += 1 |
| | gotElem, ok := got[key] |
| | if !ok { |
| | t.Errorf("Iteration missing key %d", key) |
| | continue |
| | } |
| | if gotElem != elem { |
| | t.Errorf("Iteration key %d got elem %d want %d", key, gotElem, elem) |
| | } |
| | } |
| | } |
| |
|
| | |
| | func TestTableIterationDelete(t *testing.T) { |
| | m, typ := maps.NewTestMap[uint32, uint64](8) |
| |
|
| | key := uint32(0) |
| | elem := uint64(256 + 0) |
| |
|
| | for i := 0; i < 31; i++ { |
| | key += 1 |
| | elem += 1 |
| | m.Put(typ, unsafe.Pointer(&key), unsafe.Pointer(&elem)) |
| |
|
| | if maps.DebugLog { |
| | fmt.Printf("After put %d: %v\n", key, m) |
| | } |
| | } |
| |
|
| | got := make(map[uint32]uint64) |
| | first := true |
| | deletedKey := uint32(1) |
| | it := new(maps.Iter) |
| | it.Init(typ, m) |
| | for { |
| | it.Next() |
| | keyPtr, elemPtr := it.Key(), it.Elem() |
| | if keyPtr == nil { |
| | break |
| | } |
| |
|
| | key := *(*uint32)(keyPtr) |
| | elem := *(*uint64)(elemPtr) |
| | got[key] = elem |
| |
|
| | if first { |
| | first = false |
| |
|
| | |
| | |
| | if key == deletedKey { |
| | deletedKey++ |
| | } |
| | m.Delete(typ, unsafe.Pointer(&deletedKey)) |
| | } |
| | } |
| |
|
| | if len(got) != 30 { |
| | t.Errorf("Iteration got %d entries, want 30: %+v", len(got), got) |
| | } |
| |
|
| | key = uint32(0) |
| | elem = uint64(256 + 0) |
| |
|
| | for i := 0; i < 31; i++ { |
| | key += 1 |
| | elem += 1 |
| |
|
| | wantOK := true |
| | if key == deletedKey { |
| | wantOK = false |
| | } |
| |
|
| | gotElem, gotOK := got[key] |
| | if gotOK != wantOK { |
| | t.Errorf("Iteration key %d got ok %v want ok %v", key, gotOK, wantOK) |
| | continue |
| | } |
| | if wantOK && gotElem != elem { |
| | t.Errorf("Iteration key %d got elem %d want %d", key, gotElem, elem) |
| | } |
| | } |
| | } |
| |
|
| | |
| | func TestTableIterationGrowDelete(t *testing.T) { |
| | m, typ := maps.NewTestMap[uint32, uint64](8) |
| |
|
| | key := uint32(0) |
| | elem := uint64(256 + 0) |
| |
|
| | for i := 0; i < 31; i++ { |
| | key += 1 |
| | elem += 1 |
| | m.Put(typ, unsafe.Pointer(&key), unsafe.Pointer(&elem)) |
| |
|
| | if maps.DebugLog { |
| | fmt.Printf("After put %d: %v\n", key, m) |
| | } |
| | } |
| |
|
| | got := make(map[uint32]uint64) |
| | first := true |
| | deletedKey := uint32(1) |
| | it := new(maps.Iter) |
| | it.Init(typ, m) |
| | for { |
| | it.Next() |
| | keyPtr, elemPtr := it.Key(), it.Elem() |
| | if keyPtr == nil { |
| | break |
| | } |
| |
|
| | key := *(*uint32)(keyPtr) |
| | elem := *(*uint64)(elemPtr) |
| | got[key] = elem |
| |
|
| | if first { |
| | first = false |
| |
|
| | |
| | |
| | if key == deletedKey { |
| | deletedKey++ |
| | } |
| |
|
| | |
| | key := uint32(32) |
| | elem := uint64(256 + 32) |
| |
|
| | for i := 0; i < 31; i++ { |
| | key += 1 |
| | elem += 1 |
| | m.Put(typ, unsafe.Pointer(&key), unsafe.Pointer(&elem)) |
| |
|
| | if maps.DebugLog { |
| | fmt.Printf("After put %d: %v\n", key, m) |
| | } |
| | } |
| |
|
| | |
| | m.Delete(typ, unsafe.Pointer(&deletedKey)) |
| | } |
| | } |
| |
|
| | |
| | |
| |
|
| | |
| | key = uint32(0) |
| | elem = uint64(256 + 0) |
| |
|
| | for i := 0; i < 31; i++ { |
| | key += 1 |
| | elem += 1 |
| |
|
| | wantOK := true |
| | if key == deletedKey { |
| | wantOK = false |
| | } |
| |
|
| | gotElem, gotOK := got[key] |
| | if gotOK != wantOK { |
| | t.Errorf("Iteration key %d got ok %v want ok %v", key, gotOK, wantOK) |
| | continue |
| | } |
| | if wantOK && gotElem != elem { |
| | t.Errorf("Iteration key %d got elem %d want %d", key, gotElem, elem) |
| | } |
| | } |
| | } |
| |
|
| | func testTableIterationGrowDuplicate(t *testing.T, grow int) { |
| | m, typ := maps.NewTestMap[uint32, uint64](8) |
| |
|
| | key := uint32(0) |
| | elem := uint64(256 + 0) |
| |
|
| | for i := 0; i < 31; i++ { |
| | key += 1 |
| | elem += 1 |
| | m.Put(typ, unsafe.Pointer(&key), unsafe.Pointer(&elem)) |
| |
|
| | if maps.DebugLog { |
| | fmt.Printf("After put %d: %v\n", key, m) |
| | } |
| | } |
| |
|
| | got := make(map[uint32]uint64) |
| | it := new(maps.Iter) |
| | it.Init(typ, m) |
| | for i := 0; ; i++ { |
| | it.Next() |
| | keyPtr, elemPtr := it.Key(), it.Elem() |
| | if keyPtr == nil { |
| | break |
| | } |
| |
|
| | key := *(*uint32)(keyPtr) |
| | elem := *(*uint64)(elemPtr) |
| | if elem != 256+uint64(key) { |
| | t.Errorf("iteration got key %d elem %d want elem %d", key, elem, 256+uint64(key)) |
| | } |
| | if _, ok := got[key]; ok { |
| | t.Errorf("iteration got key %d more than once", key) |
| | } |
| | got[key] = elem |
| |
|
| | |
| | if i == 16 { |
| | key := uint32(32) |
| | elem := uint64(256 + 32) |
| |
|
| | for i := 0; i < grow; i++ { |
| | key += 1 |
| | elem += 1 |
| | m.Put(typ, unsafe.Pointer(&key), unsafe.Pointer(&elem)) |
| |
|
| | if maps.DebugLog { |
| | fmt.Printf("After put %d: %v\n", key, m) |
| | } |
| | } |
| | } |
| | } |
| |
|
| | |
| | |
| | } |
| |
|
| | |
| | func TestTableIterationGrowDuplicate(t *testing.T) { |
| | |
| | t.Run("grow", func(t *testing.T) { testTableIterationGrowDuplicate(t, 32) }) |
| |
|
| | |
| | t.Run("split", func(t *testing.T) { testTableIterationGrowDuplicate(t, 2*maps.MaxTableCapacity) }) |
| | } |
| |
|
| | func TestAlignUpPow2(t *testing.T) { |
| | tests := []struct { |
| | in uint64 |
| | want uint64 |
| | overflow bool |
| | }{ |
| | { |
| | in: 0, |
| | want: 0, |
| | }, |
| | { |
| | in: 3, |
| | want: 4, |
| | }, |
| | { |
| | in: 4, |
| | want: 4, |
| | }, |
| | { |
| | in: 1 << 63, |
| | want: 1 << 63, |
| | }, |
| | { |
| | in: (1 << 63) - 1, |
| | want: 1 << 63, |
| | }, |
| | { |
| | in: (1 << 63) + 1, |
| | overflow: true, |
| | }, |
| | } |
| |
|
| | for _, tc := range tests { |
| | got, overflow := maps.AlignUpPow2(tc.in) |
| | if got != tc.want { |
| | t.Errorf("alignUpPow2(%d) got %d, want %d", tc.in, got, tc.want) |
| | } |
| | if overflow != tc.overflow { |
| | t.Errorf("alignUpPow2(%d) got overflow %v, want %v", tc.in, overflow, tc.overflow) |
| | } |
| | } |
| | } |
| |
|
| | |
| | func TestMapZeroSizeSlot(t *testing.T) { |
| | m, typ := maps.NewTestMap[struct{}, struct{}](16) |
| |
|
| | key := struct{}{} |
| | elem := struct{}{} |
| |
|
| | m.Put(typ, unsafe.Pointer(&key), unsafe.Pointer(&elem)) |
| |
|
| | if maps.DebugLog { |
| | fmt.Printf("After put %d: %v\n", key, m) |
| | } |
| |
|
| | got, ok := m.Get(typ, unsafe.Pointer(&key)) |
| | if !ok { |
| | t.Errorf("Get(%d) got ok false want true", key) |
| | } |
| | gotElem := *(*struct{})(got) |
| | if gotElem != elem { |
| | t.Errorf("Get(%d) got elem %d want %d", key, gotElem, elem) |
| | } |
| |
|
| | tab := m.TableFor(typ, unsafe.Pointer(&key)) |
| | start := tab.GroupsStart() |
| | length := tab.GroupsLength() |
| | end := unsafe.Pointer(uintptr(start) + length*typ.GroupSize - 1) |
| | if uintptr(got) < uintptr(start) || uintptr(got) > uintptr(end) { |
| | t.Errorf("elem address outside groups allocation; got %p want [%p, %p]", got, start, end) |
| | } |
| | } |
| |
|
| | func TestMapIndirect(t *testing.T) { |
| | type big [abi.MapMaxKeyBytes + abi.MapMaxElemBytes]byte |
| |
|
| | m, typ := maps.NewTestMap[big, big](8) |
| |
|
| | key := big{} |
| | elem := big{} |
| | elem[0] = 128 |
| |
|
| | for i := 0; i < 31; i++ { |
| | key[0] += 1 |
| | elem[0] += 1 |
| | m.Put(typ, unsafe.Pointer(&key), unsafe.Pointer(&elem)) |
| |
|
| | if maps.DebugLog { |
| | fmt.Printf("After put %v: %v\n", key, m) |
| | } |
| | } |
| |
|
| | if m.Used() != 31 { |
| | t.Errorf("Used() used got %d want 31", m.Used()) |
| | } |
| |
|
| | key = big{} |
| | elem = big{} |
| | elem[0] = 128 |
| |
|
| | for i := 0; i < 31; i++ { |
| | key[0] += 1 |
| | elem[0] += 1 |
| | got, ok := m.Get(typ, unsafe.Pointer(&key)) |
| | if !ok { |
| | t.Errorf("Get(%v) got ok false want true", key) |
| | } |
| | gotElem := *(*big)(got) |
| | if gotElem != elem { |
| | t.Errorf("Get(%v) got elem %v want %v", key, gotElem, elem) |
| | } |
| | } |
| | } |
| |
|
| | |
| | func TestMapDeleteClear(t *testing.T) { |
| | m, typ := maps.NewTestMap[int64, int64](8) |
| |
|
| | key := int64(0) |
| | elem := int64(128) |
| |
|
| | m.Put(typ, unsafe.Pointer(&key), unsafe.Pointer(&elem)) |
| |
|
| | if maps.DebugLog { |
| | fmt.Printf("After put %d: %v\n", key, m) |
| | } |
| |
|
| | got, ok := m.Get(typ, unsafe.Pointer(&key)) |
| | if !ok { |
| | t.Errorf("Get(%d) got ok false want true", key) |
| | } |
| | gotElem := *(*int64)(got) |
| | if gotElem != elem { |
| | t.Errorf("Get(%d) got elem %d want %d", key, gotElem, elem) |
| | } |
| |
|
| | m.Delete(typ, unsafe.Pointer(&key)) |
| |
|
| | gotElem = *(*int64)(got) |
| | if gotElem != 0 { |
| | t.Errorf("Delete(%d) failed to clear element. got %d want 0", key, gotElem) |
| | } |
| | } |
| |
|
| | var alwaysFalse bool |
| | var escapeSink any |
| |
|
| | func escape[T any](x T) T { |
| | if alwaysFalse { |
| | escapeSink = x |
| | } |
| | return x |
| | } |
| |
|
| | const ( |
| | belowMax = abi.MapGroupSlots * 3 / 2 |
| | atMax = (2 * abi.MapGroupSlots * maps.MaxAvgGroupLoad) / abi.MapGroupSlots |
| | ) |
| |
|
| | func TestTableGroupCount(t *testing.T) { |
| | |
| | |
| |
|
| | type mapCount struct { |
| | tables int |
| | groups uint64 |
| | } |
| |
|
| | type mapCase struct { |
| | initialLit mapCount |
| | initialHint mapCount |
| | after mapCount |
| | } |
| |
|
| | var testCases = []struct { |
| | n int |
| | escape mapCase |
| | }{ |
| | { |
| | n: -(1 << 30), |
| | escape: mapCase{ |
| | initialLit: mapCount{0, 0}, |
| | initialHint: mapCount{0, 0}, |
| | after: mapCount{0, 0}, |
| | }, |
| | }, |
| | { |
| | n: -1, |
| | escape: mapCase{ |
| | initialLit: mapCount{0, 0}, |
| | initialHint: mapCount{0, 0}, |
| | after: mapCount{0, 0}, |
| | }, |
| | }, |
| | { |
| | n: 0, |
| | escape: mapCase{ |
| | initialLit: mapCount{0, 0}, |
| | initialHint: mapCount{0, 0}, |
| | after: mapCount{0, 0}, |
| | }, |
| | }, |
| | { |
| | n: 1, |
| | escape: mapCase{ |
| | initialLit: mapCount{0, 0}, |
| | initialHint: mapCount{0, 0}, |
| | after: mapCount{0, 1}, |
| | }, |
| | }, |
| | { |
| | n: abi.MapGroupSlots, |
| | escape: mapCase{ |
| | initialLit: mapCount{0, 0}, |
| | initialHint: mapCount{0, 0}, |
| | after: mapCount{0, 1}, |
| | }, |
| | }, |
| | { |
| | n: abi.MapGroupSlots + 1, |
| | escape: mapCase{ |
| | initialLit: mapCount{0, 0}, |
| | initialHint: mapCount{1, 2}, |
| | after: mapCount{1, 2}, |
| | }, |
| | }, |
| | { |
| | n: belowMax, |
| | escape: mapCase{ |
| | initialLit: mapCount{0, 0}, |
| | initialHint: mapCount{1, 2}, |
| | after: mapCount{1, 2}, |
| | }, |
| | }, |
| | { |
| | n: atMax, |
| | escape: mapCase{ |
| | initialLit: mapCount{0, 0}, |
| | initialHint: mapCount{1, 2}, |
| | after: mapCount{1, 2}, |
| | }, |
| | }, |
| | { |
| | n: atMax + 1, |
| | escape: mapCase{ |
| | initialLit: mapCount{0, 0}, |
| | initialHint: mapCount{1, 4}, |
| | after: mapCount{1, 4}, |
| | }, |
| | }, |
| | { |
| | n: 2 * belowMax, |
| | escape: mapCase{ |
| | initialLit: mapCount{0, 0}, |
| | initialHint: mapCount{1, 4}, |
| | after: mapCount{1, 4}, |
| | }, |
| | }, |
| | { |
| | n: 2*atMax + 1, |
| | escape: mapCase{ |
| | initialLit: mapCount{0, 0}, |
| | initialHint: mapCount{1, 8}, |
| | after: mapCount{1, 8}, |
| | }, |
| | }, |
| | } |
| |
|
| | testMap := func(t *testing.T, m map[int]int, n int, initial, after mapCount) { |
| | mm := *(**maps.Map)(unsafe.Pointer(&m)) |
| |
|
| | gotTab := mm.TableCount() |
| | if gotTab != initial.tables { |
| | t.Errorf("initial TableCount got %d want %d", gotTab, initial.tables) |
| | } |
| |
|
| | gotGroup := mm.GroupCount() |
| | if gotGroup != initial.groups { |
| | t.Errorf("initial GroupCount got %d want %d", gotGroup, initial.groups) |
| | } |
| |
|
| | for i := 0; i < n; i++ { |
| | m[i] = i |
| | } |
| |
|
| | gotTab = mm.TableCount() |
| | if gotTab != after.tables { |
| | t.Errorf("after TableCount got %d want %d", gotTab, after.tables) |
| | } |
| |
|
| | gotGroup = mm.GroupCount() |
| | if gotGroup != after.groups { |
| | t.Errorf("after GroupCount got %d want %d", gotGroup, after.groups) |
| | } |
| | } |
| |
|
| | t.Run("mapliteral", func(t *testing.T) { |
| | for _, tc := range testCases { |
| | t.Run(fmt.Sprintf("n=%d", tc.n), func(t *testing.T) { |
| | t.Run("escape", func(t *testing.T) { |
| | m := escape(map[int]int{}) |
| | testMap(t, m, tc.n, tc.escape.initialLit, tc.escape.after) |
| | }) |
| | }) |
| | } |
| | }) |
| | t.Run("nohint", func(t *testing.T) { |
| | for _, tc := range testCases { |
| | t.Run(fmt.Sprintf("n=%d", tc.n), func(t *testing.T) { |
| | t.Run("escape", func(t *testing.T) { |
| | m := escape(make(map[int]int)) |
| | testMap(t, m, tc.n, tc.escape.initialLit, tc.escape.after) |
| | }) |
| | }) |
| | } |
| | }) |
| | t.Run("makemap", func(t *testing.T) { |
| | for _, tc := range testCases { |
| | t.Run(fmt.Sprintf("n=%d", tc.n), func(t *testing.T) { |
| | t.Run("escape", func(t *testing.T) { |
| | m := escape(make(map[int]int, tc.n)) |
| | testMap(t, m, tc.n, tc.escape.initialHint, tc.escape.after) |
| | }) |
| | }) |
| | } |
| | }) |
| | t.Run("makemap64", func(t *testing.T) { |
| | for _, tc := range testCases { |
| | t.Run(fmt.Sprintf("n=%d", tc.n), func(t *testing.T) { |
| | t.Run("escape", func(t *testing.T) { |
| | m := escape(make(map[int]int, int64(tc.n))) |
| | testMap(t, m, tc.n, tc.escape.initialHint, tc.escape.after) |
| | }) |
| | }) |
| | } |
| | }) |
| | } |
| |
|
| | func TestTombstoneGrow(t *testing.T) { |
| | tableSizes := []int{16, 32, 64, 128, 256} |
| | for _, tableSize := range tableSizes { |
| | for _, load := range []string{"low", "mid", "high"} { |
| | capacity := tableSize * 7 / 8 |
| | var initialElems int |
| | switch load { |
| | case "low": |
| | initialElems = capacity / 8 |
| | case "mid": |
| | initialElems = capacity / 2 |
| | case "high": |
| | initialElems = capacity |
| | } |
| | t.Run(fmt.Sprintf("tableSize=%d/elems=%d/load=%0.3f", tableSize, initialElems, float64(initialElems)/float64(tableSize)), func(t *testing.T) { |
| | allocs := testing.AllocsPerRun(1, func() { |
| | |
| | m := make(map[int]int, capacity) |
| | for i := range initialElems { |
| | m[i] = i |
| | } |
| |
|
| | |
| | |
| | |
| | nextKey := initialElems |
| | for range 100000 { |
| | for k := range m { |
| | delete(m, k) |
| | break |
| | } |
| | m[nextKey] = nextKey |
| | nextKey++ |
| | if len(m) != initialElems { |
| | t.Fatal("len(m) should remain constant") |
| | } |
| | } |
| | }) |
| |
|
| | |
| | |
| | |
| | |
| | allowed := float64(4 + 1*2) |
| | if initialElems == capacity { |
| | allowed += 2 |
| | } |
| | if allocs > allowed { |
| | t.Fatalf("got %v allocations, allowed %v", allocs, allowed) |
| | } |
| | }) |
| | } |
| | } |
| | } |
| |
|