mirror of
https://github.com/golang/go
synced 2024-09-29 12:14:28 -06:00
maps,runtime: improve maps.Clone
name old time/op new time/op delta MapClone-10 65.8ms ± 7% 10.3ms ± 2% -84.30% (p=0.000 n=10+9) name old alloc/op new alloc/op delta MapClone-10 40.2MB ± 0% 40.5MB ± 0% +0.57% (p=0.000 n=10+9) name old allocs/op new allocs/op delta MapClone-10 20.0 ± 0% 23.0 ± 0% +15.00% (p=0.000 n=10+10) Updates #58740. Change-Id: I148501e723cb2124f02045400e7ceb36af0871c8 Reviewed-on: https://go-review.googlesource.com/c/go/+/471400 Reviewed-by: Heschi Kreinick <heschi@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Keith Randall <khr@golang.org> Run-TryBot: xie cui <523516579@qq.com> Reviewed-by: Keith Randall <khr@google.com>
This commit is contained in:
parent
1312d9e6da
commit
b5a7f2eef7
@ -53,6 +53,9 @@ func EqualFunc[M1 ~map[K]V1, M2 ~map[K]V2, K comparable, V1, V2 any](m1 M1, m2 M
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// clone is implemented in the runtime package.
|
||||||
|
func clone(m any) any
|
||||||
|
|
||||||
// Clone returns a copy of m. This is a shallow clone:
|
// Clone returns a copy of m. This is a shallow clone:
|
||||||
// the new keys and values are set using ordinary assignment.
|
// the new keys and values are set using ordinary assignment.
|
||||||
func Clone[M ~map[K]V, K comparable, V any](m M) M {
|
func Clone[M ~map[K]V, K comparable, V any](m M) M {
|
||||||
@ -60,11 +63,7 @@ func Clone[M ~map[K]V, K comparable, V any](m M) M {
|
|||||||
if m == nil {
|
if m == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
r := make(M, len(m))
|
return clone(m).(M)
|
||||||
for k, v := range m {
|
|
||||||
r[k] = v
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy copies all key/value pairs in src adding them to dst.
|
// Copy copies all key/value pairs in src adding them to dst.
|
||||||
|
5
src/maps/maps.s
Normal file
5
src/maps/maps.s
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
// Copyright 2023 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.
|
||||||
|
|
||||||
|
// need this empty asm file to enable linkname.
|
@ -179,3 +179,52 @@ func TestDeleteFunc(t *testing.T) {
|
|||||||
t.Errorf("DeleteFunc result = %v, want %v", mc, want)
|
t.Errorf("DeleteFunc result = %v, want %v", mc, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var n map[int]int
|
||||||
|
|
||||||
|
func BenchmarkMapClone(b *testing.B) {
|
||||||
|
var m = make(map[int]int)
|
||||||
|
for i := 0; i < 1000000; i++ {
|
||||||
|
m[i] = i
|
||||||
|
}
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
n = Clone(m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCloneWithDelete(t *testing.T) {
|
||||||
|
var m = make(map[int]int)
|
||||||
|
for i := 0; i < 32; i++ {
|
||||||
|
m[i] = i
|
||||||
|
}
|
||||||
|
for i := 8; i < 32; i++ {
|
||||||
|
delete(m, i)
|
||||||
|
}
|
||||||
|
m2 := Clone(m)
|
||||||
|
if len(m2) != 8 {
|
||||||
|
t.Errorf("len2(m2) = %d, want %d", len(m2), 8)
|
||||||
|
}
|
||||||
|
for i := 0; i < 8; i++ {
|
||||||
|
if m2[i] != m[i] {
|
||||||
|
t.Errorf("m2[%d] = %d, want %d", i, m2[i], m[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCloneWithMapAssign(t *testing.T) {
|
||||||
|
var m = make(map[int]int)
|
||||||
|
const N = 25
|
||||||
|
for i := 0; i < N; i++ {
|
||||||
|
m[i] = i
|
||||||
|
}
|
||||||
|
m2 := Clone(m)
|
||||||
|
if len(m2) != N {
|
||||||
|
t.Errorf("len2(m2) = %d, want %d", len(m2), N)
|
||||||
|
}
|
||||||
|
for i := 0; i < N; i++ {
|
||||||
|
if m2[i] != m[i] {
|
||||||
|
t.Errorf("m2[%d] = %d, want %d", i, m2[i], m[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1445,3 +1445,151 @@ var zeroVal [maxZero]byte
|
|||||||
// map init function to this symbol. Defined in assembly so as to avoid
|
// map init function to this symbol. Defined in assembly so as to avoid
|
||||||
// complications with instrumentation (coverage, etc).
|
// complications with instrumentation (coverage, etc).
|
||||||
func mapinitnoop()
|
func mapinitnoop()
|
||||||
|
|
||||||
|
// mapclone for implementing maps.Clone
|
||||||
|
//
|
||||||
|
//go:linkname mapclone maps.clone
|
||||||
|
func mapclone(m any) any {
|
||||||
|
e := efaceOf(&m)
|
||||||
|
e.data = unsafe.Pointer(mapclone2((*maptype)(unsafe.Pointer(e._type)), (*hmap)(e.data)))
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// moveToBmap moves a bucket from src to dst. It returns the destination bucket or new destination bucket if it overflows
|
||||||
|
// and the pos that the next key/value will be written, if pos == bucketCnt means needs to written in overflow bucket.
|
||||||
|
func moveToBmap(t *maptype, h *hmap, dst *bmap, pos int, src *bmap) (*bmap, int) {
|
||||||
|
for i := 0; i < bucketCnt; i++ {
|
||||||
|
if isEmpty(src.tophash[i]) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for ; pos < bucketCnt; pos++ {
|
||||||
|
if isEmpty(dst.tophash[pos]) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if pos == bucketCnt {
|
||||||
|
dst = h.newoverflow(t, dst)
|
||||||
|
pos = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
srcK := add(unsafe.Pointer(src), dataOffset+uintptr(i)*uintptr(t.keysize))
|
||||||
|
srcEle := add(unsafe.Pointer(src), dataOffset+bucketCnt*uintptr(t.keysize)+uintptr(i)*uintptr(t.elemsize))
|
||||||
|
dstK := add(unsafe.Pointer(dst), dataOffset+uintptr(pos)*uintptr(t.keysize))
|
||||||
|
dstEle := add(unsafe.Pointer(dst), dataOffset+bucketCnt*uintptr(t.keysize)+uintptr(pos)*uintptr(t.elemsize))
|
||||||
|
|
||||||
|
dst.tophash[pos] = src.tophash[i]
|
||||||
|
if t.indirectkey() {
|
||||||
|
*(*unsafe.Pointer)(dstK) = *(*unsafe.Pointer)(srcK)
|
||||||
|
} else {
|
||||||
|
typedmemmove(t.key, dstK, srcK)
|
||||||
|
}
|
||||||
|
if t.indirectelem() {
|
||||||
|
*(*unsafe.Pointer)(dstEle) = *(*unsafe.Pointer)(srcEle)
|
||||||
|
} else {
|
||||||
|
typedmemmove(t.elem, dstEle, srcEle)
|
||||||
|
}
|
||||||
|
pos++
|
||||||
|
h.count++
|
||||||
|
}
|
||||||
|
return dst, pos
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapclone2(t *maptype, src *hmap) *hmap {
|
||||||
|
dst := makemap(t, src.count, nil)
|
||||||
|
dst.hash0 = src.hash0
|
||||||
|
dst.nevacuate = 0
|
||||||
|
//flags do not need to be copied here, just like a new map has no flags.
|
||||||
|
|
||||||
|
if src.count == 0 {
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
if src.flags&hashWriting != 0 {
|
||||||
|
fatal("concurrent map clone and map write")
|
||||||
|
}
|
||||||
|
|
||||||
|
if src.B == 0 {
|
||||||
|
dst.buckets = newobject(t.bucket)
|
||||||
|
dst.count = src.count
|
||||||
|
typedmemmove(t.bucket, dst.buckets, src.buckets)
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
//src.B != 0
|
||||||
|
if dst.B == 0 {
|
||||||
|
dst.buckets = newobject(t.bucket)
|
||||||
|
}
|
||||||
|
dstArraySize := int(bucketShift(dst.B))
|
||||||
|
srcArraySize := int(bucketShift(src.B))
|
||||||
|
for i := 0; i < dstArraySize; i++ {
|
||||||
|
dstBmap := (*bmap)(add(dst.buckets, uintptr(i*int(t.bucketsize))))
|
||||||
|
pos := 0
|
||||||
|
for j := 0; j < srcArraySize; j += dstArraySize {
|
||||||
|
srcBmap := (*bmap)(add(src.buckets, uintptr((i+j)*int(t.bucketsize))))
|
||||||
|
for srcBmap != nil {
|
||||||
|
dstBmap, pos = moveToBmap(t, dst, dstBmap, pos, srcBmap)
|
||||||
|
srcBmap = srcBmap.overflow(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if src.oldbuckets == nil {
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
oldB := src.B
|
||||||
|
srcOldbuckets := src.oldbuckets
|
||||||
|
if !src.sameSizeGrow() {
|
||||||
|
oldB--
|
||||||
|
}
|
||||||
|
oldSrcArraySize := int(bucketShift(oldB))
|
||||||
|
|
||||||
|
for i := 0; i < oldSrcArraySize; i++ {
|
||||||
|
srcBmap := (*bmap)(add(srcOldbuckets, uintptr(i*int(t.bucketsize))))
|
||||||
|
if evacuated(srcBmap) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if oldB >= dst.B { // main bucket bits in dst is less than oldB bits in src
|
||||||
|
dstBmap := (*bmap)(add(dst.buckets, uintptr(i)&bucketMask(dst.B)))
|
||||||
|
for dstBmap.overflow(t) != nil {
|
||||||
|
dstBmap = dstBmap.overflow(t)
|
||||||
|
}
|
||||||
|
pos := 0
|
||||||
|
for srcBmap != nil {
|
||||||
|
dstBmap, pos = moveToBmap(t, dst, dstBmap, pos, srcBmap)
|
||||||
|
srcBmap = srcBmap.overflow(t)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for srcBmap != nil {
|
||||||
|
// move from oldBlucket to new bucket
|
||||||
|
for i := uintptr(0); i < bucketCnt; i++ {
|
||||||
|
if isEmpty(srcBmap.tophash[i]) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if src.flags&hashWriting != 0 {
|
||||||
|
fatal("concurrent map clone and map write")
|
||||||
|
}
|
||||||
|
|
||||||
|
srcK := add(unsafe.Pointer(srcBmap), dataOffset+i*uintptr(t.keysize))
|
||||||
|
if t.indirectkey() {
|
||||||
|
srcK = *((*unsafe.Pointer)(srcK))
|
||||||
|
}
|
||||||
|
|
||||||
|
srcEle := add(unsafe.Pointer(srcBmap), dataOffset+bucketCnt*uintptr(t.keysize)+i*uintptr(t.elemsize))
|
||||||
|
if t.indirectelem() {
|
||||||
|
srcEle = *((*unsafe.Pointer)(srcEle))
|
||||||
|
}
|
||||||
|
dstEle := mapassign(t, dst, srcK)
|
||||||
|
typedmemmove(t.elem, dstEle, srcEle)
|
||||||
|
}
|
||||||
|
srcBmap = srcBmap.overflow(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user