Skip to content

Commit dbe3522

Browse files
committed
runtime: fix hashmap load factor computation
overLoadFactor wasn't really doing what it says it does. It was reporting overOrEqualToLoadFactor. That's actually what we want when adding an entry to a map, but it isn't what we want when constructing a map in the first place. The impetus for this change is that if you make a map with a hint of exactly 8 (which happens, for example, with the unitMap in time/format.go), we allocate 2 buckets for it instead of 1. Instead, make overLoadFactor really report when it is > the max allowed load factor, not >=. Adjust the callers who want to ensure that the map is no more than the max load factor after an insertion by adding a +1 to the current (pre-addition) size. Change-Id: Ie8d85344800a9a870036b637b1031ddd9e4b93f9 Reviewed-on: https://go-review.googlesource.com/61053 Run-TryBot: Keith Randall <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Martin Möhrmann <[email protected]>
1 parent a6a92b1 commit dbe3522

File tree

4 files changed

+40
-6
lines changed

4 files changed

+40
-6
lines changed

src/runtime/export_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,3 +376,8 @@ func (rw *RWMutex) Lock() {
376376
func (rw *RWMutex) Unlock() {
377377
rw.rw.unlock()
378378
}
379+
380+
func MapBuckets(m map[int]int) int {
381+
h := *(**hmap)(unsafe.Pointer(&m))
382+
return 1 << h.B
383+
}

src/runtime/hashmap.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -573,7 +573,7 @@ again:
573573

574574
// If we hit the max load factor or we have too many overflow buckets,
575575
// and we're not already in the middle of growing, start growing.
576-
if !h.growing() && (overLoadFactor(h.count, h.B) || tooManyOverflowBuckets(h.noverflow, h.B)) {
576+
if !h.growing() && (overLoadFactor(h.count+1, h.B) || tooManyOverflowBuckets(h.noverflow, h.B)) {
577577
hashGrow(t, h)
578578
goto again // Growing the table invalidates everything, so try again
579579
}
@@ -904,7 +904,7 @@ func hashGrow(t *maptype, h *hmap) {
904904
// Otherwise, there are too many overflow buckets,
905905
// so keep the same number of buckets and "grow" laterally.
906906
bigger := uint8(1)
907-
if !overLoadFactor(h.count, h.B) {
907+
if !overLoadFactor(h.count+1, h.B) {
908908
bigger = 0
909909
h.flags |= sameSizeGrow
910910
}
@@ -944,7 +944,7 @@ func hashGrow(t *maptype, h *hmap) {
944944

945945
// overLoadFactor reports whether count items placed in 1<<B buckets is over loadFactor.
946946
func overLoadFactor(count int, B uint8) bool {
947-
return count >= bucketCnt && uintptr(count) >= loadFactorNum*(bucketShift(B)/loadFactorDen)
947+
return count > bucketCnt && uintptr(count) > loadFactorNum*(bucketShift(B)/loadFactorDen)
948948
}
949949

950950
// tooManyOverflowBuckets reports whether noverflow buckets is too many for a map with 1<<B buckets.

src/runtime/hashmap_fast.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,7 @@ again:
406406

407407
// If we hit the max load factor or we have too many overflow buckets,
408408
// and we're not already in the middle of growing, start growing.
409-
if !h.growing() && (overLoadFactor(h.count, h.B) || tooManyOverflowBuckets(h.noverflow, h.B)) {
409+
if !h.growing() && (overLoadFactor(h.count+1, h.B) || tooManyOverflowBuckets(h.noverflow, h.B)) {
410410
hashGrow(t, h)
411411
goto again // Growing the table invalidates everything, so try again
412412
}
@@ -495,7 +495,7 @@ again:
495495

496496
// If we hit the max load factor or we have too many overflow buckets,
497497
// and we're not already in the middle of growing, start growing.
498-
if !h.growing() && (overLoadFactor(h.count, h.B) || tooManyOverflowBuckets(h.noverflow, h.B)) {
498+
if !h.growing() && (overLoadFactor(h.count+1, h.B) || tooManyOverflowBuckets(h.noverflow, h.B)) {
499499
hashGrow(t, h)
500500
goto again // Growing the table invalidates everything, so try again
501501
}
@@ -596,7 +596,7 @@ again:
596596

597597
// If we hit the max load factor or we have too many overflow buckets,
598598
// and we're not already in the middle of growing, start growing.
599-
if !h.growing() && (overLoadFactor(h.count, h.B) || tooManyOverflowBuckets(h.noverflow, h.B)) {
599+
if !h.growing() && (overLoadFactor(h.count+1, h.B) || tooManyOverflowBuckets(h.noverflow, h.B)) {
600600
hashGrow(t, h)
601601
goto again // Growing the table invalidates everything, so try again
602602
}

src/runtime/map_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -596,6 +596,35 @@ func TestIgnoreBogusMapHint(t *testing.T) {
596596
}
597597
}
598598

599+
func TestMapBuckets(t *testing.T) {
600+
// Test that maps of different sizes have the right number of buckets.
601+
// 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
614+
}
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)
617+
}
618+
m = make(map[int]int, tt.n)
619+
for i := 0; i < tt.n; i++ {
620+
m[i] = i
621+
}
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)
624+
}
625+
}
626+
}
627+
599628
func benchmarkMapPop(b *testing.B, n int) {
600629
m := map[int]int{}
601630
for i := 0; i < b.N; i++ {

0 commit comments

Comments
 (0)