]> Cypherpunks repositories - gostls13.git/commitdiff
maps,runtime: improve maps.Keys
authorcuiweixie <cuiweixie@gmail.com>
Sun, 2 Apr 2023 07:49:58 +0000 (15:49 +0800)
committerKeith Randall <khr@golang.org>
Fri, 19 May 2023 15:54:43 +0000 (15:54 +0000)
name     old time/op    new time/op    delta
Keys-10    8.65ms ± 0%    7.06ms ± 0%  -18.41%  (p=0.000 n=9+9)

name     old alloc/op   new alloc/op   delta
Keys-10    58.2kB ± 1%    47.4kB ± 2%  -18.54%  (p=0.000 n=10+10)

name     old allocs/op  new allocs/op  delta
Keys-10      0.00           0.00          ~     (all equal)

Change-Id: Ia7061c37be89e906e79bc3ef3bb1ef0d7913f9f6
Reviewed-on: https://go-review.googlesource.com/c/go/+/481435
Reviewed-by: Keith Randall <khr@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@google.com>
Reviewed-by: Heschi Kreinick <heschi@google.com>
Run-TryBot: xie cui <523516579@qq.com>

src/go/build/deps_test.go
src/maps/maps.go
src/maps/maps_test.go
src/runtime/map.go

index e93422addcace77f1dc5f4de454422998f74cedf..89c7035d35667f3fde6c0f250b927423967e9b1b 100644 (file)
@@ -46,7 +46,7 @@ var depsRules = `
          internal/goexperiment, internal/goos,
          internal/goversion, internal/nettrace, internal/platform,
          log/internal,
-         maps, unicode/utf8, unicode/utf16, unicode,
+         unicode/utf8, unicode/utf16, unicode,
          unsafe;
 
        # slices depends on unsafe for overlapping check.
@@ -57,6 +57,8 @@ var depsRules = `
        internal/goarch, unsafe
        < internal/abi;
 
+       unsafe < maps;
+
        # RUNTIME is the core runtime group of packages, all of them very light-weight.
        internal/abi, internal/cpu, internal/goarch,
        internal/coverage/rtcov, internal/godebugs, internal/goexperiment,
index 27eea015015c0978bfaa049da7d17fd4fd01122e..dddfb37973c1704a5208e3fb50569f745169c141 100644 (file)
@@ -5,16 +5,25 @@
 // Package maps defines various functions useful with maps of any type.
 package maps
 
+import "unsafe"
+
+// keys is implemented in the runtime package.
+//
+//go:noescape
+func keys(m any, slice unsafe.Pointer)
+
 // Keys returns the keys of the map m.
 // The keys will be in an indeterminate order.
 func Keys[M ~map[K]V, K comparable, V any](m M) []K {
        r := make([]K, 0, len(m))
-       for k := range m {
-               r = append(r, k)
-       }
+       keys(m, unsafe.Pointer(&r))
        return r
 }
 
+func keysForBenchmarking[M ~map[K]V, K comparable, V any](m M, s []K) {
+       keys(m, unsafe.Pointer(&s))
+}
+
 // Values returns the values of the map m.
 // The values will be in an indeterminate order.
 func Values[M ~map[K]V, K comparable, V any](m M) []V {
index 1825df5b7716ab4b3f094a1595e6cbb77668d77c..a7a8c10f71fc92262e206a85a31420a5aef99218 100644 (file)
@@ -29,6 +29,23 @@ func TestKeys(t *testing.T) {
        if !slices.Equal(got2, want) {
                t.Errorf("Keys(%v) = %v, want %v", m2, got2, want)
        }
+
+       // test for oldbucket code path
+       // We grow from 128 to 256 buckets at size 832 (6.5 * 128).
+       // Then we have to evacuate 128 buckets, which means we'll be done evacuation at 832+128=960 elements inserted.
+       // so 840 is a good number to test for oldbucket code path.
+       var want3 []int
+       var m = make(map[int]int)
+       for i := 0; i < 840; i++ {
+               want3 = append(want3, i)
+               m[i] = i
+       }
+
+       got3 := Keys(m)
+       sort.Ints(got3)
+       if !slices.Equal(got3, want3) {
+               t.Errorf("Keys(%v) = %v, want %v", m, got3, want3)
+       }
 }
 
 func TestValues(t *testing.T) {
@@ -216,3 +233,18 @@ func TestCloneWithMapAssign(t *testing.T) {
                }
        }
 }
+
+var keysArr []int
+
+func BenchmarkKeys(b *testing.B) {
+       m := make(map[int]int, 1000000)
+       for i := 0; i < 1000000; i++ {
+               m[i] = i
+       }
+       b.ResetTimer()
+
+       slice := make([]int, 0, len(m))
+       for i := 0; i < b.N; i++ {
+               keysForBenchmarking(m, slice)
+       }
+}
index a1fe08f7581b3d0b277a64a09a8de9324da4fa0d..33685269cd5a1a0032790706a16039afe7f0443a 100644 (file)
@@ -1593,3 +1593,67 @@ func mapclone2(t *maptype, src *hmap) *hmap {
        }
        return dst
 }
+
+// keys for implementing maps.keys
+//
+//go:linkname keys maps.keys
+func keys(m any, p unsafe.Pointer) {
+       e := efaceOf(&m)
+       t := (*maptype)(unsafe.Pointer(e._type))
+       h := (*hmap)(e.data)
+
+       if h == nil || h.count == 0 {
+               return
+       }
+       s := (*slice)(p)
+       r := int(fastrand())
+       offset := uint8(r >> h.B & (bucketCnt - 1))
+       if h.B == 0 {
+               copyKeys(t, h, (*bmap)(h.buckets), s, offset)
+               return
+       }
+       arraySize := int(bucketShift(h.B))
+       buckets := h.buckets
+       for i := 0; i < arraySize; i++ {
+               bucket := (i + r) & (arraySize - 1)
+               b := (*bmap)(add(buckets, uintptr(bucket)*uintptr(t.BucketSize)))
+               copyKeys(t, h, b, s, offset)
+       }
+
+       if h.growing() {
+               oldArraySize := int(h.noldbuckets())
+               for i := 0; i < oldArraySize; i++ {
+                       bucket := (i + r) & (oldArraySize - 1)
+                       b := (*bmap)(add(h.oldbuckets, uintptr(bucket)*uintptr(t.BucketSize)))
+                       if evacuated(b) {
+                               continue
+                       }
+                       copyKeys(t, h, b, s, offset)
+               }
+       }
+       return
+}
+
+func copyKeys(t *maptype, h *hmap, b *bmap, s *slice, offset uint8) {
+       for b != nil {
+               for i := uintptr(0); i < bucketCnt; i++ {
+                       offi := (i + uintptr(offset)) & (bucketCnt - 1)
+                       if isEmpty(b.tophash[offi]) {
+                               continue
+                       }
+                       if h.flags&hashWriting != 0 {
+                               fatal("concurrent map read and map write")
+                       }
+                       k := add(unsafe.Pointer(b), dataOffset+offi*uintptr(t.KeySize))
+                       if t.IndirectKey() {
+                               k = *((*unsafe.Pointer)(k))
+                       }
+                       if s.len >= s.cap {
+                               fatal("concurrent map read and map write")
+                       }
+                       typedmemmove(t.Key, add(s.array, uintptr(s.len)*uintptr(t.KeySize)), k)
+                       s.len++
+               }
+               b = b.overflow(t)
+       }
+}