maps: implement faster clone

│     base     │             experiment              │
            │    sec/op    │   sec/op     vs base                │
MapClone-24   66.802m ± 7%   3.348m ± 2%  -94.99% (p=0.000 n=10)

Fixes #70836

Change-Id: I9e192b1ee82e18f5580ff18918307042a337fdcc
Reviewed-on: https://go-review.googlesource.com/c/go/+/660175
Reviewed-by: Michael Pratt <mpratt@google.com>
Auto-Submit: Keith Randall <khr@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Keith Randall <khr@google.com>
This commit is contained in:
Keith Randall 2025-03-21 16:07:20 -07:00 committed by Gopher Robot
parent 6722c008c1
commit a645bc5eb9
4 changed files with 89 additions and 13 deletions

View File

@ -322,3 +322,32 @@ func (g *groupsReference) group(typ *abi.SwissMapType, i uint64) groupReference
data: unsafe.Pointer(uintptr(g.data) + offset),
}
}
func cloneGroup(typ *abi.SwissMapType, newGroup, oldGroup groupReference) {
typedmemmove(typ.Group, newGroup.data, oldGroup.data)
if typ.IndirectKey() {
// Deep copy keys if indirect.
for i := uintptr(0); i < abi.SwissMapGroupSlots; i++ {
oldKey := *(*unsafe.Pointer)(oldGroup.key(typ, i))
if oldKey == nil {
continue
}
newKey := newobject(typ.Key)
typedmemmove(typ.Key, newKey, oldKey)
*(*unsafe.Pointer)(newGroup.key(typ, i)) = newKey
}
}
if typ.IndirectElem() {
// Deep copy elems if indirect.
for i := uintptr(0); i < abi.SwissMapGroupSlots; i++ {
oldElem := *(*unsafe.Pointer)(oldGroup.elem(typ, i))
if oldElem == nil {
continue
}
newElem := newobject(typ.Elem)
typedmemmove(typ.Elem, newElem, oldElem)
*(*unsafe.Pointer)(newGroup.elem(typ, i)) = newElem
}
}
}

View File

@ -770,3 +770,40 @@ func (m *Map) clearSmall(typ *abi.SwissMapType) {
m.used = 0
m.clearSeq++
}
func (m *Map) Clone(typ *abi.SwissMapType) *Map {
// Note: this should never be called with a nil map.
if m.writing != 0 {
fatal("concurrent map clone and map write")
}
// Shallow copy the Map structure.
m2 := new(Map)
*m2 = *m
m = m2
// We need to just deep copy the dirPtr field.
if m.dirPtr == nil {
// delayed group allocation, nothing to do.
} else if m.dirLen == 0 {
// Clone one group.
oldGroup := groupReference{data: m.dirPtr}
newGroup := groupReference{data: newGroups(typ, 1).data}
cloneGroup(typ, newGroup, oldGroup)
m.dirPtr = newGroup.data
} else {
// Clone each (different) table.
oldDir := unsafe.Slice((**table)(m.dirPtr), m.dirLen)
newDir := make([]*table, m.dirLen)
for i, t := range oldDir {
if i > 0 && t == oldDir[i-1] {
newDir[i] = newDir[i-1]
continue
}
newDir[i] = t.clone(typ)
}
m.dirPtr = unsafe.Pointer(&newDir[0])
}
return m
}

View File

@ -1152,3 +1152,22 @@ func (s probeSeq) next() probeSeq {
s.offset = (s.offset + s.index) & s.mask
return s
}
func (t *table) clone(typ *abi.SwissMapType) *table {
// Shallow copy the table structure.
t2 := new(table)
*t2 = *t
t = t2
// We need to just deep copy the groups.data field.
oldGroups := t.groups
newGroups := newGroups(typ, oldGroups.lengthMask+1)
for i := uint64(0); i <= oldGroups.lengthMask; i++ {
oldGroup := oldGroups.group(typ, i)
newGroup := newGroups.group(typ, i)
cloneGroup(typ, newGroup, oldGroup)
}
t.groups = newGroups
return t
}

View File

@ -330,22 +330,13 @@ func mapinitnoop()
//go:linkname mapclone maps.clone
func mapclone(m any) any {
e := efaceOf(&m)
e.data = unsafe.Pointer(mapclone2((*abi.SwissMapType)(unsafe.Pointer(e._type)), (*maps.Map)(e.data)))
typ := (*abi.SwissMapType)(unsafe.Pointer(e._type))
map_ := (*maps.Map)(e.data)
map_ = map_.Clone(typ)
e.data = (unsafe.Pointer)(map_)
return m
}
func mapclone2(t *abi.SwissMapType, src *maps.Map) *maps.Map {
dst := makemap(t, int(src.Used()), nil)
var iter maps.Iter
iter.Init(t, src)
for iter.Next(); iter.Key() != nil; iter.Next() {
dst.Put(t, iter.Key(), iter.Elem())
}
return dst
}
// keys for implementing maps.keys
//
//go:linkname keys maps.keys