]> Cypherpunks repositories - gostls13.git/commitdiff
maps,runtime: improve maps.Clone
authorcuiweixie <cuiweixie@gmail.com>
Sun, 26 Feb 2023 23:15:50 +0000 (07:15 +0800)
committerKeith Randall <khr@golang.org>
Thu, 13 Apr 2023 17:28:28 +0000 (17:28 +0000)
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>
src/maps/maps.go
src/maps/maps.s [new file with mode: 0644]
src/maps/maps_test.go
src/runtime/map.go

index 25a0142eae11f0eb69ba46c1d0ef34c9c1c31b2b..27eea015015c0978bfaa049da7d17fd4fd01122e 100644 (file)
@@ -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
 }
 
+// clone is implemented in the runtime package.
+func clone(m any) any
+
 // Clone returns a copy of m.  This is a shallow clone:
 // the new keys and values are set using ordinary assignment.
 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 {
                return nil
        }
-       r := make(M, len(m))
-       for k, v := range m {
-               r[k] = v
-       }
-       return r
+       return clone(m).(M)
 }
 
 // Copy copies all key/value pairs in src adding them to dst.
diff --git a/src/maps/maps.s b/src/maps/maps.s
new file mode 100644 (file)
index 0000000..4e55778
--- /dev/null
@@ -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.
\ No newline at end of file
index a7e03ad9b08e9a7cc77787065472f6ae43f92d76..144f5375c960b5e96821f1fabc600b8321bccbd7 100644 (file)
@@ -179,3 +179,52 @@ func TestDeleteFunc(t *testing.T) {
                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])
+               }
+       }
+}
index 9c3a7e2b8cee5c39614e9248062de9e19ad15274..e98860fe7a83ed98627d5e9be232ee1c3c1051f8 100644 (file)
@@ -1445,3 +1445,151 @@ var zeroVal [maxZero]byte
 // map init function to this symbol. Defined in assembly so as to avoid
 // complications with instrumentation (coverage, etc).
 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
+}