diff options
| author | cuiweixie <cuiweixie@gmail.com> | 2023-04-03 20:14:06 +0800 |
|---|---|---|
| committer | Gopher Robot <gobot@golang.org> | 2023-05-24 04:19:06 +0000 |
| commit | c6fd0c22dca065fbcf8f4a6516be34db408b4397 (patch) | |
| tree | f1ac51f82f1c1ecbe99ce33a00729e73f6dee7bc /src | |
| parent | 5b93af7f4f61ccc7d770ee24641b2fd9c71c0329 (diff) | |
| download | go-c6fd0c22dca065fbcf8f4a6516be34db408b4397.tar.xz | |
maps,runtime: improve maps.Values
name old time/op new time/op delta
Values-10 8.67ms ± 0% 7.19ms ± 2% -17.05% (p=0.000 n=9+10)
name old alloc/op new alloc/op delta
Values-10 58.2kB ± 2% 48.3kB ± 2% -17.14% (p=0.000 n=9+10)
name old allocs/op new allocs/op delta
Values-10 0.00 0.00 ~ (all equal)
Change-Id: Idd35ea37514a21d97bdd6191c8fb8a478c00e414
Reviewed-on: https://go-review.googlesource.com/c/go/+/481436
Reviewed-by: Keith Randall <khr@golang.org>
Run-TryBot: xie cui <523516579@qq.com>
Reviewed-by: Keith Randall <khr@google.com>
Reviewed-by: Ian Lance Taylor <iant@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Auto-Submit: Keith Randall <khr@golang.org>
Diffstat (limited to 'src')
| -rw-r--r-- | src/maps/maps.go | 13 | ||||
| -rw-r--r-- | src/maps/maps_test.go | 27 | ||||
| -rw-r--r-- | src/runtime/map.go | 65 |
3 files changed, 102 insertions, 3 deletions
diff --git a/src/maps/maps.go b/src/maps/maps.go index dddfb37973..15ec456a17 100644 --- a/src/maps/maps.go +++ b/src/maps/maps.go @@ -24,16 +24,23 @@ func keysForBenchmarking[M ~map[K]V, K comparable, V any](m M, s []K) { keys(m, unsafe.Pointer(&s)) } +// values is implemented in the runtime package. +// +//go:noescape +func values(m any, slice unsafe.Pointer) + // 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 { r := make([]V, 0, len(m)) - for _, v := range m { - r = append(r, v) - } + values(m, unsafe.Pointer(&r)) return r } +func valuesForBenchmarking[M ~map[K]V, K comparable, V any](m M, s []V) { + values(m, unsafe.Pointer(&s)) +} + // Equal reports whether two maps contain the same key/value pairs. // Values are compared using ==. func Equal[M1, M2 ~map[K]V, K, V comparable](m1 M1, m2 M2) bool { diff --git a/src/maps/maps_test.go b/src/maps/maps_test.go index e7670839c9..6b92e0d8d6 100644 --- a/src/maps/maps_test.go +++ b/src/maps/maps_test.go @@ -62,6 +62,20 @@ func TestValues(t *testing.T) { if !slices.Equal(got2, want2) { t.Errorf("Values(%v) = %v, want %v", m2, got2, want2) } + + //test for oldbucket code path + var want3 []int + var m = make(map[int]int) + for i := 0; i < 840; i++ { + want3 = append(want3, i*i) + m[i] = i * i + } + + got3 := Values(m) + sort.Ints(got3) + if !slices.Equal(got3, want3) { + t.Errorf("Values(%v) = %v, want %v", m, got3, want3) + } } func TestEqual(t *testing.T) { @@ -246,3 +260,16 @@ func BenchmarkKeys(b *testing.B) { keysForBenchmarking(m, slice) } } + +func BenchmarkValues(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++ { + valuesForBenchmarking(m, slice) + } +} diff --git a/src/runtime/map.go b/src/runtime/map.go index 33685269cd..7b954759f1 100644 --- a/src/runtime/map.go +++ b/src/runtime/map.go @@ -1657,3 +1657,68 @@ func copyKeys(t *maptype, h *hmap, b *bmap, s *slice, offset uint8) { b = b.overflow(t) } } + +// values for implementing maps.values +// +//go:linkname values maps.values +func values(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 { + copyValues(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))) + copyValues(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 + } + copyValues(t, h, b, s, offset) + } + } + return +} + +func copyValues(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") + } + + ele := add(unsafe.Pointer(b), dataOffset+bucketCnt*uintptr(t.KeySize)+offi*uintptr(t.ValueSize)) + if t.IndirectElem() { + ele = *((*unsafe.Pointer)(ele)) + } + if s.len >= s.cap { + fatal("concurrent map read and map write") + } + typedmemmove(t.Elem, add(s.array, uintptr(s.len)*uintptr(t.ValueSize)), ele) + s.len++ + } + b = b.overflow(t) + } +} |
