Skip to content
Snippets Groups Projects
Commit 4e63ae46 authored by khr@golang.org's avatar khr@golang.org Committed by Gopher Robot
Browse files

internal/runtime/maps: make clear also erase tombstones

This will make future uses of the map faster because the probe
sequences will likely be shorter.

Change-Id: If10f3af49a5feaff7d1b82337bbbfb93bcd9dcb5
Reviewed-on: https://go-review.googlesource.com/c/go/+/633076


Auto-Submit: Keith Randall <khr@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: default avatarMichael Pratt <mpratt@google.com>
Reviewed-by: default avatarKeith Randall <khr@google.com>
parent 94c4cdc9
No related branches found
No related tags found
No related merge requests found
...@@ -159,6 +159,7 @@ func SwissMapType() *types.Type { ...@@ -159,6 +159,7 @@ func SwissMapType() *types.Type {
// globalShift uint8 // globalShift uint8
// //
// writing uint8 // writing uint8
// tombstonePossible bool
// // N.B Padding // // N.B Padding
// //
// clearSeq uint64 // clearSeq uint64
...@@ -172,6 +173,7 @@ func SwissMapType() *types.Type { ...@@ -172,6 +173,7 @@ func SwissMapType() *types.Type {
makefield("globalDepth", types.Types[types.TUINT8]), makefield("globalDepth", types.Types[types.TUINT8]),
makefield("globalShift", types.Types[types.TUINT8]), makefield("globalShift", types.Types[types.TUINT8]),
makefield("writing", types.Types[types.TUINT8]), makefield("writing", types.Types[types.TUINT8]),
makefield("tombstonePossible", types.Types[types.TBOOL]),
makefield("clearSeq", types.Types[types.TUINT64]), makefield("clearSeq", types.Types[types.TUINT64]),
} }
......
...@@ -191,6 +191,7 @@ func h2(h uintptr) uintptr { ...@@ -191,6 +191,7 @@ func h2(h uintptr) uintptr {
return h & 0x7f return h & 0x7f
} }
// Note: changes here must be reflected in cmd/compile/internal/reflectdata/map_swiss.go:SwissMapType.
type Map struct { type Map struct {
// The number of filled slots (i.e. the number of elements in all // The number of filled slots (i.e. the number of elements in all
// tables). Excludes deleted slots. // tables). Excludes deleted slots.
...@@ -235,6 +236,10 @@ type Map struct { ...@@ -235,6 +236,10 @@ type Map struct {
// that both sides will detect the race. // that both sides will detect the race.
writing uint8 writing uint8
// tombstonePossible is false if we know that no table in this map
// contains a tombstone.
tombstonePossible bool
// clearSeq is a sequence counter of calls to Clear. It is used to // clearSeq is a sequence counter of calls to Clear. It is used to
// detect map clears during iteration. // detect map clears during iteration.
clearSeq uint64 clearSeq uint64
...@@ -657,7 +662,9 @@ func (m *Map) Delete(typ *abi.SwissMapType, key unsafe.Pointer) { ...@@ -657,7 +662,9 @@ func (m *Map) Delete(typ *abi.SwissMapType, key unsafe.Pointer) {
m.deleteSmall(typ, hash, key) m.deleteSmall(typ, hash, key)
} else { } else {
idx := m.directoryIndex(hash) idx := m.directoryIndex(hash)
m.directoryAt(idx).Delete(typ, m, hash, key) if m.directoryAt(idx).Delete(typ, m, hash, key) {
m.tombstonePossible = true
}
} }
if m.used == 0 { if m.used == 0 {
...@@ -722,7 +729,7 @@ func (m *Map) deleteSmall(typ *abi.SwissMapType, hash uintptr, key unsafe.Pointe ...@@ -722,7 +729,7 @@ func (m *Map) deleteSmall(typ *abi.SwissMapType, hash uintptr, key unsafe.Pointe
// Clear deletes all entries from the map resulting in an empty map. // Clear deletes all entries from the map resulting in an empty map.
func (m *Map) Clear(typ *abi.SwissMapType) { func (m *Map) Clear(typ *abi.SwissMapType) {
if m == nil || m.Used() == 0 { if m == nil || m.Used() == 0 && !m.tombstonePossible {
return return
} }
...@@ -744,9 +751,10 @@ func (m *Map) Clear(typ *abi.SwissMapType) { ...@@ -744,9 +751,10 @@ func (m *Map) Clear(typ *abi.SwissMapType) {
lastTab = t lastTab = t
} }
m.used = 0 m.used = 0
m.clearSeq++ m.tombstonePossible = false
// TODO: shrink directory? // TODO: shrink directory?
} }
m.clearSeq++
// Reset the hash seed to make it more difficult for attackers to // Reset the hash seed to make it more difficult for attackers to
// repeatedly trigger hash collisions. See https://go.dev/issue/25237. // repeatedly trigger hash collisions. See https://go.dev/issue/25237.
...@@ -767,7 +775,6 @@ func (m *Map) clearSmall(typ *abi.SwissMapType) { ...@@ -767,7 +775,6 @@ func (m *Map) clearSmall(typ *abi.SwissMapType) {
g.ctrls().setEmpty() g.ctrls().setEmpty()
m.used = 0 m.used = 0
m.clearSeq++
} }
func (m *Map) Clone(typ *abi.SwissMapType) *Map { func (m *Map) Clone(typ *abi.SwissMapType) *Map {
......
...@@ -103,7 +103,7 @@ func (t *table) reset(typ *abi.SwissMapType, capacity uint16) { ...@@ -103,7 +103,7 @@ func (t *table) reset(typ *abi.SwissMapType, capacity uint16) {
groupCount := uint64(capacity) / abi.SwissMapGroupSlots groupCount := uint64(capacity) / abi.SwissMapGroupSlots
t.groups = newGroups(typ, groupCount) t.groups = newGroups(typ, groupCount)
t.capacity = capacity t.capacity = capacity
t.resetGrowthLeft() t.growthLeft = t.maxGrowthLeft()
for i := uint64(0); i <= t.groups.lengthMask; i++ { for i := uint64(0); i <= t.groups.lengthMask; i++ {
g := t.groups.group(typ, i) g := t.groups.group(typ, i)
...@@ -111,9 +111,9 @@ func (t *table) reset(typ *abi.SwissMapType, capacity uint16) { ...@@ -111,9 +111,9 @@ func (t *table) reset(typ *abi.SwissMapType, capacity uint16) {
} }
} }
// Preconditions: table must be empty. // maxGrowthLeft is the number of inserts we can do before
func (t *table) resetGrowthLeft() { // resizing, starting from an empty table.
var growthLeft uint16 func (t *table) maxGrowthLeft() uint16 {
if t.capacity == 0 { if t.capacity == 0 {
// No real reason to support zero capacity table, since an // No real reason to support zero capacity table, since an
// empty Map simply won't have a table. // empty Map simply won't have a table.
...@@ -125,15 +125,15 @@ func (t *table) resetGrowthLeft() { ...@@ -125,15 +125,15 @@ func (t *table) resetGrowthLeft() {
// //
// TODO(go.dev/issue/54766): With a special case in probing for // TODO(go.dev/issue/54766): With a special case in probing for
// single-group tables, we could fill all slots. // single-group tables, we could fill all slots.
growthLeft = t.capacity - 1 return t.capacity - 1
} else { } else {
if t.capacity*maxAvgGroupLoad < t.capacity { if t.capacity*maxAvgGroupLoad < t.capacity {
// TODO(prattmic): Do something cleaner. // TODO(prattmic): Do something cleaner.
panic("overflow") panic("overflow")
} }
growthLeft = (t.capacity * maxAvgGroupLoad) / abi.SwissMapGroupSlots return (t.capacity * maxAvgGroupLoad) / abi.SwissMapGroupSlots
} }
t.growthLeft = growthLeft
} }
func (t *table) Used() uint64 { func (t *table) Used() uint64 {
...@@ -417,7 +417,8 @@ func (t *table) uncheckedPutSlot(typ *abi.SwissMapType, hash uintptr, key, elem ...@@ -417,7 +417,8 @@ func (t *table) uncheckedPutSlot(typ *abi.SwissMapType, hash uintptr, key, elem
} }
} }
func (t *table) Delete(typ *abi.SwissMapType, m *Map, hash uintptr, key unsafe.Pointer) { // Delete returns true if it put a tombstone in t.
func (t *table) Delete(typ *abi.SwissMapType, m *Map, hash uintptr, key unsafe.Pointer) bool {
seq := makeProbeSeq(h1(hash), t.groups.lengthMask) seq := makeProbeSeq(h1(hash), t.groups.lengthMask)
for ; ; seq = seq.next() { for ; ; seq = seq.next() {
g := t.groups.group(typ, seq.offset) g := t.groups.group(typ, seq.offset)
...@@ -466,15 +467,17 @@ func (t *table) Delete(typ *abi.SwissMapType, m *Map, hash uintptr, key unsafe.P ...@@ -466,15 +467,17 @@ func (t *table) Delete(typ *abi.SwissMapType, m *Map, hash uintptr, key unsafe.P
// full now, we can simply remove the element. // full now, we can simply remove the element.
// Otherwise, we create a tombstone to mark the // Otherwise, we create a tombstone to mark the
// slot as deleted. // slot as deleted.
var tombstone bool
if g.ctrls().matchEmpty() != 0 { if g.ctrls().matchEmpty() != 0 {
g.ctrls().set(i, ctrlEmpty) g.ctrls().set(i, ctrlEmpty)
t.growthLeft++ t.growthLeft++
} else { } else {
g.ctrls().set(i, ctrlDeleted) g.ctrls().set(i, ctrlDeleted)
tombstone = true
} }
t.checkInvariants(typ, m) t.checkInvariants(typ, m)
return return tombstone
} }
match = match.removeFirst() match = match.removeFirst()
} }
...@@ -483,7 +486,7 @@ func (t *table) Delete(typ *abi.SwissMapType, m *Map, hash uintptr, key unsafe.P ...@@ -483,7 +486,7 @@ func (t *table) Delete(typ *abi.SwissMapType, m *Map, hash uintptr, key unsafe.P
if match != 0 { if match != 0 {
// Finding an empty slot means we've reached the end of // Finding an empty slot means we've reached the end of
// the probe sequence. // the probe sequence.
return return false
} }
} }
} }
...@@ -593,14 +596,19 @@ func (t *table) tombstones() uint16 { ...@@ -593,14 +596,19 @@ func (t *table) tombstones() uint16 {
// Clear deletes all entries from the map resulting in an empty map. // Clear deletes all entries from the map resulting in an empty map.
func (t *table) Clear(typ *abi.SwissMapType) { func (t *table) Clear(typ *abi.SwissMapType) {
mgl := t.maxGrowthLeft()
if t.used == 0 && t.growthLeft == mgl { // no current entries and no tombstones
return
}
for i := uint64(0); i <= t.groups.lengthMask; i++ { for i := uint64(0); i <= t.groups.lengthMask; i++ {
g := t.groups.group(typ, i) g := t.groups.group(typ, i)
typedmemclr(typ.Group, g.data) if g.ctrls().matchFull() != 0 {
typedmemclr(typ.Group, g.data)
}
g.ctrls().setEmpty() g.ctrls().setEmpty()
} }
t.used = 0 t.used = 0
t.resetGrowthLeft() t.growthLeft = mgl
} }
type Iter struct { type Iter struct {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment