2014-07-16 14:16:19 -07:00
|
|
|
// Copyright 2014 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.
|
|
|
|
|
|
|
|
|
|
package runtime
|
|
|
|
|
|
|
|
|
|
// This file contains the implementation of Go's map type.
|
|
|
|
|
//
|
2016-03-01 23:21:55 +00:00
|
|
|
// A map is just a hash table. The data is arranged
|
|
|
|
|
// into an array of buckets. Each bucket contains up to
|
2019-04-22 13:37:08 -07:00
|
|
|
// 8 key/elem pairs. The low-order bits of the hash are
|
2016-03-01 23:21:55 +00:00
|
|
|
// used to select a bucket. Each bucket contains a few
|
2014-07-16 14:16:19 -07:00
|
|
|
// high-order bits of each hash to distinguish the entries
|
|
|
|
|
// within a single bucket.
|
|
|
|
|
//
|
|
|
|
|
// If more than 8 keys hash to a bucket, we chain on
|
|
|
|
|
// extra buckets.
|
|
|
|
|
//
|
|
|
|
|
// When the hashtable grows, we allocate a new array
|
2016-03-01 23:21:55 +00:00
|
|
|
// of buckets twice as big. Buckets are incrementally
|
2014-07-16 14:16:19 -07:00
|
|
|
// copied from the old bucket array to the new bucket array.
|
|
|
|
|
//
|
|
|
|
|
// Map iterators walk through the array of buckets and
|
|
|
|
|
// return the keys in walk order (bucket #, then overflow
|
|
|
|
|
// chain order, then bucket index). To maintain iteration
|
|
|
|
|
// semantics, we never move keys within their bucket (if
|
|
|
|
|
// we did, keys might be returned 0 or 2 times). When
|
|
|
|
|
// growing the table, iterators remain iterating through the
|
|
|
|
|
// old table and must check the new table if the bucket
|
|
|
|
|
// they are iterating through has been moved ("evacuated")
|
|
|
|
|
// to the new table.
|
|
|
|
|
|
|
|
|
|
// Picking loadFactor: too large and we have lots of overflow
|
2016-03-01 23:21:55 +00:00
|
|
|
// buckets, too small and we waste a lot of space. I wrote
|
2014-07-16 14:16:19 -07:00
|
|
|
// a simple program to check some stats for different loads:
|
2019-04-22 13:37:08 -07:00
|
|
|
// (64-bit, 8 byte keys and elems)
|
2014-07-16 14:16:19 -07:00
|
|
|
// loadFactor %overflow bytes/entry hitprobe missprobe
|
|
|
|
|
// 4.00 2.13 20.77 3.00 4.00
|
|
|
|
|
// 4.50 4.05 17.30 3.25 4.50
|
|
|
|
|
// 5.00 6.85 14.77 3.50 5.00
|
|
|
|
|
// 5.50 10.55 12.94 3.75 5.50
|
|
|
|
|
// 6.00 15.27 11.67 4.00 6.00
|
|
|
|
|
// 6.50 20.90 10.79 4.25 6.50
|
|
|
|
|
// 7.00 27.14 10.15 4.50 7.00
|
|
|
|
|
// 7.50 34.03 9.73 4.75 7.50
|
|
|
|
|
// 8.00 41.10 9.40 5.00 8.00
|
|
|
|
|
//
|
|
|
|
|
// %overflow = percentage of buckets which have an overflow bucket
|
2019-04-22 13:37:08 -07:00
|
|
|
// bytes/entry = overhead bytes used per key/elem pair
|
2014-07-16 14:16:19 -07:00
|
|
|
// hitprobe = # of entries to check when looking up a present key
|
|
|
|
|
// missprobe = # of entries to check when looking up an absent key
|
|
|
|
|
//
|
|
|
|
|
// Keep in mind this data is for maximally loaded tables, i.e. just
|
2016-03-01 23:21:55 +00:00
|
|
|
// before the table grows. Typical tables will be somewhat less loaded.
|
2014-07-16 14:16:19 -07:00
|
|
|
|
|
|
|
|
import (
|
2021-05-21 13:37:19 -04:00
|
|
|
"internal/abi"
|
2021-06-17 19:10:18 +00:00
|
|
|
"internal/goarch"
|
2024-02-01 10:21:14 +08:00
|
|
|
"internal/runtime/atomic"
|
2024-07-23 11:18:08 -04:00
|
|
|
"internal/runtime/math"
|
2014-07-16 14:16:19 -07:00
|
|
|
"unsafe"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const (
|
2019-04-22 13:37:08 -07:00
|
|
|
// Maximum number of key/elem pairs a bucket can hold.
|
2023-01-13 16:12:47 -05:00
|
|
|
bucketCntBits = abi.MapBucketCountBits
|
2014-07-16 14:16:19 -07:00
|
|
|
|
2023-01-13 16:12:47 -05:00
|
|
|
// Maximum average load of a bucket that triggers growth is bucketCnt*13/16 (about 80% full)
|
|
|
|
|
// Because of minimum alignment rules, bucketCnt is known to be at least 8.
|
2020-04-01 07:15:49 +00:00
|
|
|
// Represent as loadFactorNum/loadFactorDen, to allow integer math.
|
2017-07-16 17:37:27 -10:00
|
|
|
loadFactorDen = 2
|
2024-01-31 03:51:34 +00:00
|
|
|
loadFactorNum = loadFactorDen * abi.MapBucketCount * 13 / 16
|
2014-07-16 14:16:19 -07:00
|
|
|
|
|
|
|
|
// data offset should be the size of the bmap struct, but needs to be
|
2016-03-01 23:21:55 +00:00
|
|
|
// aligned correctly. For amd64p32 this means 64-bit alignment
|
2014-07-16 14:16:19 -07:00
|
|
|
// even though pointers are 32 bit.
|
|
|
|
|
dataOffset = unsafe.Offsetof(struct {
|
|
|
|
|
b bmap
|
|
|
|
|
v int64
|
|
|
|
|
}{}.v)
|
|
|
|
|
|
2016-03-01 23:21:55 +00:00
|
|
|
// Possible tophash values. We reserve a few possibilities for special marks.
|
2014-07-16 14:16:19 -07:00
|
|
|
// Each bucket (including its overflow buckets, if any) will have either all or none of its
|
|
|
|
|
// entries in the evacuated* states (except during the evacuate() method, which only happens
|
|
|
|
|
// during map writes and thus no one else can observe the map during that time).
|
2018-10-15 15:14:48 -07:00
|
|
|
emptyRest = 0 // this cell is empty, and there are no more non-empty cells at higher indexes or overflows.
|
|
|
|
|
emptyOne = 1 // this cell is empty
|
2019-04-22 13:37:08 -07:00
|
|
|
evacuatedX = 2 // key/elem is valid. Entry has been evacuated to first half of larger table.
|
2014-07-16 14:16:19 -07:00
|
|
|
evacuatedY = 3 // same as above, but evacuated to second half of larger table.
|
2018-10-15 15:14:48 -07:00
|
|
|
evacuatedEmpty = 4 // cell is empty, bucket is evacuated.
|
|
|
|
|
minTopHash = 5 // minimum tophash for a normal filled cell.
|
2014-07-16 14:16:19 -07:00
|
|
|
|
|
|
|
|
// flags
|
runtime: limit the number of map overflow buckets
Consider repeatedly adding many items to a map
and then deleting them all, as in #16070. The map
itself doesn't need to grow above the high water
mark of number of items. However, due to random
collisions, the map can accumulate overflow
buckets.
Prior to this CL, those overflow buckets were
never removed, which led to a slow memory leak.
The problem with removing overflow buckets is
iterators. The obvious approach is to repack
keys and values and eliminate unused overflow
buckets. However, keys, values, and overflow
buckets cannot be manipulated without disrupting
iterators.
This CL takes a different approach, which is to
reuse the existing map growth mechanism,
which is well established, well tested, and
safe in the presence of iterators.
When a map has accumulated enough overflow buckets
we trigger map growth, but grow into a map of the
same size as before. The old overflow buckets will
be left behind for garbage collection.
For the code in #16070, instead of climbing
(very slowly) forever, memory usage now cycles
between 264mb and 483mb every 15 minutes or so.
To avoid increasing the size of maps,
the overflow bucket counter is only 16 bits.
For large maps, the counter is incremented
stochastically.
Fixes #16070
Change-Id: If551d77613ec6836907efca58bda3deee304297e
Reviewed-on: https://go-review.googlesource.com/25049
Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2016-07-19 11:47:53 -07:00
|
|
|
iterator = 1 // there may be an iterator using buckets
|
|
|
|
|
oldIterator = 2 // there may be an iterator using oldbuckets
|
|
|
|
|
hashWriting = 4 // a goroutine is writing to the map
|
|
|
|
|
sameSizeGrow = 8 // the current map growth is to a new map of the same size
|
2014-07-16 14:16:19 -07:00
|
|
|
|
|
|
|
|
// sentinel bucket ID for iterator checks
|
2021-06-16 23:05:44 +00:00
|
|
|
noCheck = 1<<(8*goarch.PtrSize) - 1
|
2014-07-16 14:16:19 -07:00
|
|
|
)
|
|
|
|
|
|
2018-10-15 15:14:48 -07:00
|
|
|
// isEmpty reports whether the given tophash array entry represents an empty bucket entry.
|
|
|
|
|
func isEmpty(x uint8) bool {
|
|
|
|
|
return x <= emptyOne
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-16 14:16:19 -07:00
|
|
|
// A header for a Go map.
|
|
|
|
|
type hmap struct {
|
2021-04-07 15:24:46 +00:00
|
|
|
// Note: the format of the hmap is also encoded in cmd/compile/internal/reflectdata/reflect.go.
|
2018-02-17 18:12:22 +01:00
|
|
|
// Make sure this stays in sync with the compiler's definition.
|
runtime: limit the number of map overflow buckets
Consider repeatedly adding many items to a map
and then deleting them all, as in #16070. The map
itself doesn't need to grow above the high water
mark of number of items. However, due to random
collisions, the map can accumulate overflow
buckets.
Prior to this CL, those overflow buckets were
never removed, which led to a slow memory leak.
The problem with removing overflow buckets is
iterators. The obvious approach is to repack
keys and values and eliminate unused overflow
buckets. However, keys, values, and overflow
buckets cannot be manipulated without disrupting
iterators.
This CL takes a different approach, which is to
reuse the existing map growth mechanism,
which is well established, well tested, and
safe in the presence of iterators.
When a map has accumulated enough overflow buckets
we trigger map growth, but grow into a map of the
same size as before. The old overflow buckets will
be left behind for garbage collection.
For the code in #16070, instead of climbing
(very slowly) forever, memory usage now cycles
between 264mb and 483mb every 15 minutes or so.
To avoid increasing the size of maps,
the overflow bucket counter is only 16 bits.
For large maps, the counter is incremented
stochastically.
Fixes #16070
Change-Id: If551d77613ec6836907efca58bda3deee304297e
Reviewed-on: https://go-review.googlesource.com/25049
Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2016-07-19 11:47:53 -07:00
|
|
|
count int // # live cells == size of map. Must be first (used by len() builtin)
|
|
|
|
|
flags uint8
|
|
|
|
|
B uint8 // log_2 of # of buckets (can hold up to loadFactor * 2^B items)
|
|
|
|
|
noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details
|
|
|
|
|
hash0 uint32 // hash seed
|
2014-07-16 14:16:19 -07:00
|
|
|
|
|
|
|
|
buckets unsafe.Pointer // array of 2^B Buckets. may be nil if count==0.
|
|
|
|
|
oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing
|
|
|
|
|
nevacuate uintptr // progress counter for evacuation (buckets less than this have been evacuated)
|
2015-01-26 21:04:41 +03:00
|
|
|
|
2017-04-12 14:42:24 -07:00
|
|
|
extra *mapextra // optional fields
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// mapextra holds fields that are not present on all maps.
|
|
|
|
|
type mapextra struct {
|
2019-04-22 13:37:08 -07:00
|
|
|
// If both key and elem do not contain pointers and are inline, then we mark bucket
|
2015-01-26 21:04:41 +03:00
|
|
|
// type as containing no pointers. This avoids scanning such maps.
|
|
|
|
|
// However, bmap.overflow is a pointer. In order to keep overflow buckets
|
2018-07-02 04:32:48 +00:00
|
|
|
// alive, we store pointers to all overflow buckets in hmap.extra.overflow and hmap.extra.oldoverflow.
|
2019-04-22 13:37:08 -07:00
|
|
|
// overflow and oldoverflow are only used if key and elem do not contain pointers.
|
2017-09-10 12:55:16 +02:00
|
|
|
// overflow contains overflow buckets for hmap.buckets.
|
|
|
|
|
// oldoverflow contains overflow buckets for hmap.oldbuckets.
|
2017-04-12 14:42:24 -07:00
|
|
|
// The indirection allows to store a pointer to the slice in hiter.
|
2017-09-10 12:55:16 +02:00
|
|
|
overflow *[]*bmap
|
|
|
|
|
oldoverflow *[]*bmap
|
2017-04-18 15:23:24 -07:00
|
|
|
|
|
|
|
|
// nextOverflow holds a pointer to a free overflow bucket.
|
|
|
|
|
nextOverflow *bmap
|
2014-07-16 14:16:19 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// A bucket for a Go map.
|
|
|
|
|
type bmap struct {
|
2016-09-26 13:10:41 -04:00
|
|
|
// tophash generally contains the top byte of the hash value
|
|
|
|
|
// for each key in this bucket. If tophash[0] < minTopHash,
|
|
|
|
|
// tophash[0] is a bucket evacuation state instead.
|
2024-01-31 03:51:34 +00:00
|
|
|
tophash [abi.MapBucketCount]uint8
|
2019-04-22 13:37:08 -07:00
|
|
|
// Followed by bucketCnt keys and then bucketCnt elems.
|
|
|
|
|
// NOTE: packing all the keys together and then all the elems together makes the
|
|
|
|
|
// code a bit more complicated than alternating key/elem/key/elem/... but it allows
|
2014-07-16 14:16:19 -07:00
|
|
|
// us to eliminate padding which would be needed for, e.g., map[int64]int8.
|
2014-12-19 20:44:18 -08:00
|
|
|
// Followed by an overflow pointer.
|
2014-07-16 14:16:19 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// A hash iteration structure.
|
2021-05-20 09:57:04 -07:00
|
|
|
// If you modify hiter, also change cmd/compile/internal/reflectdata/reflect.go
|
|
|
|
|
// and reflect/value.go to match the layout of this structure.
|
2014-07-16 14:16:19 -07:00
|
|
|
type hiter struct {
|
2021-04-07 15:24:46 +00:00
|
|
|
key unsafe.Pointer // Must be in first position. Write nil to indicate iteration end (see cmd/compile/internal/walk/range.go).
|
|
|
|
|
elem unsafe.Pointer // Must be in second position (see cmd/compile/internal/walk/range.go).
|
2014-07-16 14:16:19 -07:00
|
|
|
t *maptype
|
|
|
|
|
h *hmap
|
|
|
|
|
buckets unsafe.Pointer // bucket ptr at hash_iter initialization time
|
|
|
|
|
bptr *bmap // current bucket
|
2017-09-10 12:55:16 +02:00
|
|
|
overflow *[]*bmap // keeps overflow buckets of hmap.buckets alive
|
|
|
|
|
oldoverflow *[]*bmap // keeps overflow buckets of hmap.oldbuckets alive
|
2014-09-08 17:42:21 -07:00
|
|
|
startBucket uintptr // bucket iteration started at
|
2014-07-16 14:16:19 -07:00
|
|
|
offset uint8 // intra-bucket offset to start from during iteration (should be big enough to hold bucketCnt-1)
|
2014-09-08 17:42:21 -07:00
|
|
|
wrapped bool // already wrapped around from end of bucket array to beginning
|
2014-07-16 14:16:19 -07:00
|
|
|
B uint8
|
2014-09-09 14:22:58 -07:00
|
|
|
i uint8
|
2014-07-16 14:16:19 -07:00
|
|
|
bucket uintptr
|
|
|
|
|
checkBucket uintptr
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-20 11:47:50 -07:00
|
|
|
// bucketShift returns 1<<b, optimized for code generation.
|
|
|
|
|
func bucketShift(b uint8) uintptr {
|
2019-03-31 15:23:07 +01:00
|
|
|
// Masking the shift amount allows overflow checks to be elided.
|
2021-06-16 23:05:44 +00:00
|
|
|
return uintptr(1) << (b & (goarch.PtrSize*8 - 1))
|
2017-08-20 11:47:50 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// bucketMask returns 1<<b - 1, optimized for code generation.
|
|
|
|
|
func bucketMask(b uint8) uintptr {
|
|
|
|
|
return bucketShift(b) - 1
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-07 09:55:05 -07:00
|
|
|
// tophash calculates the tophash value for hash.
|
|
|
|
|
func tophash(hash uintptr) uint8 {
|
2021-06-16 23:05:44 +00:00
|
|
|
top := uint8(hash >> (goarch.PtrSize*8 - 8))
|
2017-06-07 09:55:05 -07:00
|
|
|
if top < minTopHash {
|
|
|
|
|
top += minTopHash
|
|
|
|
|
}
|
|
|
|
|
return top
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-16 14:16:19 -07:00
|
|
|
func evacuated(b *bmap) bool {
|
|
|
|
|
h := b.tophash[0]
|
2018-10-15 15:14:48 -07:00
|
|
|
return h > emptyOne && h < minTopHash
|
2014-07-16 14:16:19 -07:00
|
|
|
}
|
|
|
|
|
|
2014-12-19 20:44:18 -08:00
|
|
|
func (b *bmap) overflow(t *maptype) *bmap {
|
2023-04-25 19:14:05 -04:00
|
|
|
return *(**bmap)(add(unsafe.Pointer(b), uintptr(t.BucketSize)-goarch.PtrSize))
|
2014-12-19 20:44:18 -08:00
|
|
|
}
|
2015-01-26 21:04:41 +03:00
|
|
|
|
2017-04-16 06:27:24 -07:00
|
|
|
func (b *bmap) setoverflow(t *maptype, ovf *bmap) {
|
2023-04-25 19:14:05 -04:00
|
|
|
*(**bmap)(add(unsafe.Pointer(b), uintptr(t.BucketSize)-goarch.PtrSize)) = ovf
|
2017-04-16 06:27:24 -07:00
|
|
|
}
|
|
|
|
|
|
2017-08-21 08:45:57 -07:00
|
|
|
func (b *bmap) keys() unsafe.Pointer {
|
|
|
|
|
return add(unsafe.Pointer(b), dataOffset)
|
|
|
|
|
}
|
|
|
|
|
|
runtime: limit the number of map overflow buckets
Consider repeatedly adding many items to a map
and then deleting them all, as in #16070. The map
itself doesn't need to grow above the high water
mark of number of items. However, due to random
collisions, the map can accumulate overflow
buckets.
Prior to this CL, those overflow buckets were
never removed, which led to a slow memory leak.
The problem with removing overflow buckets is
iterators. The obvious approach is to repack
keys and values and eliminate unused overflow
buckets. However, keys, values, and overflow
buckets cannot be manipulated without disrupting
iterators.
This CL takes a different approach, which is to
reuse the existing map growth mechanism,
which is well established, well tested, and
safe in the presence of iterators.
When a map has accumulated enough overflow buckets
we trigger map growth, but grow into a map of the
same size as before. The old overflow buckets will
be left behind for garbage collection.
For the code in #16070, instead of climbing
(very slowly) forever, memory usage now cycles
between 264mb and 483mb every 15 minutes or so.
To avoid increasing the size of maps,
the overflow bucket counter is only 16 bits.
For large maps, the counter is incremented
stochastically.
Fixes #16070
Change-Id: If551d77613ec6836907efca58bda3deee304297e
Reviewed-on: https://go-review.googlesource.com/25049
Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2016-07-19 11:47:53 -07:00
|
|
|
// incrnoverflow increments h.noverflow.
|
|
|
|
|
// noverflow counts the number of overflow buckets.
|
|
|
|
|
// This is used to trigger same-size map growth.
|
|
|
|
|
// See also tooManyOverflowBuckets.
|
|
|
|
|
// To keep hmap small, noverflow is a uint16.
|
|
|
|
|
// When there are few buckets, noverflow is an exact count.
|
|
|
|
|
// When there are many buckets, noverflow is an approximate count.
|
|
|
|
|
func (h *hmap) incrnoverflow() {
|
|
|
|
|
// We trigger same-size map growth if there are
|
|
|
|
|
// as many overflow buckets as buckets.
|
|
|
|
|
// We need to be able to count to 1<<h.B.
|
|
|
|
|
if h.B < 16 {
|
|
|
|
|
h.noverflow++
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
// Increment with probability 1/(1<<(h.B-15)).
|
|
|
|
|
// When we reach 1<<15 - 1, we will have approximately
|
|
|
|
|
// as many overflow buckets as buckets.
|
|
|
|
|
mask := uint32(1)<<(h.B-15) - 1
|
|
|
|
|
// Example: if h.B == 18, then mask == 7,
|
math/rand, math/rand/v2: use ChaCha8 for global rand
Move ChaCha8 code into internal/chacha8rand and use it to implement
runtime.rand, which is used for the unseeded global source for
both math/rand and math/rand/v2. This also affects the calculation of
the start point for iteration over very very large maps (when the
32-bit fastrand is not big enough).
The benefit is that misuse of the global random number generators
in math/rand and math/rand/v2 in contexts where non-predictable
randomness is important for security reasons is no longer a
security problem, removing a common mistake among programmers
who are unaware of the different kinds of randomness.
The cost is an extra 304 bytes per thread stored in the m struct
plus 2-3ns more per random uint64 due to the more sophisticated
algorithm. Using PCG looks like it would cost about the same,
although I haven't benchmarked that.
Before this, the math/rand and math/rand/v2 global generator
was wyrand (https://github.com/wangyi-fudan/wyhash).
For math/rand, using wyrand instead of the Mitchell/Reeds/Thompson
ALFG was justifiable, since the latter was not any better.
But for math/rand/v2, the global generator really should be
at least as good as one of the well-studied, specific algorithms
provided directly by the package, and it's not.
(Wyrand is still reasonable for scheduling and cache decisions.)
Good randomness does have a cost: about twice wyrand.
Also rationalize the various runtime rand references.
goos: linux
goarch: amd64
pkg: math/rand/v2
cpu: AMD Ryzen 9 7950X 16-Core Processor
│ bbb48afeb7.amd64 │ 5cf807d1ea.amd64 │
│ sec/op │ sec/op vs base │
ChaCha8-32 1.862n ± 2% 1.861n ± 2% ~ (p=0.825 n=20)
PCG_DXSM-32 1.471n ± 1% 1.460n ± 2% ~ (p=0.153 n=20)
SourceUint64-32 1.636n ± 2% 1.582n ± 1% -3.30% (p=0.000 n=20)
GlobalInt64-32 2.087n ± 1% 3.663n ± 1% +75.54% (p=0.000 n=20)
GlobalInt64Parallel-32 0.1042n ± 1% 0.2026n ± 1% +94.48% (p=0.000 n=20)
GlobalUint64-32 2.263n ± 2% 3.724n ± 1% +64.57% (p=0.000 n=20)
GlobalUint64Parallel-32 0.1019n ± 1% 0.1973n ± 1% +93.67% (p=0.000 n=20)
Int64-32 1.771n ± 1% 1.774n ± 1% ~ (p=0.449 n=20)
Uint64-32 1.863n ± 2% 1.866n ± 1% ~ (p=0.364 n=20)
GlobalIntN1000-32 3.134n ± 3% 4.730n ± 2% +50.95% (p=0.000 n=20)
IntN1000-32 2.489n ± 1% 2.489n ± 1% ~ (p=0.683 n=20)
Int64N1000-32 2.521n ± 1% 2.516n ± 1% ~ (p=0.394 n=20)
Int64N1e8-32 2.479n ± 1% 2.478n ± 2% ~ (p=0.743 n=20)
Int64N1e9-32 2.530n ± 2% 2.514n ± 2% ~ (p=0.193 n=20)
Int64N2e9-32 2.501n ± 1% 2.494n ± 1% ~ (p=0.616 n=20)
Int64N1e18-32 3.227n ± 1% 3.205n ± 1% ~ (p=0.101 n=20)
Int64N2e18-32 3.647n ± 1% 3.599n ± 1% ~ (p=0.019 n=20)
Int64N4e18-32 5.135n ± 1% 5.069n ± 2% ~ (p=0.034 n=20)
Int32N1000-32 2.657n ± 1% 2.637n ± 1% ~ (p=0.180 n=20)
Int32N1e8-32 2.636n ± 1% 2.636n ± 1% ~ (p=0.763 n=20)
Int32N1e9-32 2.660n ± 2% 2.638n ± 1% ~ (p=0.358 n=20)
Int32N2e9-32 2.662n ± 2% 2.618n ± 2% ~ (p=0.064 n=20)
Float32-32 2.272n ± 2% 2.239n ± 2% ~ (p=0.194 n=20)
Float64-32 2.272n ± 1% 2.286n ± 2% ~ (p=0.763 n=20)
ExpFloat64-32 3.762n ± 1% 3.744n ± 1% ~ (p=0.171 n=20)
NormFloat64-32 3.706n ± 1% 3.655n ± 2% ~ (p=0.066 n=20)
Perm3-32 32.93n ± 3% 34.62n ± 1% +5.13% (p=0.000 n=20)
Perm30-32 202.9n ± 1% 204.0n ± 1% ~ (p=0.482 n=20)
Perm30ViaShuffle-32 115.0n ± 1% 114.9n ± 1% ~ (p=0.358 n=20)
ShuffleOverhead-32 112.8n ± 1% 112.7n ± 1% ~ (p=0.692 n=20)
Concurrent-32 2.107n ± 0% 3.725n ± 1% +76.75% (p=0.000 n=20)
goos: darwin
goarch: arm64
pkg: math/rand/v2
│ bbb48afeb7.arm64 │ 5cf807d1ea.arm64 │
│ sec/op │ sec/op vs base │
ChaCha8-8 2.480n ± 0% 2.429n ± 0% -2.04% (p=0.000 n=20)
PCG_DXSM-8 2.531n ± 0% 2.530n ± 0% ~ (p=0.877 n=20)
SourceUint64-8 2.534n ± 0% 2.533n ± 0% ~ (p=0.732 n=20)
GlobalInt64-8 2.172n ± 1% 4.794n ± 0% +120.67% (p=0.000 n=20)
GlobalInt64Parallel-8 0.4320n ± 0% 0.9605n ± 0% +122.32% (p=0.000 n=20)
GlobalUint64-8 2.182n ± 0% 4.770n ± 0% +118.58% (p=0.000 n=20)
GlobalUint64Parallel-8 0.4307n ± 0% 0.9583n ± 0% +122.51% (p=0.000 n=20)
Int64-8 4.107n ± 0% 4.104n ± 0% ~ (p=0.416 n=20)
Uint64-8 4.080n ± 0% 4.080n ± 0% ~ (p=0.052 n=20)
GlobalIntN1000-8 2.814n ± 2% 5.643n ± 0% +100.50% (p=0.000 n=20)
IntN1000-8 4.141n ± 0% 4.139n ± 0% ~ (p=0.140 n=20)
Int64N1000-8 4.140n ± 0% 4.140n ± 0% ~ (p=0.313 n=20)
Int64N1e8-8 4.140n ± 0% 4.139n ± 0% ~ (p=0.103 n=20)
Int64N1e9-8 4.139n ± 0% 4.140n ± 0% ~ (p=0.761 n=20)
Int64N2e9-8 4.140n ± 0% 4.140n ± 0% ~ (p=0.636 n=20)
Int64N1e18-8 5.266n ± 0% 5.326n ± 1% +1.14% (p=0.001 n=20)
Int64N2e18-8 6.052n ± 0% 6.167n ± 0% +1.90% (p=0.000 n=20)
Int64N4e18-8 8.826n ± 0% 9.051n ± 0% +2.55% (p=0.000 n=20)
Int32N1000-8 4.127n ± 0% 4.132n ± 0% +0.12% (p=0.000 n=20)
Int32N1e8-8 4.126n ± 0% 4.131n ± 0% +0.12% (p=0.000 n=20)
Int32N1e9-8 4.127n ± 0% 4.132n ± 0% +0.12% (p=0.000 n=20)
Int32N2e9-8 4.132n ± 0% 4.131n ± 0% ~ (p=0.017 n=20)
Float32-8 4.109n ± 0% 4.105n ± 0% ~ (p=0.379 n=20)
Float64-8 4.107n ± 0% 4.106n ± 0% ~ (p=0.867 n=20)
ExpFloat64-8 5.339n ± 0% 5.383n ± 0% +0.82% (p=0.000 n=20)
NormFloat64-8 5.735n ± 0% 5.737n ± 1% ~ (p=0.856 n=20)
Perm3-8 26.65n ± 0% 26.80n ± 1% +0.58% (p=0.000 n=20)
Perm30-8 194.8n ± 1% 197.0n ± 0% +1.18% (p=0.000 n=20)
Perm30ViaShuffle-8 156.6n ± 0% 157.6n ± 1% +0.61% (p=0.000 n=20)
ShuffleOverhead-8 124.9n ± 0% 125.5n ± 0% +0.52% (p=0.000 n=20)
Concurrent-8 2.434n ± 3% 5.066n ± 0% +108.09% (p=0.000 n=20)
goos: linux
goarch: 386
pkg: math/rand/v2
cpu: AMD Ryzen 9 7950X 16-Core Processor
│ bbb48afeb7.386 │ 5cf807d1ea.386 │
│ sec/op │ sec/op vs base │
ChaCha8-32 11.295n ± 1% 4.748n ± 2% -57.96% (p=0.000 n=20)
PCG_DXSM-32 7.693n ± 1% 7.738n ± 2% ~ (p=0.542 n=20)
SourceUint64-32 7.658n ± 2% 7.622n ± 2% ~ (p=0.344 n=20)
GlobalInt64-32 3.473n ± 2% 7.526n ± 2% +116.73% (p=0.000 n=20)
GlobalInt64Parallel-32 0.3198n ± 0% 0.5444n ± 0% +70.22% (p=0.000 n=20)
GlobalUint64-32 3.612n ± 0% 7.575n ± 1% +109.69% (p=0.000 n=20)
GlobalUint64Parallel-32 0.3168n ± 0% 0.5403n ± 0% +70.51% (p=0.000 n=20)
Int64-32 7.673n ± 2% 7.789n ± 1% ~ (p=0.122 n=20)
Uint64-32 7.773n ± 1% 7.827n ± 2% ~ (p=0.920 n=20)
GlobalIntN1000-32 6.268n ± 1% 9.581n ± 1% +52.87% (p=0.000 n=20)
IntN1000-32 10.33n ± 2% 10.45n ± 1% ~ (p=0.233 n=20)
Int64N1000-32 10.98n ± 2% 11.01n ± 1% ~ (p=0.401 n=20)
Int64N1e8-32 11.19n ± 2% 10.97n ± 1% ~ (p=0.033 n=20)
Int64N1e9-32 11.06n ± 1% 11.08n ± 1% ~ (p=0.498 n=20)
Int64N2e9-32 11.10n ± 1% 11.01n ± 2% ~ (p=0.995 n=20)
Int64N1e18-32 15.23n ± 2% 15.04n ± 1% ~ (p=0.973 n=20)
Int64N2e18-32 15.89n ± 1% 15.85n ± 1% ~ (p=0.409 n=20)
Int64N4e18-32 18.96n ± 2% 19.34n ± 2% ~ (p=0.048 n=20)
Int32N1000-32 10.46n ± 2% 10.44n ± 2% ~ (p=0.480 n=20)
Int32N1e8-32 10.46n ± 2% 10.49n ± 2% ~ (p=0.951 n=20)
Int32N1e9-32 10.28n ± 2% 10.26n ± 1% ~ (p=0.431 n=20)
Int32N2e9-32 10.50n ± 2% 10.44n ± 2% ~ (p=0.249 n=20)
Float32-32 13.80n ± 2% 13.80n ± 2% ~ (p=0.751 n=20)
Float64-32 23.55n ± 2% 23.87n ± 0% ~ (p=0.408 n=20)
ExpFloat64-32 15.36n ± 1% 15.29n ± 2% ~ (p=0.316 n=20)
NormFloat64-32 13.57n ± 1% 13.79n ± 1% +1.66% (p=0.005 n=20)
Perm3-32 45.70n ± 2% 46.99n ± 2% +2.81% (p=0.001 n=20)
Perm30-32 399.0n ± 1% 403.8n ± 1% +1.19% (p=0.006 n=20)
Perm30ViaShuffle-32 349.0n ± 1% 350.4n ± 1% ~ (p=0.909 n=20)
ShuffleOverhead-32 322.3n ± 1% 323.8n ± 1% ~ (p=0.410 n=20)
Concurrent-32 3.331n ± 1% 7.312n ± 1% +119.50% (p=0.000 n=20)
For #61716.
Change-Id: Ibdddeed85c34d9ae397289dc899e04d4845f9ed2
Reviewed-on: https://go-review.googlesource.com/c/go/+/516860
Reviewed-by: Michael Pratt <mpratt@google.com>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
2023-08-06 13:26:28 +10:00
|
|
|
// and rand() & 7 == 0 with probability 1/8.
|
|
|
|
|
if uint32(rand())&mask == 0 {
|
runtime: limit the number of map overflow buckets
Consider repeatedly adding many items to a map
and then deleting them all, as in #16070. The map
itself doesn't need to grow above the high water
mark of number of items. However, due to random
collisions, the map can accumulate overflow
buckets.
Prior to this CL, those overflow buckets were
never removed, which led to a slow memory leak.
The problem with removing overflow buckets is
iterators. The obvious approach is to repack
keys and values and eliminate unused overflow
buckets. However, keys, values, and overflow
buckets cannot be manipulated without disrupting
iterators.
This CL takes a different approach, which is to
reuse the existing map growth mechanism,
which is well established, well tested, and
safe in the presence of iterators.
When a map has accumulated enough overflow buckets
we trigger map growth, but grow into a map of the
same size as before. The old overflow buckets will
be left behind for garbage collection.
For the code in #16070, instead of climbing
(very slowly) forever, memory usage now cycles
between 264mb and 483mb every 15 minutes or so.
To avoid increasing the size of maps,
the overflow bucket counter is only 16 bits.
For large maps, the counter is incremented
stochastically.
Fixes #16070
Change-Id: If551d77613ec6836907efca58bda3deee304297e
Reviewed-on: https://go-review.googlesource.com/25049
Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2016-07-19 11:47:53 -07:00
|
|
|
h.noverflow++
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-13 05:25:20 -07:00
|
|
|
func (h *hmap) newoverflow(t *maptype, b *bmap) *bmap {
|
2017-04-18 15:23:24 -07:00
|
|
|
var ovf *bmap
|
|
|
|
|
if h.extra != nil && h.extra.nextOverflow != nil {
|
|
|
|
|
// We have preallocated overflow buckets available.
|
|
|
|
|
// See makeBucketArray for more details.
|
|
|
|
|
ovf = h.extra.nextOverflow
|
|
|
|
|
if ovf.overflow(t) == nil {
|
|
|
|
|
// We're not at the end of the preallocated overflow buckets. Bump the pointer.
|
2023-04-25 19:14:05 -04:00
|
|
|
h.extra.nextOverflow = (*bmap)(add(unsafe.Pointer(ovf), uintptr(t.BucketSize)))
|
2017-04-18 15:23:24 -07:00
|
|
|
} else {
|
|
|
|
|
// This is the last preallocated overflow bucket.
|
|
|
|
|
// Reset the overflow pointer on this bucket,
|
|
|
|
|
// which was set to a non-nil sentinel value.
|
|
|
|
|
ovf.setoverflow(t, nil)
|
|
|
|
|
h.extra.nextOverflow = nil
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2023-04-25 19:14:05 -04:00
|
|
|
ovf = (*bmap)(newobject(t.Bucket))
|
2017-04-18 15:23:24 -07:00
|
|
|
}
|
runtime: limit the number of map overflow buckets
Consider repeatedly adding many items to a map
and then deleting them all, as in #16070. The map
itself doesn't need to grow above the high water
mark of number of items. However, due to random
collisions, the map can accumulate overflow
buckets.
Prior to this CL, those overflow buckets were
never removed, which led to a slow memory leak.
The problem with removing overflow buckets is
iterators. The obvious approach is to repack
keys and values and eliminate unused overflow
buckets. However, keys, values, and overflow
buckets cannot be manipulated without disrupting
iterators.
This CL takes a different approach, which is to
reuse the existing map growth mechanism,
which is well established, well tested, and
safe in the presence of iterators.
When a map has accumulated enough overflow buckets
we trigger map growth, but grow into a map of the
same size as before. The old overflow buckets will
be left behind for garbage collection.
For the code in #16070, instead of climbing
(very slowly) forever, memory usage now cycles
between 264mb and 483mb every 15 minutes or so.
To avoid increasing the size of maps,
the overflow bucket counter is only 16 bits.
For large maps, the counter is incremented
stochastically.
Fixes #16070
Change-Id: If551d77613ec6836907efca58bda3deee304297e
Reviewed-on: https://go-review.googlesource.com/25049
Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2016-07-19 11:47:53 -07:00
|
|
|
h.incrnoverflow()
|
2024-02-27 21:51:31 +00:00
|
|
|
if !t.Bucket.Pointers() {
|
2015-01-26 21:04:41 +03:00
|
|
|
h.createOverflow()
|
2017-09-10 12:55:16 +02:00
|
|
|
*h.extra.overflow = append(*h.extra.overflow, ovf)
|
2015-01-26 21:04:41 +03:00
|
|
|
}
|
2017-04-16 06:27:24 -07:00
|
|
|
b.setoverflow(t, ovf)
|
2017-04-13 05:25:20 -07:00
|
|
|
return ovf
|
2014-12-19 20:44:18 -08:00
|
|
|
}
|
|
|
|
|
|
2015-01-26 21:04:41 +03:00
|
|
|
func (h *hmap) createOverflow() {
|
2017-04-12 14:42:24 -07:00
|
|
|
if h.extra == nil {
|
|
|
|
|
h.extra = new(mapextra)
|
2015-01-26 21:04:41 +03:00
|
|
|
}
|
2017-09-10 12:55:16 +02:00
|
|
|
if h.extra.overflow == nil {
|
|
|
|
|
h.extra.overflow = new([]*bmap)
|
2015-01-26 21:04:41 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-23 13:48:05 +02:00
|
|
|
func makemap64(t *maptype, hint int64, h *hmap) *hmap {
|
2017-08-14 10:16:21 +02:00
|
|
|
if int64(int(hint)) != hint {
|
|
|
|
|
hint = 0
|
|
|
|
|
}
|
|
|
|
|
return makemap(t, int(hint), h)
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-25 22:42:54 -07:00
|
|
|
// makemap_small implements Go map creation for make(map[k]v) and
|
2017-09-02 18:46:59 +02:00
|
|
|
// make(map[k]v, hint) when hint is known to be at most bucketCnt
|
|
|
|
|
// at compile time and the map needs to be allocated on the heap.
|
2024-05-21 23:24:47 -04:00
|
|
|
//
|
|
|
|
|
// makemap_small should be an internal detail,
|
|
|
|
|
// but widely used packages access it using linkname.
|
|
|
|
|
// Notable members of the hall of shame include:
|
|
|
|
|
// - github.com/bytedance/sonic
|
|
|
|
|
//
|
|
|
|
|
// Do not remove or change the type signature.
|
|
|
|
|
// See go.dev/issue/67401.
|
|
|
|
|
//
|
|
|
|
|
//go:linkname makemap_small
|
2017-09-02 18:46:59 +02:00
|
|
|
func makemap_small() *hmap {
|
|
|
|
|
h := new(hmap)
|
math/rand, math/rand/v2: use ChaCha8 for global rand
Move ChaCha8 code into internal/chacha8rand and use it to implement
runtime.rand, which is used for the unseeded global source for
both math/rand and math/rand/v2. This also affects the calculation of
the start point for iteration over very very large maps (when the
32-bit fastrand is not big enough).
The benefit is that misuse of the global random number generators
in math/rand and math/rand/v2 in contexts where non-predictable
randomness is important for security reasons is no longer a
security problem, removing a common mistake among programmers
who are unaware of the different kinds of randomness.
The cost is an extra 304 bytes per thread stored in the m struct
plus 2-3ns more per random uint64 due to the more sophisticated
algorithm. Using PCG looks like it would cost about the same,
although I haven't benchmarked that.
Before this, the math/rand and math/rand/v2 global generator
was wyrand (https://github.com/wangyi-fudan/wyhash).
For math/rand, using wyrand instead of the Mitchell/Reeds/Thompson
ALFG was justifiable, since the latter was not any better.
But for math/rand/v2, the global generator really should be
at least as good as one of the well-studied, specific algorithms
provided directly by the package, and it's not.
(Wyrand is still reasonable for scheduling and cache decisions.)
Good randomness does have a cost: about twice wyrand.
Also rationalize the various runtime rand references.
goos: linux
goarch: amd64
pkg: math/rand/v2
cpu: AMD Ryzen 9 7950X 16-Core Processor
│ bbb48afeb7.amd64 │ 5cf807d1ea.amd64 │
│ sec/op │ sec/op vs base │
ChaCha8-32 1.862n ± 2% 1.861n ± 2% ~ (p=0.825 n=20)
PCG_DXSM-32 1.471n ± 1% 1.460n ± 2% ~ (p=0.153 n=20)
SourceUint64-32 1.636n ± 2% 1.582n ± 1% -3.30% (p=0.000 n=20)
GlobalInt64-32 2.087n ± 1% 3.663n ± 1% +75.54% (p=0.000 n=20)
GlobalInt64Parallel-32 0.1042n ± 1% 0.2026n ± 1% +94.48% (p=0.000 n=20)
GlobalUint64-32 2.263n ± 2% 3.724n ± 1% +64.57% (p=0.000 n=20)
GlobalUint64Parallel-32 0.1019n ± 1% 0.1973n ± 1% +93.67% (p=0.000 n=20)
Int64-32 1.771n ± 1% 1.774n ± 1% ~ (p=0.449 n=20)
Uint64-32 1.863n ± 2% 1.866n ± 1% ~ (p=0.364 n=20)
GlobalIntN1000-32 3.134n ± 3% 4.730n ± 2% +50.95% (p=0.000 n=20)
IntN1000-32 2.489n ± 1% 2.489n ± 1% ~ (p=0.683 n=20)
Int64N1000-32 2.521n ± 1% 2.516n ± 1% ~ (p=0.394 n=20)
Int64N1e8-32 2.479n ± 1% 2.478n ± 2% ~ (p=0.743 n=20)
Int64N1e9-32 2.530n ± 2% 2.514n ± 2% ~ (p=0.193 n=20)
Int64N2e9-32 2.501n ± 1% 2.494n ± 1% ~ (p=0.616 n=20)
Int64N1e18-32 3.227n ± 1% 3.205n ± 1% ~ (p=0.101 n=20)
Int64N2e18-32 3.647n ± 1% 3.599n ± 1% ~ (p=0.019 n=20)
Int64N4e18-32 5.135n ± 1% 5.069n ± 2% ~ (p=0.034 n=20)
Int32N1000-32 2.657n ± 1% 2.637n ± 1% ~ (p=0.180 n=20)
Int32N1e8-32 2.636n ± 1% 2.636n ± 1% ~ (p=0.763 n=20)
Int32N1e9-32 2.660n ± 2% 2.638n ± 1% ~ (p=0.358 n=20)
Int32N2e9-32 2.662n ± 2% 2.618n ± 2% ~ (p=0.064 n=20)
Float32-32 2.272n ± 2% 2.239n ± 2% ~ (p=0.194 n=20)
Float64-32 2.272n ± 1% 2.286n ± 2% ~ (p=0.763 n=20)
ExpFloat64-32 3.762n ± 1% 3.744n ± 1% ~ (p=0.171 n=20)
NormFloat64-32 3.706n ± 1% 3.655n ± 2% ~ (p=0.066 n=20)
Perm3-32 32.93n ± 3% 34.62n ± 1% +5.13% (p=0.000 n=20)
Perm30-32 202.9n ± 1% 204.0n ± 1% ~ (p=0.482 n=20)
Perm30ViaShuffle-32 115.0n ± 1% 114.9n ± 1% ~ (p=0.358 n=20)
ShuffleOverhead-32 112.8n ± 1% 112.7n ± 1% ~ (p=0.692 n=20)
Concurrent-32 2.107n ± 0% 3.725n ± 1% +76.75% (p=0.000 n=20)
goos: darwin
goarch: arm64
pkg: math/rand/v2
│ bbb48afeb7.arm64 │ 5cf807d1ea.arm64 │
│ sec/op │ sec/op vs base │
ChaCha8-8 2.480n ± 0% 2.429n ± 0% -2.04% (p=0.000 n=20)
PCG_DXSM-8 2.531n ± 0% 2.530n ± 0% ~ (p=0.877 n=20)
SourceUint64-8 2.534n ± 0% 2.533n ± 0% ~ (p=0.732 n=20)
GlobalInt64-8 2.172n ± 1% 4.794n ± 0% +120.67% (p=0.000 n=20)
GlobalInt64Parallel-8 0.4320n ± 0% 0.9605n ± 0% +122.32% (p=0.000 n=20)
GlobalUint64-8 2.182n ± 0% 4.770n ± 0% +118.58% (p=0.000 n=20)
GlobalUint64Parallel-8 0.4307n ± 0% 0.9583n ± 0% +122.51% (p=0.000 n=20)
Int64-8 4.107n ± 0% 4.104n ± 0% ~ (p=0.416 n=20)
Uint64-8 4.080n ± 0% 4.080n ± 0% ~ (p=0.052 n=20)
GlobalIntN1000-8 2.814n ± 2% 5.643n ± 0% +100.50% (p=0.000 n=20)
IntN1000-8 4.141n ± 0% 4.139n ± 0% ~ (p=0.140 n=20)
Int64N1000-8 4.140n ± 0% 4.140n ± 0% ~ (p=0.313 n=20)
Int64N1e8-8 4.140n ± 0% 4.139n ± 0% ~ (p=0.103 n=20)
Int64N1e9-8 4.139n ± 0% 4.140n ± 0% ~ (p=0.761 n=20)
Int64N2e9-8 4.140n ± 0% 4.140n ± 0% ~ (p=0.636 n=20)
Int64N1e18-8 5.266n ± 0% 5.326n ± 1% +1.14% (p=0.001 n=20)
Int64N2e18-8 6.052n ± 0% 6.167n ± 0% +1.90% (p=0.000 n=20)
Int64N4e18-8 8.826n ± 0% 9.051n ± 0% +2.55% (p=0.000 n=20)
Int32N1000-8 4.127n ± 0% 4.132n ± 0% +0.12% (p=0.000 n=20)
Int32N1e8-8 4.126n ± 0% 4.131n ± 0% +0.12% (p=0.000 n=20)
Int32N1e9-8 4.127n ± 0% 4.132n ± 0% +0.12% (p=0.000 n=20)
Int32N2e9-8 4.132n ± 0% 4.131n ± 0% ~ (p=0.017 n=20)
Float32-8 4.109n ± 0% 4.105n ± 0% ~ (p=0.379 n=20)
Float64-8 4.107n ± 0% 4.106n ± 0% ~ (p=0.867 n=20)
ExpFloat64-8 5.339n ± 0% 5.383n ± 0% +0.82% (p=0.000 n=20)
NormFloat64-8 5.735n ± 0% 5.737n ± 1% ~ (p=0.856 n=20)
Perm3-8 26.65n ± 0% 26.80n ± 1% +0.58% (p=0.000 n=20)
Perm30-8 194.8n ± 1% 197.0n ± 0% +1.18% (p=0.000 n=20)
Perm30ViaShuffle-8 156.6n ± 0% 157.6n ± 1% +0.61% (p=0.000 n=20)
ShuffleOverhead-8 124.9n ± 0% 125.5n ± 0% +0.52% (p=0.000 n=20)
Concurrent-8 2.434n ± 3% 5.066n ± 0% +108.09% (p=0.000 n=20)
goos: linux
goarch: 386
pkg: math/rand/v2
cpu: AMD Ryzen 9 7950X 16-Core Processor
│ bbb48afeb7.386 │ 5cf807d1ea.386 │
│ sec/op │ sec/op vs base │
ChaCha8-32 11.295n ± 1% 4.748n ± 2% -57.96% (p=0.000 n=20)
PCG_DXSM-32 7.693n ± 1% 7.738n ± 2% ~ (p=0.542 n=20)
SourceUint64-32 7.658n ± 2% 7.622n ± 2% ~ (p=0.344 n=20)
GlobalInt64-32 3.473n ± 2% 7.526n ± 2% +116.73% (p=0.000 n=20)
GlobalInt64Parallel-32 0.3198n ± 0% 0.5444n ± 0% +70.22% (p=0.000 n=20)
GlobalUint64-32 3.612n ± 0% 7.575n ± 1% +109.69% (p=0.000 n=20)
GlobalUint64Parallel-32 0.3168n ± 0% 0.5403n ± 0% +70.51% (p=0.000 n=20)
Int64-32 7.673n ± 2% 7.789n ± 1% ~ (p=0.122 n=20)
Uint64-32 7.773n ± 1% 7.827n ± 2% ~ (p=0.920 n=20)
GlobalIntN1000-32 6.268n ± 1% 9.581n ± 1% +52.87% (p=0.000 n=20)
IntN1000-32 10.33n ± 2% 10.45n ± 1% ~ (p=0.233 n=20)
Int64N1000-32 10.98n ± 2% 11.01n ± 1% ~ (p=0.401 n=20)
Int64N1e8-32 11.19n ± 2% 10.97n ± 1% ~ (p=0.033 n=20)
Int64N1e9-32 11.06n ± 1% 11.08n ± 1% ~ (p=0.498 n=20)
Int64N2e9-32 11.10n ± 1% 11.01n ± 2% ~ (p=0.995 n=20)
Int64N1e18-32 15.23n ± 2% 15.04n ± 1% ~ (p=0.973 n=20)
Int64N2e18-32 15.89n ± 1% 15.85n ± 1% ~ (p=0.409 n=20)
Int64N4e18-32 18.96n ± 2% 19.34n ± 2% ~ (p=0.048 n=20)
Int32N1000-32 10.46n ± 2% 10.44n ± 2% ~ (p=0.480 n=20)
Int32N1e8-32 10.46n ± 2% 10.49n ± 2% ~ (p=0.951 n=20)
Int32N1e9-32 10.28n ± 2% 10.26n ± 1% ~ (p=0.431 n=20)
Int32N2e9-32 10.50n ± 2% 10.44n ± 2% ~ (p=0.249 n=20)
Float32-32 13.80n ± 2% 13.80n ± 2% ~ (p=0.751 n=20)
Float64-32 23.55n ± 2% 23.87n ± 0% ~ (p=0.408 n=20)
ExpFloat64-32 15.36n ± 1% 15.29n ± 2% ~ (p=0.316 n=20)
NormFloat64-32 13.57n ± 1% 13.79n ± 1% +1.66% (p=0.005 n=20)
Perm3-32 45.70n ± 2% 46.99n ± 2% +2.81% (p=0.001 n=20)
Perm30-32 399.0n ± 1% 403.8n ± 1% +1.19% (p=0.006 n=20)
Perm30ViaShuffle-32 349.0n ± 1% 350.4n ± 1% ~ (p=0.909 n=20)
ShuffleOverhead-32 322.3n ± 1% 323.8n ± 1% ~ (p=0.410 n=20)
Concurrent-32 3.331n ± 1% 7.312n ± 1% +119.50% (p=0.000 n=20)
For #61716.
Change-Id: Ibdddeed85c34d9ae397289dc899e04d4845f9ed2
Reviewed-on: https://go-review.googlesource.com/c/go/+/516860
Reviewed-by: Michael Pratt <mpratt@google.com>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
2023-08-06 13:26:28 +10:00
|
|
|
h.hash0 = uint32(rand())
|
2017-09-02 18:46:59 +02:00
|
|
|
return h
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// makemap implements Go map creation for make(map[k]v, hint).
|
2015-01-29 19:40:02 +03:00
|
|
|
// If the compiler has determined that the map or the first bucket
|
|
|
|
|
// can be created on the stack, h and/or bucket may be non-nil.
|
|
|
|
|
// If h != nil, the map can be created directly in h.
|
2017-08-16 23:36:58 +02:00
|
|
|
// If h.buckets != nil, bucket pointed to can be used as the first bucket.
|
2024-05-21 23:02:51 -04:00
|
|
|
//
|
|
|
|
|
// makemap should be an internal detail,
|
|
|
|
|
// but widely used packages access it using linkname.
|
|
|
|
|
// Notable members of the hall of shame include:
|
2024-05-22 15:46:02 -04:00
|
|
|
// - github.com/cloudwego/frugal
|
2024-05-21 23:02:51 -04:00
|
|
|
// - github.com/ugorji/go/codec
|
|
|
|
|
//
|
|
|
|
|
// Do not remove or change the type signature.
|
|
|
|
|
// See go.dev/issue/67401.
|
|
|
|
|
//
|
|
|
|
|
//go:linkname makemap
|
2017-08-14 10:16:21 +02:00
|
|
|
func makemap(t *maptype, hint int, h *hmap) *hmap {
|
2023-04-25 19:14:05 -04:00
|
|
|
mem, overflow := math.MulUintptr(uintptr(hint), t.Bucket.Size_)
|
2018-10-22 20:45:04 +02:00
|
|
|
if overflow || mem > maxAlloc {
|
2017-04-15 15:17:29 -07:00
|
|
|
hint = 0
|
2014-07-16 14:16:19 -07:00
|
|
|
}
|
|
|
|
|
|
2017-08-16 23:36:58 +02:00
|
|
|
// initialize Hmap
|
|
|
|
|
if h == nil {
|
2018-02-03 16:29:54 +01:00
|
|
|
h = new(hmap)
|
2017-08-16 23:36:58 +02:00
|
|
|
}
|
math/rand, math/rand/v2: use ChaCha8 for global rand
Move ChaCha8 code into internal/chacha8rand and use it to implement
runtime.rand, which is used for the unseeded global source for
both math/rand and math/rand/v2. This also affects the calculation of
the start point for iteration over very very large maps (when the
32-bit fastrand is not big enough).
The benefit is that misuse of the global random number generators
in math/rand and math/rand/v2 in contexts where non-predictable
randomness is important for security reasons is no longer a
security problem, removing a common mistake among programmers
who are unaware of the different kinds of randomness.
The cost is an extra 304 bytes per thread stored in the m struct
plus 2-3ns more per random uint64 due to the more sophisticated
algorithm. Using PCG looks like it would cost about the same,
although I haven't benchmarked that.
Before this, the math/rand and math/rand/v2 global generator
was wyrand (https://github.com/wangyi-fudan/wyhash).
For math/rand, using wyrand instead of the Mitchell/Reeds/Thompson
ALFG was justifiable, since the latter was not any better.
But for math/rand/v2, the global generator really should be
at least as good as one of the well-studied, specific algorithms
provided directly by the package, and it's not.
(Wyrand is still reasonable for scheduling and cache decisions.)
Good randomness does have a cost: about twice wyrand.
Also rationalize the various runtime rand references.
goos: linux
goarch: amd64
pkg: math/rand/v2
cpu: AMD Ryzen 9 7950X 16-Core Processor
│ bbb48afeb7.amd64 │ 5cf807d1ea.amd64 │
│ sec/op │ sec/op vs base │
ChaCha8-32 1.862n ± 2% 1.861n ± 2% ~ (p=0.825 n=20)
PCG_DXSM-32 1.471n ± 1% 1.460n ± 2% ~ (p=0.153 n=20)
SourceUint64-32 1.636n ± 2% 1.582n ± 1% -3.30% (p=0.000 n=20)
GlobalInt64-32 2.087n ± 1% 3.663n ± 1% +75.54% (p=0.000 n=20)
GlobalInt64Parallel-32 0.1042n ± 1% 0.2026n ± 1% +94.48% (p=0.000 n=20)
GlobalUint64-32 2.263n ± 2% 3.724n ± 1% +64.57% (p=0.000 n=20)
GlobalUint64Parallel-32 0.1019n ± 1% 0.1973n ± 1% +93.67% (p=0.000 n=20)
Int64-32 1.771n ± 1% 1.774n ± 1% ~ (p=0.449 n=20)
Uint64-32 1.863n ± 2% 1.866n ± 1% ~ (p=0.364 n=20)
GlobalIntN1000-32 3.134n ± 3% 4.730n ± 2% +50.95% (p=0.000 n=20)
IntN1000-32 2.489n ± 1% 2.489n ± 1% ~ (p=0.683 n=20)
Int64N1000-32 2.521n ± 1% 2.516n ± 1% ~ (p=0.394 n=20)
Int64N1e8-32 2.479n ± 1% 2.478n ± 2% ~ (p=0.743 n=20)
Int64N1e9-32 2.530n ± 2% 2.514n ± 2% ~ (p=0.193 n=20)
Int64N2e9-32 2.501n ± 1% 2.494n ± 1% ~ (p=0.616 n=20)
Int64N1e18-32 3.227n ± 1% 3.205n ± 1% ~ (p=0.101 n=20)
Int64N2e18-32 3.647n ± 1% 3.599n ± 1% ~ (p=0.019 n=20)
Int64N4e18-32 5.135n ± 1% 5.069n ± 2% ~ (p=0.034 n=20)
Int32N1000-32 2.657n ± 1% 2.637n ± 1% ~ (p=0.180 n=20)
Int32N1e8-32 2.636n ± 1% 2.636n ± 1% ~ (p=0.763 n=20)
Int32N1e9-32 2.660n ± 2% 2.638n ± 1% ~ (p=0.358 n=20)
Int32N2e9-32 2.662n ± 2% 2.618n ± 2% ~ (p=0.064 n=20)
Float32-32 2.272n ± 2% 2.239n ± 2% ~ (p=0.194 n=20)
Float64-32 2.272n ± 1% 2.286n ± 2% ~ (p=0.763 n=20)
ExpFloat64-32 3.762n ± 1% 3.744n ± 1% ~ (p=0.171 n=20)
NormFloat64-32 3.706n ± 1% 3.655n ± 2% ~ (p=0.066 n=20)
Perm3-32 32.93n ± 3% 34.62n ± 1% +5.13% (p=0.000 n=20)
Perm30-32 202.9n ± 1% 204.0n ± 1% ~ (p=0.482 n=20)
Perm30ViaShuffle-32 115.0n ± 1% 114.9n ± 1% ~ (p=0.358 n=20)
ShuffleOverhead-32 112.8n ± 1% 112.7n ± 1% ~ (p=0.692 n=20)
Concurrent-32 2.107n ± 0% 3.725n ± 1% +76.75% (p=0.000 n=20)
goos: darwin
goarch: arm64
pkg: math/rand/v2
│ bbb48afeb7.arm64 │ 5cf807d1ea.arm64 │
│ sec/op │ sec/op vs base │
ChaCha8-8 2.480n ± 0% 2.429n ± 0% -2.04% (p=0.000 n=20)
PCG_DXSM-8 2.531n ± 0% 2.530n ± 0% ~ (p=0.877 n=20)
SourceUint64-8 2.534n ± 0% 2.533n ± 0% ~ (p=0.732 n=20)
GlobalInt64-8 2.172n ± 1% 4.794n ± 0% +120.67% (p=0.000 n=20)
GlobalInt64Parallel-8 0.4320n ± 0% 0.9605n ± 0% +122.32% (p=0.000 n=20)
GlobalUint64-8 2.182n ± 0% 4.770n ± 0% +118.58% (p=0.000 n=20)
GlobalUint64Parallel-8 0.4307n ± 0% 0.9583n ± 0% +122.51% (p=0.000 n=20)
Int64-8 4.107n ± 0% 4.104n ± 0% ~ (p=0.416 n=20)
Uint64-8 4.080n ± 0% 4.080n ± 0% ~ (p=0.052 n=20)
GlobalIntN1000-8 2.814n ± 2% 5.643n ± 0% +100.50% (p=0.000 n=20)
IntN1000-8 4.141n ± 0% 4.139n ± 0% ~ (p=0.140 n=20)
Int64N1000-8 4.140n ± 0% 4.140n ± 0% ~ (p=0.313 n=20)
Int64N1e8-8 4.140n ± 0% 4.139n ± 0% ~ (p=0.103 n=20)
Int64N1e9-8 4.139n ± 0% 4.140n ± 0% ~ (p=0.761 n=20)
Int64N2e9-8 4.140n ± 0% 4.140n ± 0% ~ (p=0.636 n=20)
Int64N1e18-8 5.266n ± 0% 5.326n ± 1% +1.14% (p=0.001 n=20)
Int64N2e18-8 6.052n ± 0% 6.167n ± 0% +1.90% (p=0.000 n=20)
Int64N4e18-8 8.826n ± 0% 9.051n ± 0% +2.55% (p=0.000 n=20)
Int32N1000-8 4.127n ± 0% 4.132n ± 0% +0.12% (p=0.000 n=20)
Int32N1e8-8 4.126n ± 0% 4.131n ± 0% +0.12% (p=0.000 n=20)
Int32N1e9-8 4.127n ± 0% 4.132n ± 0% +0.12% (p=0.000 n=20)
Int32N2e9-8 4.132n ± 0% 4.131n ± 0% ~ (p=0.017 n=20)
Float32-8 4.109n ± 0% 4.105n ± 0% ~ (p=0.379 n=20)
Float64-8 4.107n ± 0% 4.106n ± 0% ~ (p=0.867 n=20)
ExpFloat64-8 5.339n ± 0% 5.383n ± 0% +0.82% (p=0.000 n=20)
NormFloat64-8 5.735n ± 0% 5.737n ± 1% ~ (p=0.856 n=20)
Perm3-8 26.65n ± 0% 26.80n ± 1% +0.58% (p=0.000 n=20)
Perm30-8 194.8n ± 1% 197.0n ± 0% +1.18% (p=0.000 n=20)
Perm30ViaShuffle-8 156.6n ± 0% 157.6n ± 1% +0.61% (p=0.000 n=20)
ShuffleOverhead-8 124.9n ± 0% 125.5n ± 0% +0.52% (p=0.000 n=20)
Concurrent-8 2.434n ± 3% 5.066n ± 0% +108.09% (p=0.000 n=20)
goos: linux
goarch: 386
pkg: math/rand/v2
cpu: AMD Ryzen 9 7950X 16-Core Processor
│ bbb48afeb7.386 │ 5cf807d1ea.386 │
│ sec/op │ sec/op vs base │
ChaCha8-32 11.295n ± 1% 4.748n ± 2% -57.96% (p=0.000 n=20)
PCG_DXSM-32 7.693n ± 1% 7.738n ± 2% ~ (p=0.542 n=20)
SourceUint64-32 7.658n ± 2% 7.622n ± 2% ~ (p=0.344 n=20)
GlobalInt64-32 3.473n ± 2% 7.526n ± 2% +116.73% (p=0.000 n=20)
GlobalInt64Parallel-32 0.3198n ± 0% 0.5444n ± 0% +70.22% (p=0.000 n=20)
GlobalUint64-32 3.612n ± 0% 7.575n ± 1% +109.69% (p=0.000 n=20)
GlobalUint64Parallel-32 0.3168n ± 0% 0.5403n ± 0% +70.51% (p=0.000 n=20)
Int64-32 7.673n ± 2% 7.789n ± 1% ~ (p=0.122 n=20)
Uint64-32 7.773n ± 1% 7.827n ± 2% ~ (p=0.920 n=20)
GlobalIntN1000-32 6.268n ± 1% 9.581n ± 1% +52.87% (p=0.000 n=20)
IntN1000-32 10.33n ± 2% 10.45n ± 1% ~ (p=0.233 n=20)
Int64N1000-32 10.98n ± 2% 11.01n ± 1% ~ (p=0.401 n=20)
Int64N1e8-32 11.19n ± 2% 10.97n ± 1% ~ (p=0.033 n=20)
Int64N1e9-32 11.06n ± 1% 11.08n ± 1% ~ (p=0.498 n=20)
Int64N2e9-32 11.10n ± 1% 11.01n ± 2% ~ (p=0.995 n=20)
Int64N1e18-32 15.23n ± 2% 15.04n ± 1% ~ (p=0.973 n=20)
Int64N2e18-32 15.89n ± 1% 15.85n ± 1% ~ (p=0.409 n=20)
Int64N4e18-32 18.96n ± 2% 19.34n ± 2% ~ (p=0.048 n=20)
Int32N1000-32 10.46n ± 2% 10.44n ± 2% ~ (p=0.480 n=20)
Int32N1e8-32 10.46n ± 2% 10.49n ± 2% ~ (p=0.951 n=20)
Int32N1e9-32 10.28n ± 2% 10.26n ± 1% ~ (p=0.431 n=20)
Int32N2e9-32 10.50n ± 2% 10.44n ± 2% ~ (p=0.249 n=20)
Float32-32 13.80n ± 2% 13.80n ± 2% ~ (p=0.751 n=20)
Float64-32 23.55n ± 2% 23.87n ± 0% ~ (p=0.408 n=20)
ExpFloat64-32 15.36n ± 1% 15.29n ± 2% ~ (p=0.316 n=20)
NormFloat64-32 13.57n ± 1% 13.79n ± 1% +1.66% (p=0.005 n=20)
Perm3-32 45.70n ± 2% 46.99n ± 2% +2.81% (p=0.001 n=20)
Perm30-32 399.0n ± 1% 403.8n ± 1% +1.19% (p=0.006 n=20)
Perm30ViaShuffle-32 349.0n ± 1% 350.4n ± 1% ~ (p=0.909 n=20)
ShuffleOverhead-32 322.3n ± 1% 323.8n ± 1% ~ (p=0.410 n=20)
Concurrent-32 3.331n ± 1% 7.312n ± 1% +119.50% (p=0.000 n=20)
For #61716.
Change-Id: Ibdddeed85c34d9ae397289dc899e04d4845f9ed2
Reviewed-on: https://go-review.googlesource.com/c/go/+/516860
Reviewed-by: Michael Pratt <mpratt@google.com>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
2023-08-06 13:26:28 +10:00
|
|
|
h.hash0 = uint32(rand())
|
2017-08-16 23:36:58 +02:00
|
|
|
|
2018-10-22 20:45:04 +02:00
|
|
|
// Find the size parameter B which will hold the requested # of elements.
|
|
|
|
|
// For hint < 0 overLoadFactor returns false since hint < bucketCnt.
|
2014-07-16 14:16:19 -07:00
|
|
|
B := uint8(0)
|
2017-08-16 23:36:58 +02:00
|
|
|
for overLoadFactor(hint, B) {
|
|
|
|
|
B++
|
2014-07-16 14:16:19 -07:00
|
|
|
}
|
2017-08-16 23:36:58 +02:00
|
|
|
h.B = B
|
2014-07-16 14:16:19 -07:00
|
|
|
|
|
|
|
|
// allocate initial hash table
|
|
|
|
|
// if B == 0, the buckets field is allocated lazily later (in mapassign)
|
|
|
|
|
// If hint is large zeroing this memory could take a while.
|
2017-08-16 23:36:58 +02:00
|
|
|
if h.B != 0 {
|
2017-04-18 15:23:24 -07:00
|
|
|
var nextOverflow *bmap
|
2018-04-27 21:58:59 +02:00
|
|
|
h.buckets, nextOverflow = makeBucketArray(t, h.B, nil)
|
2017-04-18 15:23:24 -07:00
|
|
|
if nextOverflow != nil {
|
2017-08-16 23:36:58 +02:00
|
|
|
h.extra = new(mapextra)
|
|
|
|
|
h.extra.nextOverflow = nextOverflow
|
2017-04-18 15:23:24 -07:00
|
|
|
}
|
2014-07-16 14:16:19 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return h
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-27 21:58:59 +02:00
|
|
|
// makeBucketArray initializes a backing array for map buckets.
|
|
|
|
|
// 1<<b is the minimum number of buckets to allocate.
|
|
|
|
|
// dirtyalloc should either be nil or a bucket array previously
|
|
|
|
|
// allocated by makeBucketArray with the same t and b parameters.
|
|
|
|
|
// If dirtyalloc is nil a new backing array will be alloced and
|
|
|
|
|
// otherwise dirtyalloc will be cleared and reused as backing array.
|
|
|
|
|
func makeBucketArray(t *maptype, b uint8, dirtyalloc unsafe.Pointer) (buckets unsafe.Pointer, nextOverflow *bmap) {
|
|
|
|
|
base := bucketShift(b)
|
|
|
|
|
nbuckets := base
|
|
|
|
|
// For small b, overflow buckets are unlikely.
|
|
|
|
|
// Avoid the overhead of the calculation.
|
|
|
|
|
if b >= 4 {
|
|
|
|
|
// Add on the estimated number of overflow buckets
|
|
|
|
|
// required to insert the median number of elements
|
|
|
|
|
// used with this value of b.
|
|
|
|
|
nbuckets += bucketShift(b - 4)
|
2023-04-25 19:14:05 -04:00
|
|
|
sz := t.Bucket.Size_ * nbuckets
|
2024-02-27 21:51:31 +00:00
|
|
|
up := roundupsize(sz, !t.Bucket.Pointers())
|
2018-04-27 21:58:59 +02:00
|
|
|
if up != sz {
|
2023-04-25 19:14:05 -04:00
|
|
|
nbuckets = up / t.Bucket.Size_
|
2018-04-27 21:58:59 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if dirtyalloc == nil {
|
2023-04-25 19:14:05 -04:00
|
|
|
buckets = newarray(t.Bucket, int(nbuckets))
|
2018-04-27 21:58:59 +02:00
|
|
|
} else {
|
|
|
|
|
// dirtyalloc was previously generated by
|
2023-04-25 19:14:05 -04:00
|
|
|
// the above newarray(t.Bucket, int(nbuckets))
|
2018-04-27 21:58:59 +02:00
|
|
|
// but may not be empty.
|
|
|
|
|
buckets = dirtyalloc
|
2023-04-25 19:14:05 -04:00
|
|
|
size := t.Bucket.Size_ * nbuckets
|
2024-02-27 21:51:31 +00:00
|
|
|
if t.Bucket.Pointers() {
|
2018-04-27 21:58:59 +02:00
|
|
|
memclrHasPointers(buckets, size)
|
|
|
|
|
} else {
|
|
|
|
|
memclrNoHeapPointers(buckets, size)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if base != nbuckets {
|
|
|
|
|
// We preallocated some overflow buckets.
|
|
|
|
|
// To keep the overhead of tracking these overflow buckets to a minimum,
|
|
|
|
|
// we use the convention that if a preallocated overflow bucket's overflow
|
|
|
|
|
// pointer is nil, then there are more available by bumping the pointer.
|
|
|
|
|
// We need a safe non-nil pointer for the last overflow bucket; just use buckets.
|
2023-04-25 19:14:05 -04:00
|
|
|
nextOverflow = (*bmap)(add(buckets, base*uintptr(t.BucketSize)))
|
|
|
|
|
last := (*bmap)(add(buckets, (nbuckets-1)*uintptr(t.BucketSize)))
|
2018-04-27 21:58:59 +02:00
|
|
|
last.setoverflow(t, (*bmap)(buckets))
|
|
|
|
|
}
|
|
|
|
|
return buckets, nextOverflow
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-16 14:16:19 -07:00
|
|
|
// mapaccess1 returns a pointer to h[key]. Never returns nil, instead
|
2019-04-22 13:37:08 -07:00
|
|
|
// it will return a reference to the zero object for the elem type if
|
2014-07-16 14:16:19 -07:00
|
|
|
// the key is not in the map.
|
|
|
|
|
// NOTE: The returned pointer may keep the whole map live, so don't
|
|
|
|
|
// hold onto it for very long.
|
|
|
|
|
func mapaccess1(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {
|
|
|
|
|
if raceenabled && h != nil {
|
2017-09-22 15:16:26 -04:00
|
|
|
callerpc := getcallerpc()
|
2021-05-21 13:37:19 -04:00
|
|
|
pc := abi.FuncPCABIInternal(mapaccess1)
|
2014-07-16 14:16:19 -07:00
|
|
|
racereadpc(unsafe.Pointer(h), callerpc, pc)
|
2023-04-25 19:14:05 -04:00
|
|
|
raceReadObjectPC(t.Key, key, callerpc, pc)
|
2014-07-16 14:16:19 -07:00
|
|
|
}
|
2015-10-21 11:04:42 -07:00
|
|
|
if msanenabled && h != nil {
|
2023-04-25 19:14:05 -04:00
|
|
|
msanread(key, t.Key.Size_)
|
2015-10-21 11:04:42 -07:00
|
|
|
}
|
2021-01-05 17:52:43 +08:00
|
|
|
if asanenabled && h != nil {
|
2023-04-25 19:14:05 -04:00
|
|
|
asanread(key, t.Key.Size_)
|
2021-01-05 17:52:43 +08:00
|
|
|
}
|
2014-07-16 14:16:19 -07:00
|
|
|
if h == nil || h.count == 0 {
|
2023-06-09 22:59:48 +08:00
|
|
|
if err := mapKeyError(t, key); err != nil {
|
|
|
|
|
panic(err) // see issue 23734
|
2018-12-28 14:34:48 -08:00
|
|
|
}
|
2024-05-21 23:04:21 -04:00
|
|
|
return unsafe.Pointer(&zeroVal[0])
|
2014-07-16 14:16:19 -07:00
|
|
|
}
|
2015-12-07 14:22:08 -05:00
|
|
|
if h.flags&hashWriting != 0 {
|
2022-03-04 13:24:04 -05:00
|
|
|
fatal("concurrent map read and map write")
|
2015-12-07 14:22:08 -05:00
|
|
|
}
|
2023-04-25 19:14:05 -04:00
|
|
|
hash := t.Hasher(key, uintptr(h.hash0))
|
2017-08-20 11:47:50 -07:00
|
|
|
m := bucketMask(h.B)
|
2023-04-25 19:14:05 -04:00
|
|
|
b := (*bmap)(add(h.buckets, (hash&m)*uintptr(t.BucketSize)))
|
2014-07-16 14:16:19 -07:00
|
|
|
if c := h.oldbuckets; c != nil {
|
runtime: limit the number of map overflow buckets
Consider repeatedly adding many items to a map
and then deleting them all, as in #16070. The map
itself doesn't need to grow above the high water
mark of number of items. However, due to random
collisions, the map can accumulate overflow
buckets.
Prior to this CL, those overflow buckets were
never removed, which led to a slow memory leak.
The problem with removing overflow buckets is
iterators. The obvious approach is to repack
keys and values and eliminate unused overflow
buckets. However, keys, values, and overflow
buckets cannot be manipulated without disrupting
iterators.
This CL takes a different approach, which is to
reuse the existing map growth mechanism,
which is well established, well tested, and
safe in the presence of iterators.
When a map has accumulated enough overflow buckets
we trigger map growth, but grow into a map of the
same size as before. The old overflow buckets will
be left behind for garbage collection.
For the code in #16070, instead of climbing
(very slowly) forever, memory usage now cycles
between 264mb and 483mb every 15 minutes or so.
To avoid increasing the size of maps,
the overflow bucket counter is only 16 bits.
For large maps, the counter is incremented
stochastically.
Fixes #16070
Change-Id: If551d77613ec6836907efca58bda3deee304297e
Reviewed-on: https://go-review.googlesource.com/25049
Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2016-07-19 11:47:53 -07:00
|
|
|
if !h.sameSizeGrow() {
|
|
|
|
|
// There used to be half as many buckets; mask down one more power of two.
|
|
|
|
|
m >>= 1
|
|
|
|
|
}
|
2023-04-25 19:14:05 -04:00
|
|
|
oldb := (*bmap)(add(c, (hash&m)*uintptr(t.BucketSize)))
|
2014-07-16 14:16:19 -07:00
|
|
|
if !evacuated(oldb) {
|
|
|
|
|
b = oldb
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-06-07 09:55:05 -07:00
|
|
|
top := tophash(hash)
|
2018-10-15 15:14:48 -07:00
|
|
|
bucketloop:
|
2017-08-23 07:49:25 -07:00
|
|
|
for ; b != nil; b = b.overflow(t) {
|
2024-01-31 03:51:34 +00:00
|
|
|
for i := uintptr(0); i < abi.MapBucketCount; i++ {
|
2014-07-16 14:16:19 -07:00
|
|
|
if b.tophash[i] != top {
|
2018-10-15 15:14:48 -07:00
|
|
|
if b.tophash[i] == emptyRest {
|
|
|
|
|
break bucketloop
|
|
|
|
|
}
|
2014-07-16 14:16:19 -07:00
|
|
|
continue
|
|
|
|
|
}
|
2023-04-25 19:14:05 -04:00
|
|
|
k := add(unsafe.Pointer(b), dataOffset+i*uintptr(t.KeySize))
|
|
|
|
|
if t.IndirectKey() {
|
2014-07-16 14:16:19 -07:00
|
|
|
k = *((*unsafe.Pointer)(k))
|
|
|
|
|
}
|
2023-04-25 19:14:05 -04:00
|
|
|
if t.Key.Equal(key, k) {
|
2024-01-31 03:51:34 +00:00
|
|
|
e := add(unsafe.Pointer(b), dataOffset+abi.MapBucketCount*uintptr(t.KeySize)+i*uintptr(t.ValueSize))
|
2023-04-25 19:14:05 -04:00
|
|
|
if t.IndirectElem() {
|
2019-04-22 13:37:08 -07:00
|
|
|
e = *((*unsafe.Pointer)(e))
|
2014-07-16 14:16:19 -07:00
|
|
|
}
|
2019-04-22 13:37:08 -07:00
|
|
|
return e
|
2014-07-16 14:16:19 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-05-21 23:04:21 -04:00
|
|
|
return unsafe.Pointer(&zeroVal[0])
|
2014-07-16 14:16:19 -07:00
|
|
|
}
|
|
|
|
|
|
2024-05-21 23:02:51 -04:00
|
|
|
// mapaccess2 should be an internal detail,
|
|
|
|
|
// but widely used packages access it using linkname.
|
|
|
|
|
// Notable members of the hall of shame include:
|
|
|
|
|
// - github.com/ugorji/go/codec
|
|
|
|
|
//
|
|
|
|
|
// Do not remove or change the type signature.
|
|
|
|
|
// See go.dev/issue/67401.
|
|
|
|
|
//
|
|
|
|
|
//go:linkname mapaccess2
|
2014-07-16 14:16:19 -07:00
|
|
|
func mapaccess2(t *maptype, h *hmap, key unsafe.Pointer) (unsafe.Pointer, bool) {
|
|
|
|
|
if raceenabled && h != nil {
|
2017-09-22 15:16:26 -04:00
|
|
|
callerpc := getcallerpc()
|
2021-05-21 13:37:19 -04:00
|
|
|
pc := abi.FuncPCABIInternal(mapaccess2)
|
2014-07-16 14:16:19 -07:00
|
|
|
racereadpc(unsafe.Pointer(h), callerpc, pc)
|
2023-04-25 19:14:05 -04:00
|
|
|
raceReadObjectPC(t.Key, key, callerpc, pc)
|
2014-07-16 14:16:19 -07:00
|
|
|
}
|
2015-10-21 11:04:42 -07:00
|
|
|
if msanenabled && h != nil {
|
2023-04-25 19:14:05 -04:00
|
|
|
msanread(key, t.Key.Size_)
|
2015-10-21 11:04:42 -07:00
|
|
|
}
|
2021-01-05 17:52:43 +08:00
|
|
|
if asanenabled && h != nil {
|
2023-04-25 19:14:05 -04:00
|
|
|
asanread(key, t.Key.Size_)
|
2021-01-05 17:52:43 +08:00
|
|
|
}
|
2014-07-16 14:16:19 -07:00
|
|
|
if h == nil || h.count == 0 {
|
2023-06-09 22:59:48 +08:00
|
|
|
if err := mapKeyError(t, key); err != nil {
|
|
|
|
|
panic(err) // see issue 23734
|
2018-12-28 14:34:48 -08:00
|
|
|
}
|
2024-05-21 23:04:21 -04:00
|
|
|
return unsafe.Pointer(&zeroVal[0]), false
|
2014-07-16 14:16:19 -07:00
|
|
|
}
|
2015-12-07 14:22:08 -05:00
|
|
|
if h.flags&hashWriting != 0 {
|
2022-03-04 13:24:04 -05:00
|
|
|
fatal("concurrent map read and map write")
|
2015-12-07 14:22:08 -05:00
|
|
|
}
|
2023-04-25 19:14:05 -04:00
|
|
|
hash := t.Hasher(key, uintptr(h.hash0))
|
2017-08-20 11:47:50 -07:00
|
|
|
m := bucketMask(h.B)
|
2023-04-25 19:14:05 -04:00
|
|
|
b := (*bmap)(add(h.buckets, (hash&m)*uintptr(t.BucketSize)))
|
2014-07-16 14:16:19 -07:00
|
|
|
if c := h.oldbuckets; c != nil {
|
runtime: limit the number of map overflow buckets
Consider repeatedly adding many items to a map
and then deleting them all, as in #16070. The map
itself doesn't need to grow above the high water
mark of number of items. However, due to random
collisions, the map can accumulate overflow
buckets.
Prior to this CL, those overflow buckets were
never removed, which led to a slow memory leak.
The problem with removing overflow buckets is
iterators. The obvious approach is to repack
keys and values and eliminate unused overflow
buckets. However, keys, values, and overflow
buckets cannot be manipulated without disrupting
iterators.
This CL takes a different approach, which is to
reuse the existing map growth mechanism,
which is well established, well tested, and
safe in the presence of iterators.
When a map has accumulated enough overflow buckets
we trigger map growth, but grow into a map of the
same size as before. The old overflow buckets will
be left behind for garbage collection.
For the code in #16070, instead of climbing
(very slowly) forever, memory usage now cycles
between 264mb and 483mb every 15 minutes or so.
To avoid increasing the size of maps,
the overflow bucket counter is only 16 bits.
For large maps, the counter is incremented
stochastically.
Fixes #16070
Change-Id: If551d77613ec6836907efca58bda3deee304297e
Reviewed-on: https://go-review.googlesource.com/25049
Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2016-07-19 11:47:53 -07:00
|
|
|
if !h.sameSizeGrow() {
|
|
|
|
|
// There used to be half as many buckets; mask down one more power of two.
|
|
|
|
|
m >>= 1
|
|
|
|
|
}
|
2023-04-25 19:14:05 -04:00
|
|
|
oldb := (*bmap)(add(c, (hash&m)*uintptr(t.BucketSize)))
|
2014-07-16 14:16:19 -07:00
|
|
|
if !evacuated(oldb) {
|
|
|
|
|
b = oldb
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-06-07 09:55:05 -07:00
|
|
|
top := tophash(hash)
|
2018-10-15 15:14:48 -07:00
|
|
|
bucketloop:
|
2017-08-23 07:49:25 -07:00
|
|
|
for ; b != nil; b = b.overflow(t) {
|
2024-01-31 03:51:34 +00:00
|
|
|
for i := uintptr(0); i < abi.MapBucketCount; i++ {
|
2014-07-16 14:16:19 -07:00
|
|
|
if b.tophash[i] != top {
|
2018-10-15 15:14:48 -07:00
|
|
|
if b.tophash[i] == emptyRest {
|
|
|
|
|
break bucketloop
|
|
|
|
|
}
|
2014-07-16 14:16:19 -07:00
|
|
|
continue
|
|
|
|
|
}
|
2023-04-25 19:14:05 -04:00
|
|
|
k := add(unsafe.Pointer(b), dataOffset+i*uintptr(t.KeySize))
|
|
|
|
|
if t.IndirectKey() {
|
2014-07-16 14:16:19 -07:00
|
|
|
k = *((*unsafe.Pointer)(k))
|
|
|
|
|
}
|
2023-04-25 19:14:05 -04:00
|
|
|
if t.Key.Equal(key, k) {
|
2024-01-31 03:51:34 +00:00
|
|
|
e := add(unsafe.Pointer(b), dataOffset+abi.MapBucketCount*uintptr(t.KeySize)+i*uintptr(t.ValueSize))
|
2023-04-25 19:14:05 -04:00
|
|
|
if t.IndirectElem() {
|
2019-04-22 13:37:08 -07:00
|
|
|
e = *((*unsafe.Pointer)(e))
|
2014-07-16 14:16:19 -07:00
|
|
|
}
|
2019-04-22 13:37:08 -07:00
|
|
|
return e, true
|
2014-07-16 14:16:19 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-05-21 23:04:21 -04:00
|
|
|
return unsafe.Pointer(&zeroVal[0]), false
|
2014-07-16 14:16:19 -07:00
|
|
|
}
|
|
|
|
|
|
2022-11-11 19:22:35 +08:00
|
|
|
// returns both key and elem. Used by map iterator.
|
2014-07-16 14:16:19 -07:00
|
|
|
func mapaccessK(t *maptype, h *hmap, key unsafe.Pointer) (unsafe.Pointer, unsafe.Pointer) {
|
|
|
|
|
if h == nil || h.count == 0 {
|
|
|
|
|
return nil, nil
|
|
|
|
|
}
|
2023-04-25 19:14:05 -04:00
|
|
|
hash := t.Hasher(key, uintptr(h.hash0))
|
2017-08-20 11:47:50 -07:00
|
|
|
m := bucketMask(h.B)
|
2023-04-25 19:14:05 -04:00
|
|
|
b := (*bmap)(add(h.buckets, (hash&m)*uintptr(t.BucketSize)))
|
2014-07-16 14:16:19 -07:00
|
|
|
if c := h.oldbuckets; c != nil {
|
runtime: limit the number of map overflow buckets
Consider repeatedly adding many items to a map
and then deleting them all, as in #16070. The map
itself doesn't need to grow above the high water
mark of number of items. However, due to random
collisions, the map can accumulate overflow
buckets.
Prior to this CL, those overflow buckets were
never removed, which led to a slow memory leak.
The problem with removing overflow buckets is
iterators. The obvious approach is to repack
keys and values and eliminate unused overflow
buckets. However, keys, values, and overflow
buckets cannot be manipulated without disrupting
iterators.
This CL takes a different approach, which is to
reuse the existing map growth mechanism,
which is well established, well tested, and
safe in the presence of iterators.
When a map has accumulated enough overflow buckets
we trigger map growth, but grow into a map of the
same size as before. The old overflow buckets will
be left behind for garbage collection.
For the code in #16070, instead of climbing
(very slowly) forever, memory usage now cycles
between 264mb and 483mb every 15 minutes or so.
To avoid increasing the size of maps,
the overflow bucket counter is only 16 bits.
For large maps, the counter is incremented
stochastically.
Fixes #16070
Change-Id: If551d77613ec6836907efca58bda3deee304297e
Reviewed-on: https://go-review.googlesource.com/25049
Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2016-07-19 11:47:53 -07:00
|
|
|
if !h.sameSizeGrow() {
|
|
|
|
|
// There used to be half as many buckets; mask down one more power of two.
|
|
|
|
|
m >>= 1
|
|
|
|
|
}
|
2023-04-25 19:14:05 -04:00
|
|
|
oldb := (*bmap)(add(c, (hash&m)*uintptr(t.BucketSize)))
|
2014-07-16 14:16:19 -07:00
|
|
|
if !evacuated(oldb) {
|
|
|
|
|
b = oldb
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-06-07 09:55:05 -07:00
|
|
|
top := tophash(hash)
|
2018-10-15 15:14:48 -07:00
|
|
|
bucketloop:
|
2017-08-23 07:49:25 -07:00
|
|
|
for ; b != nil; b = b.overflow(t) {
|
2024-01-31 03:51:34 +00:00
|
|
|
for i := uintptr(0); i < abi.MapBucketCount; i++ {
|
2014-07-16 14:16:19 -07:00
|
|
|
if b.tophash[i] != top {
|
2018-10-15 15:14:48 -07:00
|
|
|
if b.tophash[i] == emptyRest {
|
|
|
|
|
break bucketloop
|
|
|
|
|
}
|
2014-07-16 14:16:19 -07:00
|
|
|
continue
|
|
|
|
|
}
|
2023-04-25 19:14:05 -04:00
|
|
|
k := add(unsafe.Pointer(b), dataOffset+i*uintptr(t.KeySize))
|
|
|
|
|
if t.IndirectKey() {
|
2014-07-16 14:16:19 -07:00
|
|
|
k = *((*unsafe.Pointer)(k))
|
|
|
|
|
}
|
2023-04-25 19:14:05 -04:00
|
|
|
if t.Key.Equal(key, k) {
|
2024-01-31 03:51:34 +00:00
|
|
|
e := add(unsafe.Pointer(b), dataOffset+abi.MapBucketCount*uintptr(t.KeySize)+i*uintptr(t.ValueSize))
|
2023-04-25 19:14:05 -04:00
|
|
|
if t.IndirectElem() {
|
2019-04-22 13:37:08 -07:00
|
|
|
e = *((*unsafe.Pointer)(e))
|
2014-07-16 14:16:19 -07:00
|
|
|
}
|
2019-04-22 13:37:08 -07:00
|
|
|
return k, e
|
2014-07-16 14:16:19 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-08-23 07:49:25 -07:00
|
|
|
return nil, nil
|
2014-07-16 14:16:19 -07:00
|
|
|
}
|
|
|
|
|
|
2016-04-19 08:31:04 -07:00
|
|
|
func mapaccess1_fat(t *maptype, h *hmap, key, zero unsafe.Pointer) unsafe.Pointer {
|
2019-04-22 13:37:08 -07:00
|
|
|
e := mapaccess1(t, h, key)
|
2024-05-21 23:04:21 -04:00
|
|
|
if e == unsafe.Pointer(&zeroVal[0]) {
|
2016-04-19 08:31:04 -07:00
|
|
|
return zero
|
|
|
|
|
}
|
2019-04-22 13:37:08 -07:00
|
|
|
return e
|
2016-04-19 08:31:04 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func mapaccess2_fat(t *maptype, h *hmap, key, zero unsafe.Pointer) (unsafe.Pointer, bool) {
|
2019-04-22 13:37:08 -07:00
|
|
|
e := mapaccess1(t, h, key)
|
2024-05-21 23:04:21 -04:00
|
|
|
if e == unsafe.Pointer(&zeroVal[0]) {
|
2016-04-19 08:31:04 -07:00
|
|
|
return zero, false
|
|
|
|
|
}
|
2019-04-22 13:37:08 -07:00
|
|
|
return e, true
|
2016-04-19 08:31:04 -07:00
|
|
|
}
|
|
|
|
|
|
2016-10-11 08:36:38 -07:00
|
|
|
// Like mapaccess, but allocates a slot for the key if it is not present in the map.
|
2024-05-21 23:02:51 -04:00
|
|
|
//
|
|
|
|
|
// mapassign should be an internal detail,
|
|
|
|
|
// but widely used packages access it using linkname.
|
|
|
|
|
// Notable members of the hall of shame include:
|
2024-05-21 23:24:47 -04:00
|
|
|
// - github.com/bytedance/sonic
|
2024-05-22 15:46:02 -04:00
|
|
|
// - github.com/cloudwego/frugal
|
2024-05-22 17:09:02 -04:00
|
|
|
// - github.com/RomiChan/protobuf
|
2024-05-22 00:14:42 -04:00
|
|
|
// - github.com/segmentio/encoding
|
2024-05-21 23:02:51 -04:00
|
|
|
// - github.com/ugorji/go/codec
|
|
|
|
|
//
|
|
|
|
|
// Do not remove or change the type signature.
|
|
|
|
|
// See go.dev/issue/67401.
|
|
|
|
|
//
|
|
|
|
|
//go:linkname mapassign
|
2016-10-11 08:36:38 -07:00
|
|
|
func mapassign(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {
|
2014-07-16 14:16:19 -07:00
|
|
|
if h == nil {
|
2016-03-27 17:29:53 -07:00
|
|
|
panic(plainError("assignment to entry in nil map"))
|
2014-07-16 14:16:19 -07:00
|
|
|
}
|
|
|
|
|
if raceenabled {
|
2017-09-22 15:16:26 -04:00
|
|
|
callerpc := getcallerpc()
|
2021-05-21 13:37:19 -04:00
|
|
|
pc := abi.FuncPCABIInternal(mapassign)
|
2014-07-16 14:16:19 -07:00
|
|
|
racewritepc(unsafe.Pointer(h), callerpc, pc)
|
2023-04-25 19:14:05 -04:00
|
|
|
raceReadObjectPC(t.Key, key, callerpc, pc)
|
2014-07-16 14:16:19 -07:00
|
|
|
}
|
2015-10-21 11:04:42 -07:00
|
|
|
if msanenabled {
|
2023-04-25 19:14:05 -04:00
|
|
|
msanread(key, t.Key.Size_)
|
2015-10-21 11:04:42 -07:00
|
|
|
}
|
2021-01-05 17:52:43 +08:00
|
|
|
if asanenabled {
|
2023-04-25 19:14:05 -04:00
|
|
|
asanread(key, t.Key.Size_)
|
2021-01-05 17:52:43 +08:00
|
|
|
}
|
2015-12-07 14:22:08 -05:00
|
|
|
if h.flags&hashWriting != 0 {
|
2022-03-04 13:24:04 -05:00
|
|
|
fatal("concurrent map writes")
|
2015-12-07 14:22:08 -05:00
|
|
|
}
|
2023-04-25 19:14:05 -04:00
|
|
|
hash := t.Hasher(key, uintptr(h.hash0))
|
2014-07-16 14:16:19 -07:00
|
|
|
|
cmd/compile,runtime: generate hash functions only for types which are map keys
Right now we generate hash functions for all types, just in case they
are used as map keys. That's a lot of wasted effort and binary size
for types which will never be used as a map key. Instead, generate
hash functions only for types that we know are map keys.
Just doing that is a bit too simple, since maps with an interface type
as a key might have to hash any concrete key type that implements that
interface. So for that case, implement hashing of such types at
runtime (instead of with generated code). It will be slower, but only
for maps with interface types as keys, and maybe only a bit slower as
the aeshash time probably dominates the dispatch time.
Reorg where we keep the equals and hash functions. Move the hash function
from the key type to the map type, saving a field in every non-map type.
That leaves only one function in the alg structure, so get rid of that and
just keep the equal function in the type descriptor itself.
cmd/go now has 10 generated hash functions, instead of 504. Makes
cmd/go 1.0% smaller. Update #6853.
Speed on non-interface keys is unchanged. Speed on interface keys
is ~20% slower:
name old time/op new time/op delta
MapInterfaceString-8 23.0ns ±21% 27.6ns ±14% +20.01% (p=0.002 n=10+10)
MapInterfacePtr-8 19.4ns ±16% 23.7ns ± 7% +22.48% (p=0.000 n=10+8)
Change-Id: I7c2e42292a46b5d4e288aaec4029bdbb01089263
Reviewed-on: https://go-review.googlesource.com/c/go/+/191198
Run-TryBot: Keith Randall <khr@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Martin Möhrmann <moehrmann@google.com>
2019-08-06 15:22:51 -07:00
|
|
|
// Set hashWriting after calling t.hasher, since t.hasher may panic,
|
2017-03-02 06:30:26 -08:00
|
|
|
// in which case we have not actually done a write.
|
2018-07-31 11:24:37 -07:00
|
|
|
h.flags ^= hashWriting
|
2017-03-02 06:30:26 -08:00
|
|
|
|
2014-07-16 14:16:19 -07:00
|
|
|
if h.buckets == nil {
|
2023-04-25 19:14:05 -04:00
|
|
|
h.buckets = newobject(t.Bucket) // newarray(t.Bucket, 1)
|
2014-07-16 14:16:19 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
again:
|
2017-08-20 11:47:50 -07:00
|
|
|
bucket := hash & bucketMask(h.B)
|
runtime: limit the number of map overflow buckets
Consider repeatedly adding many items to a map
and then deleting them all, as in #16070. The map
itself doesn't need to grow above the high water
mark of number of items. However, due to random
collisions, the map can accumulate overflow
buckets.
Prior to this CL, those overflow buckets were
never removed, which led to a slow memory leak.
The problem with removing overflow buckets is
iterators. The obvious approach is to repack
keys and values and eliminate unused overflow
buckets. However, keys, values, and overflow
buckets cannot be manipulated without disrupting
iterators.
This CL takes a different approach, which is to
reuse the existing map growth mechanism,
which is well established, well tested, and
safe in the presence of iterators.
When a map has accumulated enough overflow buckets
we trigger map growth, but grow into a map of the
same size as before. The old overflow buckets will
be left behind for garbage collection.
For the code in #16070, instead of climbing
(very slowly) forever, memory usage now cycles
between 264mb and 483mb every 15 minutes or so.
To avoid increasing the size of maps,
the overflow bucket counter is only 16 bits.
For large maps, the counter is incremented
stochastically.
Fixes #16070
Change-Id: If551d77613ec6836907efca58bda3deee304297e
Reviewed-on: https://go-review.googlesource.com/25049
Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2016-07-19 11:47:53 -07:00
|
|
|
if h.growing() {
|
2014-07-16 14:16:19 -07:00
|
|
|
growWork(t, h, bucket)
|
|
|
|
|
}
|
2023-04-25 19:14:05 -04:00
|
|
|
b := (*bmap)(add(h.buckets, bucket*uintptr(t.BucketSize)))
|
2017-06-07 09:55:05 -07:00
|
|
|
top := tophash(hash)
|
2014-07-16 14:16:19 -07:00
|
|
|
|
|
|
|
|
var inserti *uint8
|
|
|
|
|
var insertk unsafe.Pointer
|
2019-04-22 13:37:08 -07:00
|
|
|
var elem unsafe.Pointer
|
2018-10-15 15:14:48 -07:00
|
|
|
bucketloop:
|
2014-07-16 14:16:19 -07:00
|
|
|
for {
|
2024-01-31 03:51:34 +00:00
|
|
|
for i := uintptr(0); i < abi.MapBucketCount; i++ {
|
2014-07-16 14:16:19 -07:00
|
|
|
if b.tophash[i] != top {
|
2018-10-15 15:14:48 -07:00
|
|
|
if isEmpty(b.tophash[i]) && inserti == nil {
|
2014-07-16 14:16:19 -07:00
|
|
|
inserti = &b.tophash[i]
|
2023-04-25 19:14:05 -04:00
|
|
|
insertk = add(unsafe.Pointer(b), dataOffset+i*uintptr(t.KeySize))
|
2024-01-31 03:51:34 +00:00
|
|
|
elem = add(unsafe.Pointer(b), dataOffset+abi.MapBucketCount*uintptr(t.KeySize)+i*uintptr(t.ValueSize))
|
2014-07-16 14:16:19 -07:00
|
|
|
}
|
2018-10-15 15:14:48 -07:00
|
|
|
if b.tophash[i] == emptyRest {
|
|
|
|
|
break bucketloop
|
|
|
|
|
}
|
2014-07-16 14:16:19 -07:00
|
|
|
continue
|
|
|
|
|
}
|
2023-04-25 19:14:05 -04:00
|
|
|
k := add(unsafe.Pointer(b), dataOffset+i*uintptr(t.KeySize))
|
|
|
|
|
if t.IndirectKey() {
|
2016-10-11 08:36:38 -07:00
|
|
|
k = *((*unsafe.Pointer)(k))
|
2014-07-16 14:16:19 -07:00
|
|
|
}
|
2023-04-25 19:14:05 -04:00
|
|
|
if !t.Key.Equal(key, k) {
|
2014-07-16 14:16:19 -07:00
|
|
|
continue
|
|
|
|
|
}
|
2016-03-01 23:21:55 +00:00
|
|
|
// already have a mapping for key. Update it.
|
2023-04-25 19:14:05 -04:00
|
|
|
if t.NeedKeyUpdate() {
|
|
|
|
|
typedmemmove(t.Key, k, key)
|
2015-06-08 08:42:28 -07:00
|
|
|
}
|
2024-01-31 03:51:34 +00:00
|
|
|
elem = add(unsafe.Pointer(b), dataOffset+abi.MapBucketCount*uintptr(t.KeySize)+i*uintptr(t.ValueSize))
|
2015-12-07 14:22:08 -05:00
|
|
|
goto done
|
2014-07-16 14:16:19 -07:00
|
|
|
}
|
2014-12-19 20:44:18 -08:00
|
|
|
ovf := b.overflow(t)
|
|
|
|
|
if ovf == nil {
|
2014-07-16 14:16:19 -07:00
|
|
|
break
|
|
|
|
|
}
|
2014-12-19 20:44:18 -08:00
|
|
|
b = ovf
|
2014-07-16 14:16:19 -07:00
|
|
|
}
|
|
|
|
|
|
runtime: limit the number of map overflow buckets
Consider repeatedly adding many items to a map
and then deleting them all, as in #16070. The map
itself doesn't need to grow above the high water
mark of number of items. However, due to random
collisions, the map can accumulate overflow
buckets.
Prior to this CL, those overflow buckets were
never removed, which led to a slow memory leak.
The problem with removing overflow buckets is
iterators. The obvious approach is to repack
keys and values and eliminate unused overflow
buckets. However, keys, values, and overflow
buckets cannot be manipulated without disrupting
iterators.
This CL takes a different approach, which is to
reuse the existing map growth mechanism,
which is well established, well tested, and
safe in the presence of iterators.
When a map has accumulated enough overflow buckets
we trigger map growth, but grow into a map of the
same size as before. The old overflow buckets will
be left behind for garbage collection.
For the code in #16070, instead of climbing
(very slowly) forever, memory usage now cycles
between 264mb and 483mb every 15 minutes or so.
To avoid increasing the size of maps,
the overflow bucket counter is only 16 bits.
For large maps, the counter is incremented
stochastically.
Fixes #16070
Change-Id: If551d77613ec6836907efca58bda3deee304297e
Reviewed-on: https://go-review.googlesource.com/25049
Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2016-07-19 11:47:53 -07:00
|
|
|
// Did not find mapping for key. Allocate new cell & add entry.
|
|
|
|
|
|
|
|
|
|
// If we hit the max load factor or we have too many overflow buckets,
|
|
|
|
|
// and we're not already in the middle of growing, start growing.
|
2017-09-01 12:32:38 -07:00
|
|
|
if !h.growing() && (overLoadFactor(h.count+1, h.B) || tooManyOverflowBuckets(h.noverflow, h.B)) {
|
2014-07-16 14:16:19 -07:00
|
|
|
hashGrow(t, h)
|
|
|
|
|
goto again // Growing the table invalidates everything, so try again
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if inserti == nil {
|
2020-09-28 17:38:13 +08:00
|
|
|
// The current bucket and all the overflow buckets connected to it are full, allocate a new one.
|
2017-04-13 05:25:20 -07:00
|
|
|
newb := h.newoverflow(t, b)
|
2014-07-16 14:16:19 -07:00
|
|
|
inserti = &newb.tophash[0]
|
|
|
|
|
insertk = add(unsafe.Pointer(newb), dataOffset)
|
2024-01-31 03:51:34 +00:00
|
|
|
elem = add(insertk, abi.MapBucketCount*uintptr(t.KeySize))
|
2014-07-16 14:16:19 -07:00
|
|
|
}
|
|
|
|
|
|
2019-04-22 13:37:08 -07:00
|
|
|
// store new key/elem at insert position
|
2023-04-25 19:14:05 -04:00
|
|
|
if t.IndirectKey() {
|
|
|
|
|
kmem := newobject(t.Key)
|
2014-07-16 14:16:19 -07:00
|
|
|
*(*unsafe.Pointer)(insertk) = kmem
|
|
|
|
|
insertk = kmem
|
|
|
|
|
}
|
2023-04-25 19:14:05 -04:00
|
|
|
if t.IndirectElem() {
|
|
|
|
|
vmem := newobject(t.Elem)
|
2019-04-22 13:37:08 -07:00
|
|
|
*(*unsafe.Pointer)(elem) = vmem
|
2014-07-16 14:16:19 -07:00
|
|
|
}
|
2023-04-25 19:14:05 -04:00
|
|
|
typedmemmove(t.Key, insertk, key)
|
2014-07-16 14:16:19 -07:00
|
|
|
*inserti = top
|
|
|
|
|
h.count++
|
2015-12-07 14:22:08 -05:00
|
|
|
|
|
|
|
|
done:
|
|
|
|
|
if h.flags&hashWriting == 0 {
|
2022-03-04 13:24:04 -05:00
|
|
|
fatal("concurrent map writes")
|
2015-12-07 14:22:08 -05:00
|
|
|
}
|
|
|
|
|
h.flags &^= hashWriting
|
2023-04-25 19:14:05 -04:00
|
|
|
if t.IndirectElem() {
|
2019-04-22 13:37:08 -07:00
|
|
|
elem = *((*unsafe.Pointer)(elem))
|
2016-10-11 08:36:38 -07:00
|
|
|
}
|
2019-04-22 13:37:08 -07:00
|
|
|
return elem
|
2014-07-16 14:16:19 -07:00
|
|
|
}
|
|
|
|
|
|
2024-05-21 23:02:51 -04:00
|
|
|
// mapdelete should be an internal detail,
|
|
|
|
|
// but widely used packages access it using linkname.
|
|
|
|
|
// Notable members of the hall of shame include:
|
|
|
|
|
// - github.com/ugorji/go/codec
|
|
|
|
|
//
|
|
|
|
|
// Do not remove or change the type signature.
|
|
|
|
|
// See go.dev/issue/67401.
|
|
|
|
|
//
|
|
|
|
|
//go:linkname mapdelete
|
2014-07-16 14:16:19 -07:00
|
|
|
func mapdelete(t *maptype, h *hmap, key unsafe.Pointer) {
|
|
|
|
|
if raceenabled && h != nil {
|
2017-09-22 15:16:26 -04:00
|
|
|
callerpc := getcallerpc()
|
2021-05-21 13:37:19 -04:00
|
|
|
pc := abi.FuncPCABIInternal(mapdelete)
|
2014-07-16 14:16:19 -07:00
|
|
|
racewritepc(unsafe.Pointer(h), callerpc, pc)
|
2023-04-25 19:14:05 -04:00
|
|
|
raceReadObjectPC(t.Key, key, callerpc, pc)
|
2014-07-16 14:16:19 -07:00
|
|
|
}
|
2015-10-21 11:04:42 -07:00
|
|
|
if msanenabled && h != nil {
|
2023-04-25 19:14:05 -04:00
|
|
|
msanread(key, t.Key.Size_)
|
2015-10-21 11:04:42 -07:00
|
|
|
}
|
2021-01-05 17:52:43 +08:00
|
|
|
if asanenabled && h != nil {
|
2023-04-25 19:14:05 -04:00
|
|
|
asanread(key, t.Key.Size_)
|
2021-01-05 17:52:43 +08:00
|
|
|
}
|
2014-07-16 14:16:19 -07:00
|
|
|
if h == nil || h.count == 0 {
|
2023-06-09 22:59:48 +08:00
|
|
|
if err := mapKeyError(t, key); err != nil {
|
|
|
|
|
panic(err) // see issue 23734
|
2018-12-28 14:34:48 -08:00
|
|
|
}
|
2014-07-16 14:16:19 -07:00
|
|
|
return
|
|
|
|
|
}
|
2015-12-07 14:22:08 -05:00
|
|
|
if h.flags&hashWriting != 0 {
|
2022-03-04 13:24:04 -05:00
|
|
|
fatal("concurrent map writes")
|
2015-12-07 14:22:08 -05:00
|
|
|
}
|
|
|
|
|
|
2023-04-25 19:14:05 -04:00
|
|
|
hash := t.Hasher(key, uintptr(h.hash0))
|
2017-03-02 06:30:26 -08:00
|
|
|
|
cmd/compile,runtime: generate hash functions only for types which are map keys
Right now we generate hash functions for all types, just in case they
are used as map keys. That's a lot of wasted effort and binary size
for types which will never be used as a map key. Instead, generate
hash functions only for types that we know are map keys.
Just doing that is a bit too simple, since maps with an interface type
as a key might have to hash any concrete key type that implements that
interface. So for that case, implement hashing of such types at
runtime (instead of with generated code). It will be slower, but only
for maps with interface types as keys, and maybe only a bit slower as
the aeshash time probably dominates the dispatch time.
Reorg where we keep the equals and hash functions. Move the hash function
from the key type to the map type, saving a field in every non-map type.
That leaves only one function in the alg structure, so get rid of that and
just keep the equal function in the type descriptor itself.
cmd/go now has 10 generated hash functions, instead of 504. Makes
cmd/go 1.0% smaller. Update #6853.
Speed on non-interface keys is unchanged. Speed on interface keys
is ~20% slower:
name old time/op new time/op delta
MapInterfaceString-8 23.0ns ±21% 27.6ns ±14% +20.01% (p=0.002 n=10+10)
MapInterfacePtr-8 19.4ns ±16% 23.7ns ± 7% +22.48% (p=0.000 n=10+8)
Change-Id: I7c2e42292a46b5d4e288aaec4029bdbb01089263
Reviewed-on: https://go-review.googlesource.com/c/go/+/191198
Run-TryBot: Keith Randall <khr@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Martin Möhrmann <moehrmann@google.com>
2019-08-06 15:22:51 -07:00
|
|
|
// Set hashWriting after calling t.hasher, since t.hasher may panic,
|
2017-03-02 06:30:26 -08:00
|
|
|
// in which case we have not actually done a write (delete).
|
2018-07-31 11:24:37 -07:00
|
|
|
h.flags ^= hashWriting
|
2017-03-02 06:30:26 -08:00
|
|
|
|
2017-08-20 11:47:50 -07:00
|
|
|
bucket := hash & bucketMask(h.B)
|
runtime: limit the number of map overflow buckets
Consider repeatedly adding many items to a map
and then deleting them all, as in #16070. The map
itself doesn't need to grow above the high water
mark of number of items. However, due to random
collisions, the map can accumulate overflow
buckets.
Prior to this CL, those overflow buckets were
never removed, which led to a slow memory leak.
The problem with removing overflow buckets is
iterators. The obvious approach is to repack
keys and values and eliminate unused overflow
buckets. However, keys, values, and overflow
buckets cannot be manipulated without disrupting
iterators.
This CL takes a different approach, which is to
reuse the existing map growth mechanism,
which is well established, well tested, and
safe in the presence of iterators.
When a map has accumulated enough overflow buckets
we trigger map growth, but grow into a map of the
same size as before. The old overflow buckets will
be left behind for garbage collection.
For the code in #16070, instead of climbing
(very slowly) forever, memory usage now cycles
between 264mb and 483mb every 15 minutes or so.
To avoid increasing the size of maps,
the overflow bucket counter is only 16 bits.
For large maps, the counter is incremented
stochastically.
Fixes #16070
Change-Id: If551d77613ec6836907efca58bda3deee304297e
Reviewed-on: https://go-review.googlesource.com/25049
Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2016-07-19 11:47:53 -07:00
|
|
|
if h.growing() {
|
2014-07-16 14:16:19 -07:00
|
|
|
growWork(t, h, bucket)
|
|
|
|
|
}
|
2023-04-25 19:14:05 -04:00
|
|
|
b := (*bmap)(add(h.buckets, bucket*uintptr(t.BucketSize)))
|
2018-10-15 17:24:21 -07:00
|
|
|
bOrig := b
|
2017-06-07 09:55:05 -07:00
|
|
|
top := tophash(hash)
|
2017-08-23 07:49:25 -07:00
|
|
|
search:
|
|
|
|
|
for ; b != nil; b = b.overflow(t) {
|
2024-01-31 03:51:34 +00:00
|
|
|
for i := uintptr(0); i < abi.MapBucketCount; i++ {
|
2014-07-16 14:16:19 -07:00
|
|
|
if b.tophash[i] != top {
|
2018-10-15 15:14:48 -07:00
|
|
|
if b.tophash[i] == emptyRest {
|
|
|
|
|
break search
|
|
|
|
|
}
|
2014-07-16 14:16:19 -07:00
|
|
|
continue
|
|
|
|
|
}
|
2023-04-25 19:14:05 -04:00
|
|
|
k := add(unsafe.Pointer(b), dataOffset+i*uintptr(t.KeySize))
|
2014-07-16 14:16:19 -07:00
|
|
|
k2 := k
|
2023-04-25 19:14:05 -04:00
|
|
|
if t.IndirectKey() {
|
2014-07-16 14:16:19 -07:00
|
|
|
k2 = *((*unsafe.Pointer)(k2))
|
|
|
|
|
}
|
2023-04-25 19:14:05 -04:00
|
|
|
if !t.Key.Equal(key, k2) {
|
2014-07-16 14:16:19 -07:00
|
|
|
continue
|
|
|
|
|
}
|
2017-08-21 11:00:31 -07:00
|
|
|
// Only clear key if there are pointers in it.
|
2023-04-25 19:14:05 -04:00
|
|
|
if t.IndirectKey() {
|
2016-10-17 17:00:05 -04:00
|
|
|
*(*unsafe.Pointer)(k) = nil
|
2024-02-27 21:51:31 +00:00
|
|
|
} else if t.Key.Pointers() {
|
2023-04-25 19:14:05 -04:00
|
|
|
memclrHasPointers(k, t.Key.Size_)
|
2016-10-17 17:00:05 -04:00
|
|
|
}
|
2024-01-31 03:51:34 +00:00
|
|
|
e := add(unsafe.Pointer(b), dataOffset+abi.MapBucketCount*uintptr(t.KeySize)+i*uintptr(t.ValueSize))
|
2023-04-25 19:14:05 -04:00
|
|
|
if t.IndirectElem() {
|
2019-04-22 13:37:08 -07:00
|
|
|
*(*unsafe.Pointer)(e) = nil
|
2024-02-27 21:51:31 +00:00
|
|
|
} else if t.Elem.Pointers() {
|
2023-04-25 19:14:05 -04:00
|
|
|
memclrHasPointers(e, t.Elem.Size_)
|
2018-06-20 22:19:56 -07:00
|
|
|
} else {
|
2023-04-25 19:14:05 -04:00
|
|
|
memclrNoHeapPointers(e, t.Elem.Size_)
|
2016-10-17 17:00:05 -04:00
|
|
|
}
|
2018-10-15 15:14:48 -07:00
|
|
|
b.tophash[i] = emptyOne
|
2018-10-15 17:24:21 -07:00
|
|
|
// If the bucket now ends in a bunch of emptyOne states,
|
|
|
|
|
// change those to emptyRest states.
|
|
|
|
|
// It would be nice to make this a separate function, but
|
|
|
|
|
// for loops are not currently inlineable.
|
2024-01-31 03:51:34 +00:00
|
|
|
if i == abi.MapBucketCount-1 {
|
2018-10-15 17:24:21 -07:00
|
|
|
if b.overflow(t) != nil && b.overflow(t).tophash[0] != emptyRest {
|
|
|
|
|
goto notLast
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if b.tophash[i+1] != emptyRest {
|
|
|
|
|
goto notLast
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for {
|
|
|
|
|
b.tophash[i] = emptyRest
|
|
|
|
|
if i == 0 {
|
|
|
|
|
if b == bOrig {
|
|
|
|
|
break // beginning of initial bucket, we're done.
|
|
|
|
|
}
|
|
|
|
|
// Find previous bucket, continue at its last entry.
|
|
|
|
|
c := b
|
|
|
|
|
for b = bOrig; b.overflow(t) != c; b = b.overflow(t) {
|
|
|
|
|
}
|
2024-01-31 03:51:34 +00:00
|
|
|
i = abi.MapBucketCount - 1
|
2018-10-15 17:24:21 -07:00
|
|
|
} else {
|
|
|
|
|
i--
|
|
|
|
|
}
|
|
|
|
|
if b.tophash[i] != emptyOne {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
notLast:
|
2014-07-16 14:16:19 -07:00
|
|
|
h.count--
|
2020-09-04 17:47:44 +07:00
|
|
|
// Reset the hash seed to make it more difficult for attackers to
|
|
|
|
|
// repeatedly trigger hash collisions. See issue 25237.
|
|
|
|
|
if h.count == 0 {
|
math/rand, math/rand/v2: use ChaCha8 for global rand
Move ChaCha8 code into internal/chacha8rand and use it to implement
runtime.rand, which is used for the unseeded global source for
both math/rand and math/rand/v2. This also affects the calculation of
the start point for iteration over very very large maps (when the
32-bit fastrand is not big enough).
The benefit is that misuse of the global random number generators
in math/rand and math/rand/v2 in contexts where non-predictable
randomness is important for security reasons is no longer a
security problem, removing a common mistake among programmers
who are unaware of the different kinds of randomness.
The cost is an extra 304 bytes per thread stored in the m struct
plus 2-3ns more per random uint64 due to the more sophisticated
algorithm. Using PCG looks like it would cost about the same,
although I haven't benchmarked that.
Before this, the math/rand and math/rand/v2 global generator
was wyrand (https://github.com/wangyi-fudan/wyhash).
For math/rand, using wyrand instead of the Mitchell/Reeds/Thompson
ALFG was justifiable, since the latter was not any better.
But for math/rand/v2, the global generator really should be
at least as good as one of the well-studied, specific algorithms
provided directly by the package, and it's not.
(Wyrand is still reasonable for scheduling and cache decisions.)
Good randomness does have a cost: about twice wyrand.
Also rationalize the various runtime rand references.
goos: linux
goarch: amd64
pkg: math/rand/v2
cpu: AMD Ryzen 9 7950X 16-Core Processor
│ bbb48afeb7.amd64 │ 5cf807d1ea.amd64 │
│ sec/op │ sec/op vs base │
ChaCha8-32 1.862n ± 2% 1.861n ± 2% ~ (p=0.825 n=20)
PCG_DXSM-32 1.471n ± 1% 1.460n ± 2% ~ (p=0.153 n=20)
SourceUint64-32 1.636n ± 2% 1.582n ± 1% -3.30% (p=0.000 n=20)
GlobalInt64-32 2.087n ± 1% 3.663n ± 1% +75.54% (p=0.000 n=20)
GlobalInt64Parallel-32 0.1042n ± 1% 0.2026n ± 1% +94.48% (p=0.000 n=20)
GlobalUint64-32 2.263n ± 2% 3.724n ± 1% +64.57% (p=0.000 n=20)
GlobalUint64Parallel-32 0.1019n ± 1% 0.1973n ± 1% +93.67% (p=0.000 n=20)
Int64-32 1.771n ± 1% 1.774n ± 1% ~ (p=0.449 n=20)
Uint64-32 1.863n ± 2% 1.866n ± 1% ~ (p=0.364 n=20)
GlobalIntN1000-32 3.134n ± 3% 4.730n ± 2% +50.95% (p=0.000 n=20)
IntN1000-32 2.489n ± 1% 2.489n ± 1% ~ (p=0.683 n=20)
Int64N1000-32 2.521n ± 1% 2.516n ± 1% ~ (p=0.394 n=20)
Int64N1e8-32 2.479n ± 1% 2.478n ± 2% ~ (p=0.743 n=20)
Int64N1e9-32 2.530n ± 2% 2.514n ± 2% ~ (p=0.193 n=20)
Int64N2e9-32 2.501n ± 1% 2.494n ± 1% ~ (p=0.616 n=20)
Int64N1e18-32 3.227n ± 1% 3.205n ± 1% ~ (p=0.101 n=20)
Int64N2e18-32 3.647n ± 1% 3.599n ± 1% ~ (p=0.019 n=20)
Int64N4e18-32 5.135n ± 1% 5.069n ± 2% ~ (p=0.034 n=20)
Int32N1000-32 2.657n ± 1% 2.637n ± 1% ~ (p=0.180 n=20)
Int32N1e8-32 2.636n ± 1% 2.636n ± 1% ~ (p=0.763 n=20)
Int32N1e9-32 2.660n ± 2% 2.638n ± 1% ~ (p=0.358 n=20)
Int32N2e9-32 2.662n ± 2% 2.618n ± 2% ~ (p=0.064 n=20)
Float32-32 2.272n ± 2% 2.239n ± 2% ~ (p=0.194 n=20)
Float64-32 2.272n ± 1% 2.286n ± 2% ~ (p=0.763 n=20)
ExpFloat64-32 3.762n ± 1% 3.744n ± 1% ~ (p=0.171 n=20)
NormFloat64-32 3.706n ± 1% 3.655n ± 2% ~ (p=0.066 n=20)
Perm3-32 32.93n ± 3% 34.62n ± 1% +5.13% (p=0.000 n=20)
Perm30-32 202.9n ± 1% 204.0n ± 1% ~ (p=0.482 n=20)
Perm30ViaShuffle-32 115.0n ± 1% 114.9n ± 1% ~ (p=0.358 n=20)
ShuffleOverhead-32 112.8n ± 1% 112.7n ± 1% ~ (p=0.692 n=20)
Concurrent-32 2.107n ± 0% 3.725n ± 1% +76.75% (p=0.000 n=20)
goos: darwin
goarch: arm64
pkg: math/rand/v2
│ bbb48afeb7.arm64 │ 5cf807d1ea.arm64 │
│ sec/op │ sec/op vs base │
ChaCha8-8 2.480n ± 0% 2.429n ± 0% -2.04% (p=0.000 n=20)
PCG_DXSM-8 2.531n ± 0% 2.530n ± 0% ~ (p=0.877 n=20)
SourceUint64-8 2.534n ± 0% 2.533n ± 0% ~ (p=0.732 n=20)
GlobalInt64-8 2.172n ± 1% 4.794n ± 0% +120.67% (p=0.000 n=20)
GlobalInt64Parallel-8 0.4320n ± 0% 0.9605n ± 0% +122.32% (p=0.000 n=20)
GlobalUint64-8 2.182n ± 0% 4.770n ± 0% +118.58% (p=0.000 n=20)
GlobalUint64Parallel-8 0.4307n ± 0% 0.9583n ± 0% +122.51% (p=0.000 n=20)
Int64-8 4.107n ± 0% 4.104n ± 0% ~ (p=0.416 n=20)
Uint64-8 4.080n ± 0% 4.080n ± 0% ~ (p=0.052 n=20)
GlobalIntN1000-8 2.814n ± 2% 5.643n ± 0% +100.50% (p=0.000 n=20)
IntN1000-8 4.141n ± 0% 4.139n ± 0% ~ (p=0.140 n=20)
Int64N1000-8 4.140n ± 0% 4.140n ± 0% ~ (p=0.313 n=20)
Int64N1e8-8 4.140n ± 0% 4.139n ± 0% ~ (p=0.103 n=20)
Int64N1e9-8 4.139n ± 0% 4.140n ± 0% ~ (p=0.761 n=20)
Int64N2e9-8 4.140n ± 0% 4.140n ± 0% ~ (p=0.636 n=20)
Int64N1e18-8 5.266n ± 0% 5.326n ± 1% +1.14% (p=0.001 n=20)
Int64N2e18-8 6.052n ± 0% 6.167n ± 0% +1.90% (p=0.000 n=20)
Int64N4e18-8 8.826n ± 0% 9.051n ± 0% +2.55% (p=0.000 n=20)
Int32N1000-8 4.127n ± 0% 4.132n ± 0% +0.12% (p=0.000 n=20)
Int32N1e8-8 4.126n ± 0% 4.131n ± 0% +0.12% (p=0.000 n=20)
Int32N1e9-8 4.127n ± 0% 4.132n ± 0% +0.12% (p=0.000 n=20)
Int32N2e9-8 4.132n ± 0% 4.131n ± 0% ~ (p=0.017 n=20)
Float32-8 4.109n ± 0% 4.105n ± 0% ~ (p=0.379 n=20)
Float64-8 4.107n ± 0% 4.106n ± 0% ~ (p=0.867 n=20)
ExpFloat64-8 5.339n ± 0% 5.383n ± 0% +0.82% (p=0.000 n=20)
NormFloat64-8 5.735n ± 0% 5.737n ± 1% ~ (p=0.856 n=20)
Perm3-8 26.65n ± 0% 26.80n ± 1% +0.58% (p=0.000 n=20)
Perm30-8 194.8n ± 1% 197.0n ± 0% +1.18% (p=0.000 n=20)
Perm30ViaShuffle-8 156.6n ± 0% 157.6n ± 1% +0.61% (p=0.000 n=20)
ShuffleOverhead-8 124.9n ± 0% 125.5n ± 0% +0.52% (p=0.000 n=20)
Concurrent-8 2.434n ± 3% 5.066n ± 0% +108.09% (p=0.000 n=20)
goos: linux
goarch: 386
pkg: math/rand/v2
cpu: AMD Ryzen 9 7950X 16-Core Processor
│ bbb48afeb7.386 │ 5cf807d1ea.386 │
│ sec/op │ sec/op vs base │
ChaCha8-32 11.295n ± 1% 4.748n ± 2% -57.96% (p=0.000 n=20)
PCG_DXSM-32 7.693n ± 1% 7.738n ± 2% ~ (p=0.542 n=20)
SourceUint64-32 7.658n ± 2% 7.622n ± 2% ~ (p=0.344 n=20)
GlobalInt64-32 3.473n ± 2% 7.526n ± 2% +116.73% (p=0.000 n=20)
GlobalInt64Parallel-32 0.3198n ± 0% 0.5444n ± 0% +70.22% (p=0.000 n=20)
GlobalUint64-32 3.612n ± 0% 7.575n ± 1% +109.69% (p=0.000 n=20)
GlobalUint64Parallel-32 0.3168n ± 0% 0.5403n ± 0% +70.51% (p=0.000 n=20)
Int64-32 7.673n ± 2% 7.789n ± 1% ~ (p=0.122 n=20)
Uint64-32 7.773n ± 1% 7.827n ± 2% ~ (p=0.920 n=20)
GlobalIntN1000-32 6.268n ± 1% 9.581n ± 1% +52.87% (p=0.000 n=20)
IntN1000-32 10.33n ± 2% 10.45n ± 1% ~ (p=0.233 n=20)
Int64N1000-32 10.98n ± 2% 11.01n ± 1% ~ (p=0.401 n=20)
Int64N1e8-32 11.19n ± 2% 10.97n ± 1% ~ (p=0.033 n=20)
Int64N1e9-32 11.06n ± 1% 11.08n ± 1% ~ (p=0.498 n=20)
Int64N2e9-32 11.10n ± 1% 11.01n ± 2% ~ (p=0.995 n=20)
Int64N1e18-32 15.23n ± 2% 15.04n ± 1% ~ (p=0.973 n=20)
Int64N2e18-32 15.89n ± 1% 15.85n ± 1% ~ (p=0.409 n=20)
Int64N4e18-32 18.96n ± 2% 19.34n ± 2% ~ (p=0.048 n=20)
Int32N1000-32 10.46n ± 2% 10.44n ± 2% ~ (p=0.480 n=20)
Int32N1e8-32 10.46n ± 2% 10.49n ± 2% ~ (p=0.951 n=20)
Int32N1e9-32 10.28n ± 2% 10.26n ± 1% ~ (p=0.431 n=20)
Int32N2e9-32 10.50n ± 2% 10.44n ± 2% ~ (p=0.249 n=20)
Float32-32 13.80n ± 2% 13.80n ± 2% ~ (p=0.751 n=20)
Float64-32 23.55n ± 2% 23.87n ± 0% ~ (p=0.408 n=20)
ExpFloat64-32 15.36n ± 1% 15.29n ± 2% ~ (p=0.316 n=20)
NormFloat64-32 13.57n ± 1% 13.79n ± 1% +1.66% (p=0.005 n=20)
Perm3-32 45.70n ± 2% 46.99n ± 2% +2.81% (p=0.001 n=20)
Perm30-32 399.0n ± 1% 403.8n ± 1% +1.19% (p=0.006 n=20)
Perm30ViaShuffle-32 349.0n ± 1% 350.4n ± 1% ~ (p=0.909 n=20)
ShuffleOverhead-32 322.3n ± 1% 323.8n ± 1% ~ (p=0.410 n=20)
Concurrent-32 3.331n ± 1% 7.312n ± 1% +119.50% (p=0.000 n=20)
For #61716.
Change-Id: Ibdddeed85c34d9ae397289dc899e04d4845f9ed2
Reviewed-on: https://go-review.googlesource.com/c/go/+/516860
Reviewed-by: Michael Pratt <mpratt@google.com>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
2023-08-06 13:26:28 +10:00
|
|
|
h.hash0 = uint32(rand())
|
2020-09-04 17:47:44 +07:00
|
|
|
}
|
2017-08-23 07:49:25 -07:00
|
|
|
break search
|
2014-07-16 14:16:19 -07:00
|
|
|
}
|
|
|
|
|
}
|
2015-12-07 14:22:08 -05:00
|
|
|
|
|
|
|
|
if h.flags&hashWriting == 0 {
|
2022-03-04 13:24:04 -05:00
|
|
|
fatal("concurrent map writes")
|
2015-12-07 14:22:08 -05:00
|
|
|
}
|
|
|
|
|
h.flags &^= hashWriting
|
2014-07-16 14:16:19 -07:00
|
|
|
}
|
|
|
|
|
|
2017-08-31 21:26:03 +02:00
|
|
|
// mapiterinit initializes the hiter struct used for ranging over maps.
|
|
|
|
|
// The hiter struct pointed to by 'it' is allocated on the stack
|
|
|
|
|
// by the compilers order pass or on the heap by reflect_mapiterinit.
|
|
|
|
|
// Both need to have zeroed hiter since the struct contains pointers.
|
2024-05-21 23:02:51 -04:00
|
|
|
//
|
|
|
|
|
// mapiterinit should be an internal detail,
|
|
|
|
|
// but widely used packages access it using linkname.
|
|
|
|
|
// Notable members of the hall of shame include:
|
2024-05-21 23:24:47 -04:00
|
|
|
// - github.com/bytedance/sonic
|
2024-05-22 15:46:02 -04:00
|
|
|
// - github.com/cloudwego/frugal
|
2024-05-21 23:02:51 -04:00
|
|
|
// - github.com/goccy/go-json
|
2024-05-22 17:09:02 -04:00
|
|
|
// - github.com/RomiChan/protobuf
|
2024-05-22 00:14:42 -04:00
|
|
|
// - github.com/segmentio/encoding
|
2024-05-21 23:02:51 -04:00
|
|
|
// - github.com/ugorji/go/codec
|
2024-05-22 23:06:30 -04:00
|
|
|
// - github.com/wI2L/jettison
|
2024-05-21 23:02:51 -04:00
|
|
|
//
|
|
|
|
|
// Do not remove or change the type signature.
|
|
|
|
|
// See go.dev/issue/67401.
|
|
|
|
|
//
|
|
|
|
|
//go:linkname mapiterinit
|
2014-07-16 14:16:19 -07:00
|
|
|
func mapiterinit(t *maptype, h *hmap, it *hiter) {
|
|
|
|
|
if raceenabled && h != nil {
|
2017-09-22 15:16:26 -04:00
|
|
|
callerpc := getcallerpc()
|
2021-05-21 13:37:19 -04:00
|
|
|
racereadpc(unsafe.Pointer(h), callerpc, abi.FuncPCABIInternal(mapiterinit))
|
2014-07-16 14:16:19 -07:00
|
|
|
}
|
|
|
|
|
|
2021-05-20 09:57:04 -07:00
|
|
|
it.t = t
|
2014-07-16 14:16:19 -07:00
|
|
|
if h == nil || h.count == 0 {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-16 23:05:44 +00:00
|
|
|
if unsafe.Sizeof(hiter{})/goarch.PtrSize != 12 {
|
2021-04-07 15:24:46 +00:00
|
|
|
throw("hash_iter size incorrect") // see cmd/compile/internal/reflectdata/reflect.go
|
2014-07-16 14:16:19 -07:00
|
|
|
}
|
|
|
|
|
it.h = h
|
|
|
|
|
|
|
|
|
|
// grab snapshot of bucket state
|
|
|
|
|
it.B = h.B
|
|
|
|
|
it.buckets = h.buckets
|
2024-02-27 21:51:31 +00:00
|
|
|
if !t.Bucket.Pointers() {
|
2015-01-26 21:04:41 +03:00
|
|
|
// Allocate the current slice and remember pointers to both current and old.
|
|
|
|
|
// This preserves all relevant overflow buckets alive even if
|
|
|
|
|
// the table grows and/or overflow buckets are added to the table
|
|
|
|
|
// while we are iterating.
|
|
|
|
|
h.createOverflow()
|
2017-04-12 14:42:24 -07:00
|
|
|
it.overflow = h.extra.overflow
|
2017-09-10 12:55:16 +02:00
|
|
|
it.oldoverflow = h.extra.oldoverflow
|
2015-01-26 21:04:41 +03:00
|
|
|
}
|
2014-07-16 14:16:19 -07:00
|
|
|
|
2014-09-08 17:42:21 -07:00
|
|
|
// decide where to start
|
math/rand, math/rand/v2: use ChaCha8 for global rand
Move ChaCha8 code into internal/chacha8rand and use it to implement
runtime.rand, which is used for the unseeded global source for
both math/rand and math/rand/v2. This also affects the calculation of
the start point for iteration over very very large maps (when the
32-bit fastrand is not big enough).
The benefit is that misuse of the global random number generators
in math/rand and math/rand/v2 in contexts where non-predictable
randomness is important for security reasons is no longer a
security problem, removing a common mistake among programmers
who are unaware of the different kinds of randomness.
The cost is an extra 304 bytes per thread stored in the m struct
plus 2-3ns more per random uint64 due to the more sophisticated
algorithm. Using PCG looks like it would cost about the same,
although I haven't benchmarked that.
Before this, the math/rand and math/rand/v2 global generator
was wyrand (https://github.com/wangyi-fudan/wyhash).
For math/rand, using wyrand instead of the Mitchell/Reeds/Thompson
ALFG was justifiable, since the latter was not any better.
But for math/rand/v2, the global generator really should be
at least as good as one of the well-studied, specific algorithms
provided directly by the package, and it's not.
(Wyrand is still reasonable for scheduling and cache decisions.)
Good randomness does have a cost: about twice wyrand.
Also rationalize the various runtime rand references.
goos: linux
goarch: amd64
pkg: math/rand/v2
cpu: AMD Ryzen 9 7950X 16-Core Processor
│ bbb48afeb7.amd64 │ 5cf807d1ea.amd64 │
│ sec/op │ sec/op vs base │
ChaCha8-32 1.862n ± 2% 1.861n ± 2% ~ (p=0.825 n=20)
PCG_DXSM-32 1.471n ± 1% 1.460n ± 2% ~ (p=0.153 n=20)
SourceUint64-32 1.636n ± 2% 1.582n ± 1% -3.30% (p=0.000 n=20)
GlobalInt64-32 2.087n ± 1% 3.663n ± 1% +75.54% (p=0.000 n=20)
GlobalInt64Parallel-32 0.1042n ± 1% 0.2026n ± 1% +94.48% (p=0.000 n=20)
GlobalUint64-32 2.263n ± 2% 3.724n ± 1% +64.57% (p=0.000 n=20)
GlobalUint64Parallel-32 0.1019n ± 1% 0.1973n ± 1% +93.67% (p=0.000 n=20)
Int64-32 1.771n ± 1% 1.774n ± 1% ~ (p=0.449 n=20)
Uint64-32 1.863n ± 2% 1.866n ± 1% ~ (p=0.364 n=20)
GlobalIntN1000-32 3.134n ± 3% 4.730n ± 2% +50.95% (p=0.000 n=20)
IntN1000-32 2.489n ± 1% 2.489n ± 1% ~ (p=0.683 n=20)
Int64N1000-32 2.521n ± 1% 2.516n ± 1% ~ (p=0.394 n=20)
Int64N1e8-32 2.479n ± 1% 2.478n ± 2% ~ (p=0.743 n=20)
Int64N1e9-32 2.530n ± 2% 2.514n ± 2% ~ (p=0.193 n=20)
Int64N2e9-32 2.501n ± 1% 2.494n ± 1% ~ (p=0.616 n=20)
Int64N1e18-32 3.227n ± 1% 3.205n ± 1% ~ (p=0.101 n=20)
Int64N2e18-32 3.647n ± 1% 3.599n ± 1% ~ (p=0.019 n=20)
Int64N4e18-32 5.135n ± 1% 5.069n ± 2% ~ (p=0.034 n=20)
Int32N1000-32 2.657n ± 1% 2.637n ± 1% ~ (p=0.180 n=20)
Int32N1e8-32 2.636n ± 1% 2.636n ± 1% ~ (p=0.763 n=20)
Int32N1e9-32 2.660n ± 2% 2.638n ± 1% ~ (p=0.358 n=20)
Int32N2e9-32 2.662n ± 2% 2.618n ± 2% ~ (p=0.064 n=20)
Float32-32 2.272n ± 2% 2.239n ± 2% ~ (p=0.194 n=20)
Float64-32 2.272n ± 1% 2.286n ± 2% ~ (p=0.763 n=20)
ExpFloat64-32 3.762n ± 1% 3.744n ± 1% ~ (p=0.171 n=20)
NormFloat64-32 3.706n ± 1% 3.655n ± 2% ~ (p=0.066 n=20)
Perm3-32 32.93n ± 3% 34.62n ± 1% +5.13% (p=0.000 n=20)
Perm30-32 202.9n ± 1% 204.0n ± 1% ~ (p=0.482 n=20)
Perm30ViaShuffle-32 115.0n ± 1% 114.9n ± 1% ~ (p=0.358 n=20)
ShuffleOverhead-32 112.8n ± 1% 112.7n ± 1% ~ (p=0.692 n=20)
Concurrent-32 2.107n ± 0% 3.725n ± 1% +76.75% (p=0.000 n=20)
goos: darwin
goarch: arm64
pkg: math/rand/v2
│ bbb48afeb7.arm64 │ 5cf807d1ea.arm64 │
│ sec/op │ sec/op vs base │
ChaCha8-8 2.480n ± 0% 2.429n ± 0% -2.04% (p=0.000 n=20)
PCG_DXSM-8 2.531n ± 0% 2.530n ± 0% ~ (p=0.877 n=20)
SourceUint64-8 2.534n ± 0% 2.533n ± 0% ~ (p=0.732 n=20)
GlobalInt64-8 2.172n ± 1% 4.794n ± 0% +120.67% (p=0.000 n=20)
GlobalInt64Parallel-8 0.4320n ± 0% 0.9605n ± 0% +122.32% (p=0.000 n=20)
GlobalUint64-8 2.182n ± 0% 4.770n ± 0% +118.58% (p=0.000 n=20)
GlobalUint64Parallel-8 0.4307n ± 0% 0.9583n ± 0% +122.51% (p=0.000 n=20)
Int64-8 4.107n ± 0% 4.104n ± 0% ~ (p=0.416 n=20)
Uint64-8 4.080n ± 0% 4.080n ± 0% ~ (p=0.052 n=20)
GlobalIntN1000-8 2.814n ± 2% 5.643n ± 0% +100.50% (p=0.000 n=20)
IntN1000-8 4.141n ± 0% 4.139n ± 0% ~ (p=0.140 n=20)
Int64N1000-8 4.140n ± 0% 4.140n ± 0% ~ (p=0.313 n=20)
Int64N1e8-8 4.140n ± 0% 4.139n ± 0% ~ (p=0.103 n=20)
Int64N1e9-8 4.139n ± 0% 4.140n ± 0% ~ (p=0.761 n=20)
Int64N2e9-8 4.140n ± 0% 4.140n ± 0% ~ (p=0.636 n=20)
Int64N1e18-8 5.266n ± 0% 5.326n ± 1% +1.14% (p=0.001 n=20)
Int64N2e18-8 6.052n ± 0% 6.167n ± 0% +1.90% (p=0.000 n=20)
Int64N4e18-8 8.826n ± 0% 9.051n ± 0% +2.55% (p=0.000 n=20)
Int32N1000-8 4.127n ± 0% 4.132n ± 0% +0.12% (p=0.000 n=20)
Int32N1e8-8 4.126n ± 0% 4.131n ± 0% +0.12% (p=0.000 n=20)
Int32N1e9-8 4.127n ± 0% 4.132n ± 0% +0.12% (p=0.000 n=20)
Int32N2e9-8 4.132n ± 0% 4.131n ± 0% ~ (p=0.017 n=20)
Float32-8 4.109n ± 0% 4.105n ± 0% ~ (p=0.379 n=20)
Float64-8 4.107n ± 0% 4.106n ± 0% ~ (p=0.867 n=20)
ExpFloat64-8 5.339n ± 0% 5.383n ± 0% +0.82% (p=0.000 n=20)
NormFloat64-8 5.735n ± 0% 5.737n ± 1% ~ (p=0.856 n=20)
Perm3-8 26.65n ± 0% 26.80n ± 1% +0.58% (p=0.000 n=20)
Perm30-8 194.8n ± 1% 197.0n ± 0% +1.18% (p=0.000 n=20)
Perm30ViaShuffle-8 156.6n ± 0% 157.6n ± 1% +0.61% (p=0.000 n=20)
ShuffleOverhead-8 124.9n ± 0% 125.5n ± 0% +0.52% (p=0.000 n=20)
Concurrent-8 2.434n ± 3% 5.066n ± 0% +108.09% (p=0.000 n=20)
goos: linux
goarch: 386
pkg: math/rand/v2
cpu: AMD Ryzen 9 7950X 16-Core Processor
│ bbb48afeb7.386 │ 5cf807d1ea.386 │
│ sec/op │ sec/op vs base │
ChaCha8-32 11.295n ± 1% 4.748n ± 2% -57.96% (p=0.000 n=20)
PCG_DXSM-32 7.693n ± 1% 7.738n ± 2% ~ (p=0.542 n=20)
SourceUint64-32 7.658n ± 2% 7.622n ± 2% ~ (p=0.344 n=20)
GlobalInt64-32 3.473n ± 2% 7.526n ± 2% +116.73% (p=0.000 n=20)
GlobalInt64Parallel-32 0.3198n ± 0% 0.5444n ± 0% +70.22% (p=0.000 n=20)
GlobalUint64-32 3.612n ± 0% 7.575n ± 1% +109.69% (p=0.000 n=20)
GlobalUint64Parallel-32 0.3168n ± 0% 0.5403n ± 0% +70.51% (p=0.000 n=20)
Int64-32 7.673n ± 2% 7.789n ± 1% ~ (p=0.122 n=20)
Uint64-32 7.773n ± 1% 7.827n ± 2% ~ (p=0.920 n=20)
GlobalIntN1000-32 6.268n ± 1% 9.581n ± 1% +52.87% (p=0.000 n=20)
IntN1000-32 10.33n ± 2% 10.45n ± 1% ~ (p=0.233 n=20)
Int64N1000-32 10.98n ± 2% 11.01n ± 1% ~ (p=0.401 n=20)
Int64N1e8-32 11.19n ± 2% 10.97n ± 1% ~ (p=0.033 n=20)
Int64N1e9-32 11.06n ± 1% 11.08n ± 1% ~ (p=0.498 n=20)
Int64N2e9-32 11.10n ± 1% 11.01n ± 2% ~ (p=0.995 n=20)
Int64N1e18-32 15.23n ± 2% 15.04n ± 1% ~ (p=0.973 n=20)
Int64N2e18-32 15.89n ± 1% 15.85n ± 1% ~ (p=0.409 n=20)
Int64N4e18-32 18.96n ± 2% 19.34n ± 2% ~ (p=0.048 n=20)
Int32N1000-32 10.46n ± 2% 10.44n ± 2% ~ (p=0.480 n=20)
Int32N1e8-32 10.46n ± 2% 10.49n ± 2% ~ (p=0.951 n=20)
Int32N1e9-32 10.28n ± 2% 10.26n ± 1% ~ (p=0.431 n=20)
Int32N2e9-32 10.50n ± 2% 10.44n ± 2% ~ (p=0.249 n=20)
Float32-32 13.80n ± 2% 13.80n ± 2% ~ (p=0.751 n=20)
Float64-32 23.55n ± 2% 23.87n ± 0% ~ (p=0.408 n=20)
ExpFloat64-32 15.36n ± 1% 15.29n ± 2% ~ (p=0.316 n=20)
NormFloat64-32 13.57n ± 1% 13.79n ± 1% +1.66% (p=0.005 n=20)
Perm3-32 45.70n ± 2% 46.99n ± 2% +2.81% (p=0.001 n=20)
Perm30-32 399.0n ± 1% 403.8n ± 1% +1.19% (p=0.006 n=20)
Perm30ViaShuffle-32 349.0n ± 1% 350.4n ± 1% ~ (p=0.909 n=20)
ShuffleOverhead-32 322.3n ± 1% 323.8n ± 1% ~ (p=0.410 n=20)
Concurrent-32 3.331n ± 1% 7.312n ± 1% +119.50% (p=0.000 n=20)
For #61716.
Change-Id: Ibdddeed85c34d9ae397289dc899e04d4845f9ed2
Reviewed-on: https://go-review.googlesource.com/c/go/+/516860
Reviewed-by: Michael Pratt <mpratt@google.com>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
2023-08-06 13:26:28 +10:00
|
|
|
r := uintptr(rand())
|
2017-08-20 11:47:50 -07:00
|
|
|
it.startBucket = r & bucketMask(h.B)
|
2024-01-31 03:51:34 +00:00
|
|
|
it.offset = uint8(r >> h.B & (abi.MapBucketCount - 1))
|
2014-09-08 17:42:21 -07:00
|
|
|
|
2014-07-16 14:16:19 -07:00
|
|
|
// iterator state
|
2014-09-08 17:42:21 -07:00
|
|
|
it.bucket = it.startBucket
|
2014-07-16 14:16:19 -07:00
|
|
|
|
|
|
|
|
// Remember we have an iterator.
|
2017-08-31 21:26:03 +02:00
|
|
|
// Can run concurrently with another mapiterinit().
|
2015-01-26 21:04:41 +03:00
|
|
|
if old := h.flags; old&(iterator|oldIterator) != iterator|oldIterator {
|
2015-11-02 14:09:24 -05:00
|
|
|
atomic.Or8(&h.flags, iterator|oldIterator)
|
2014-07-16 14:16:19 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mapiternext(it)
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-21 23:02:51 -04:00
|
|
|
// mapiternext should be an internal detail,
|
|
|
|
|
// but widely used packages access it using linkname.
|
|
|
|
|
// Notable members of the hall of shame include:
|
2024-05-21 23:24:47 -04:00
|
|
|
// - github.com/bytedance/sonic
|
2024-05-22 15:46:02 -04:00
|
|
|
// - github.com/cloudwego/frugal
|
2024-05-22 17:09:02 -04:00
|
|
|
// - github.com/RomiChan/protobuf
|
2024-05-22 00:14:42 -04:00
|
|
|
// - github.com/segmentio/encoding
|
2024-05-22 15:46:02 -04:00
|
|
|
// - github.com/ugorji/go/codec
|
2024-05-21 23:50:52 -04:00
|
|
|
// - gonum.org/v1/gonum
|
2024-05-21 23:02:51 -04:00
|
|
|
//
|
|
|
|
|
// Do not remove or change the type signature.
|
|
|
|
|
// See go.dev/issue/67401.
|
|
|
|
|
//
|
|
|
|
|
//go:linkname mapiternext
|
2014-07-16 14:16:19 -07:00
|
|
|
func mapiternext(it *hiter) {
|
|
|
|
|
h := it.h
|
|
|
|
|
if raceenabled {
|
2017-09-22 15:16:26 -04:00
|
|
|
callerpc := getcallerpc()
|
2021-05-21 13:37:19 -04:00
|
|
|
racereadpc(unsafe.Pointer(h), callerpc, abi.FuncPCABIInternal(mapiternext))
|
2014-07-16 14:16:19 -07:00
|
|
|
}
|
2016-07-06 15:02:49 -07:00
|
|
|
if h.flags&hashWriting != 0 {
|
2022-03-04 13:24:04 -05:00
|
|
|
fatal("concurrent map iteration and map write")
|
2016-07-06 15:02:49 -07:00
|
|
|
}
|
2014-07-16 14:16:19 -07:00
|
|
|
t := it.t
|
|
|
|
|
bucket := it.bucket
|
|
|
|
|
b := it.bptr
|
|
|
|
|
i := it.i
|
|
|
|
|
checkBucket := it.checkBucket
|
|
|
|
|
|
|
|
|
|
next:
|
|
|
|
|
if b == nil {
|
2014-09-08 17:42:21 -07:00
|
|
|
if bucket == it.startBucket && it.wrapped {
|
2014-07-16 14:16:19 -07:00
|
|
|
// end of iteration
|
|
|
|
|
it.key = nil
|
2019-04-22 13:37:08 -07:00
|
|
|
it.elem = nil
|
2014-07-16 14:16:19 -07:00
|
|
|
return
|
|
|
|
|
}
|
runtime: limit the number of map overflow buckets
Consider repeatedly adding many items to a map
and then deleting them all, as in #16070. The map
itself doesn't need to grow above the high water
mark of number of items. However, due to random
collisions, the map can accumulate overflow
buckets.
Prior to this CL, those overflow buckets were
never removed, which led to a slow memory leak.
The problem with removing overflow buckets is
iterators. The obvious approach is to repack
keys and values and eliminate unused overflow
buckets. However, keys, values, and overflow
buckets cannot be manipulated without disrupting
iterators.
This CL takes a different approach, which is to
reuse the existing map growth mechanism,
which is well established, well tested, and
safe in the presence of iterators.
When a map has accumulated enough overflow buckets
we trigger map growth, but grow into a map of the
same size as before. The old overflow buckets will
be left behind for garbage collection.
For the code in #16070, instead of climbing
(very slowly) forever, memory usage now cycles
between 264mb and 483mb every 15 minutes or so.
To avoid increasing the size of maps,
the overflow bucket counter is only 16 bits.
For large maps, the counter is incremented
stochastically.
Fixes #16070
Change-Id: If551d77613ec6836907efca58bda3deee304297e
Reviewed-on: https://go-review.googlesource.com/25049
Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2016-07-19 11:47:53 -07:00
|
|
|
if h.growing() && it.B == h.B {
|
2014-07-16 14:16:19 -07:00
|
|
|
// Iterator was started in the middle of a grow, and the grow isn't done yet.
|
|
|
|
|
// If the bucket we're looking at hasn't been filled in yet (i.e. the old
|
|
|
|
|
// bucket hasn't been evacuated) then we need to iterate through the old
|
|
|
|
|
// bucket and only return the ones that will be migrated to this bucket.
|
runtime: limit the number of map overflow buckets
Consider repeatedly adding many items to a map
and then deleting them all, as in #16070. The map
itself doesn't need to grow above the high water
mark of number of items. However, due to random
collisions, the map can accumulate overflow
buckets.
Prior to this CL, those overflow buckets were
never removed, which led to a slow memory leak.
The problem with removing overflow buckets is
iterators. The obvious approach is to repack
keys and values and eliminate unused overflow
buckets. However, keys, values, and overflow
buckets cannot be manipulated without disrupting
iterators.
This CL takes a different approach, which is to
reuse the existing map growth mechanism,
which is well established, well tested, and
safe in the presence of iterators.
When a map has accumulated enough overflow buckets
we trigger map growth, but grow into a map of the
same size as before. The old overflow buckets will
be left behind for garbage collection.
For the code in #16070, instead of climbing
(very slowly) forever, memory usage now cycles
between 264mb and 483mb every 15 minutes or so.
To avoid increasing the size of maps,
the overflow bucket counter is only 16 bits.
For large maps, the counter is incremented
stochastically.
Fixes #16070
Change-Id: If551d77613ec6836907efca58bda3deee304297e
Reviewed-on: https://go-review.googlesource.com/25049
Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2016-07-19 11:47:53 -07:00
|
|
|
oldbucket := bucket & it.h.oldbucketmask()
|
2023-04-25 19:14:05 -04:00
|
|
|
b = (*bmap)(add(h.oldbuckets, oldbucket*uintptr(t.BucketSize)))
|
2014-07-16 14:16:19 -07:00
|
|
|
if !evacuated(b) {
|
|
|
|
|
checkBucket = bucket
|
|
|
|
|
} else {
|
2023-04-25 19:14:05 -04:00
|
|
|
b = (*bmap)(add(it.buckets, bucket*uintptr(t.BucketSize)))
|
2014-07-16 14:16:19 -07:00
|
|
|
checkBucket = noCheck
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2023-04-25 19:14:05 -04:00
|
|
|
b = (*bmap)(add(it.buckets, bucket*uintptr(t.BucketSize)))
|
2014-07-16 14:16:19 -07:00
|
|
|
checkBucket = noCheck
|
|
|
|
|
}
|
|
|
|
|
bucket++
|
2017-08-20 11:47:50 -07:00
|
|
|
if bucket == bucketShift(it.B) {
|
2014-07-16 14:16:19 -07:00
|
|
|
bucket = 0
|
2014-09-08 17:42:21 -07:00
|
|
|
it.wrapped = true
|
2014-07-16 14:16:19 -07:00
|
|
|
}
|
|
|
|
|
i = 0
|
|
|
|
|
}
|
2024-01-31 03:51:34 +00:00
|
|
|
for ; i < abi.MapBucketCount; i++ {
|
|
|
|
|
offi := (i + it.offset) & (abi.MapBucketCount - 1)
|
2018-10-15 15:14:48 -07:00
|
|
|
if isEmpty(b.tophash[offi]) || b.tophash[offi] == evacuatedEmpty {
|
|
|
|
|
// TODO: emptyRest is hard to use here, as we start iterating
|
|
|
|
|
// in the middle of a bucket. It's feasible, just tricky.
|
2017-08-11 08:36:13 -07:00
|
|
|
continue
|
|
|
|
|
}
|
2023-04-25 19:14:05 -04:00
|
|
|
k := add(unsafe.Pointer(b), dataOffset+uintptr(offi)*uintptr(t.KeySize))
|
|
|
|
|
if t.IndirectKey() {
|
2017-08-11 08:43:30 -07:00
|
|
|
k = *((*unsafe.Pointer)(k))
|
|
|
|
|
}
|
2024-01-31 03:51:34 +00:00
|
|
|
e := add(unsafe.Pointer(b), dataOffset+abi.MapBucketCount*uintptr(t.KeySize)+uintptr(offi)*uintptr(t.ValueSize))
|
2017-08-11 08:36:13 -07:00
|
|
|
if checkBucket != noCheck && !h.sameSizeGrow() {
|
|
|
|
|
// Special case: iterator was started during a grow to a larger size
|
|
|
|
|
// and the grow is not done yet. We're working on a bucket whose
|
|
|
|
|
// oldbucket has not been evacuated yet. Or at least, it wasn't
|
|
|
|
|
// evacuated when we started the bucket. So we're iterating
|
|
|
|
|
// through the oldbucket, skipping any keys that will go
|
|
|
|
|
// to the other new bucket (each oldbucket expands to two
|
|
|
|
|
// buckets during a grow).
|
2023-04-25 19:14:05 -04:00
|
|
|
if t.ReflexiveKey() || t.Key.Equal(k, k) {
|
2017-08-11 08:36:13 -07:00
|
|
|
// If the item in the oldbucket is not destined for
|
|
|
|
|
// the current new bucket in the iteration, skip it.
|
2023-04-25 19:14:05 -04:00
|
|
|
hash := t.Hasher(k, uintptr(h.hash0))
|
2017-08-20 11:47:50 -07:00
|
|
|
if hash&bucketMask(it.B) != checkBucket {
|
2017-08-11 08:36:13 -07:00
|
|
|
continue
|
2014-07-16 14:16:19 -07:00
|
|
|
}
|
2017-08-11 08:36:13 -07:00
|
|
|
} else {
|
|
|
|
|
// Hash isn't repeatable if k != k (NaNs). We need a
|
|
|
|
|
// repeatable and randomish choice of which direction
|
|
|
|
|
// to send NaNs during evacuation. We'll use the low
|
|
|
|
|
// bit of tophash to decide which way NaNs go.
|
|
|
|
|
// NOTE: this case is why we need two evacuate tophash
|
|
|
|
|
// values, evacuatedX and evacuatedY, that differ in
|
|
|
|
|
// their low bit.
|
|
|
|
|
if checkBucket>>(it.B-1) != uintptr(b.tophash[offi]&1) {
|
|
|
|
|
continue
|
2014-07-16 14:16:19 -07:00
|
|
|
}
|
|
|
|
|
}
|
2017-08-11 08:36:13 -07:00
|
|
|
}
|
2017-08-11 08:48:10 -07:00
|
|
|
if (b.tophash[offi] != evacuatedX && b.tophash[offi] != evacuatedY) ||
|
2023-04-25 19:14:05 -04:00
|
|
|
!(t.ReflexiveKey() || t.Key.Equal(k, k)) {
|
2017-08-11 08:48:10 -07:00
|
|
|
// This is the golden data, we can return it.
|
|
|
|
|
// OR
|
|
|
|
|
// key!=key, so the entry can't be deleted or updated, so we can just return it.
|
|
|
|
|
// That's lucky for us because when key!=key we can't look it up successfully.
|
2017-08-11 08:36:13 -07:00
|
|
|
it.key = k
|
2023-04-25 19:14:05 -04:00
|
|
|
if t.IndirectElem() {
|
2019-04-22 13:37:08 -07:00
|
|
|
e = *((*unsafe.Pointer)(e))
|
2017-08-11 08:36:13 -07:00
|
|
|
}
|
2019-04-22 13:37:08 -07:00
|
|
|
it.elem = e
|
2017-08-11 08:36:13 -07:00
|
|
|
} else {
|
|
|
|
|
// The hash table has grown since the iterator was started.
|
|
|
|
|
// The golden data for this key is now somewhere else.
|
2017-08-11 08:48:10 -07:00
|
|
|
// Check the current hash table for the data.
|
|
|
|
|
// This code handles the case where the key
|
|
|
|
|
// has been deleted, updated, or deleted and reinserted.
|
|
|
|
|
// NOTE: we need to regrab the key as it has potentially been
|
|
|
|
|
// updated to an equal() but not identical key (e.g. +0.0 vs -0.0).
|
2019-04-22 13:37:08 -07:00
|
|
|
rk, re := mapaccessK(t, h, k)
|
2017-08-11 08:48:10 -07:00
|
|
|
if rk == nil {
|
|
|
|
|
continue // key has been deleted
|
2016-04-10 10:43:04 -07:00
|
|
|
}
|
2017-08-11 08:48:10 -07:00
|
|
|
it.key = rk
|
2019-04-22 13:37:08 -07:00
|
|
|
it.elem = re
|
2014-07-16 14:16:19 -07:00
|
|
|
}
|
2017-08-11 08:36:13 -07:00
|
|
|
it.bucket = bucket
|
|
|
|
|
if it.bptr != b { // avoid unnecessary write barrier; see issue 14921
|
|
|
|
|
it.bptr = b
|
|
|
|
|
}
|
|
|
|
|
it.i = i + 1
|
|
|
|
|
it.checkBucket = checkBucket
|
|
|
|
|
return
|
2014-07-16 14:16:19 -07:00
|
|
|
}
|
2014-12-19 20:44:18 -08:00
|
|
|
b = b.overflow(t)
|
2014-07-16 14:16:19 -07:00
|
|
|
i = 0
|
|
|
|
|
goto next
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-27 21:58:59 +02:00
|
|
|
// mapclear deletes all keys from a map.
|
2024-05-22 15:46:02 -04:00
|
|
|
// It is called by the compiler.
|
|
|
|
|
//
|
|
|
|
|
// mapclear should be an internal detail,
|
|
|
|
|
// but widely used packages access it using linkname.
|
|
|
|
|
// Notable members of the hall of shame include:
|
|
|
|
|
// - github.com/cloudwego/frugal
|
|
|
|
|
//
|
|
|
|
|
// Do not remove or change the type signature.
|
|
|
|
|
// See go.dev/issue/67401.
|
|
|
|
|
//
|
|
|
|
|
//go:linkname mapclear
|
2018-04-27 21:58:59 +02:00
|
|
|
func mapclear(t *maptype, h *hmap) {
|
|
|
|
|
if raceenabled && h != nil {
|
|
|
|
|
callerpc := getcallerpc()
|
2021-05-21 13:37:19 -04:00
|
|
|
pc := abi.FuncPCABIInternal(mapclear)
|
2018-04-27 21:58:59 +02:00
|
|
|
racewritepc(unsafe.Pointer(h), callerpc, pc)
|
2017-04-18 15:23:24 -07:00
|
|
|
}
|
2018-04-27 21:58:59 +02:00
|
|
|
|
|
|
|
|
if h == nil || h.count == 0 {
|
|
|
|
|
return
|
2017-04-18 15:23:24 -07:00
|
|
|
}
|
2018-04-27 21:58:59 +02:00
|
|
|
|
|
|
|
|
if h.flags&hashWriting != 0 {
|
2022-03-04 13:24:04 -05:00
|
|
|
fatal("concurrent map writes")
|
2018-04-27 21:58:59 +02:00
|
|
|
}
|
|
|
|
|
|
2018-07-31 11:24:37 -07:00
|
|
|
h.flags ^= hashWriting
|
2018-04-27 21:58:59 +02:00
|
|
|
|
2023-04-04 12:49:39 +07:00
|
|
|
// Mark buckets empty, so existing iterators can be terminated, see issue #59411.
|
|
|
|
|
markBucketsEmpty := func(bucket unsafe.Pointer, mask uintptr) {
|
|
|
|
|
for i := uintptr(0); i <= mask; i++ {
|
2023-04-25 19:14:05 -04:00
|
|
|
b := (*bmap)(add(bucket, i*uintptr(t.BucketSize)))
|
2023-04-04 12:49:39 +07:00
|
|
|
for ; b != nil; b = b.overflow(t) {
|
2024-01-31 03:51:34 +00:00
|
|
|
for i := uintptr(0); i < abi.MapBucketCount; i++ {
|
2023-04-04 12:49:39 +07:00
|
|
|
b.tophash[i] = emptyRest
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
markBucketsEmpty(h.buckets, bucketMask(h.B))
|
|
|
|
|
if oldBuckets := h.oldbuckets; oldBuckets != nil {
|
|
|
|
|
markBucketsEmpty(oldBuckets, h.oldbucketmask())
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-27 21:58:59 +02:00
|
|
|
h.flags &^= sameSizeGrow
|
|
|
|
|
h.oldbuckets = nil
|
|
|
|
|
h.nevacuate = 0
|
|
|
|
|
h.noverflow = 0
|
|
|
|
|
h.count = 0
|
|
|
|
|
|
2020-09-03 16:36:38 -04:00
|
|
|
// Reset the hash seed to make it more difficult for attackers to
|
|
|
|
|
// repeatedly trigger hash collisions. See issue 25237.
|
math/rand, math/rand/v2: use ChaCha8 for global rand
Move ChaCha8 code into internal/chacha8rand and use it to implement
runtime.rand, which is used for the unseeded global source for
both math/rand and math/rand/v2. This also affects the calculation of
the start point for iteration over very very large maps (when the
32-bit fastrand is not big enough).
The benefit is that misuse of the global random number generators
in math/rand and math/rand/v2 in contexts where non-predictable
randomness is important for security reasons is no longer a
security problem, removing a common mistake among programmers
who are unaware of the different kinds of randomness.
The cost is an extra 304 bytes per thread stored in the m struct
plus 2-3ns more per random uint64 due to the more sophisticated
algorithm. Using PCG looks like it would cost about the same,
although I haven't benchmarked that.
Before this, the math/rand and math/rand/v2 global generator
was wyrand (https://github.com/wangyi-fudan/wyhash).
For math/rand, using wyrand instead of the Mitchell/Reeds/Thompson
ALFG was justifiable, since the latter was not any better.
But for math/rand/v2, the global generator really should be
at least as good as one of the well-studied, specific algorithms
provided directly by the package, and it's not.
(Wyrand is still reasonable for scheduling and cache decisions.)
Good randomness does have a cost: about twice wyrand.
Also rationalize the various runtime rand references.
goos: linux
goarch: amd64
pkg: math/rand/v2
cpu: AMD Ryzen 9 7950X 16-Core Processor
│ bbb48afeb7.amd64 │ 5cf807d1ea.amd64 │
│ sec/op │ sec/op vs base │
ChaCha8-32 1.862n ± 2% 1.861n ± 2% ~ (p=0.825 n=20)
PCG_DXSM-32 1.471n ± 1% 1.460n ± 2% ~ (p=0.153 n=20)
SourceUint64-32 1.636n ± 2% 1.582n ± 1% -3.30% (p=0.000 n=20)
GlobalInt64-32 2.087n ± 1% 3.663n ± 1% +75.54% (p=0.000 n=20)
GlobalInt64Parallel-32 0.1042n ± 1% 0.2026n ± 1% +94.48% (p=0.000 n=20)
GlobalUint64-32 2.263n ± 2% 3.724n ± 1% +64.57% (p=0.000 n=20)
GlobalUint64Parallel-32 0.1019n ± 1% 0.1973n ± 1% +93.67% (p=0.000 n=20)
Int64-32 1.771n ± 1% 1.774n ± 1% ~ (p=0.449 n=20)
Uint64-32 1.863n ± 2% 1.866n ± 1% ~ (p=0.364 n=20)
GlobalIntN1000-32 3.134n ± 3% 4.730n ± 2% +50.95% (p=0.000 n=20)
IntN1000-32 2.489n ± 1% 2.489n ± 1% ~ (p=0.683 n=20)
Int64N1000-32 2.521n ± 1% 2.516n ± 1% ~ (p=0.394 n=20)
Int64N1e8-32 2.479n ± 1% 2.478n ± 2% ~ (p=0.743 n=20)
Int64N1e9-32 2.530n ± 2% 2.514n ± 2% ~ (p=0.193 n=20)
Int64N2e9-32 2.501n ± 1% 2.494n ± 1% ~ (p=0.616 n=20)
Int64N1e18-32 3.227n ± 1% 3.205n ± 1% ~ (p=0.101 n=20)
Int64N2e18-32 3.647n ± 1% 3.599n ± 1% ~ (p=0.019 n=20)
Int64N4e18-32 5.135n ± 1% 5.069n ± 2% ~ (p=0.034 n=20)
Int32N1000-32 2.657n ± 1% 2.637n ± 1% ~ (p=0.180 n=20)
Int32N1e8-32 2.636n ± 1% 2.636n ± 1% ~ (p=0.763 n=20)
Int32N1e9-32 2.660n ± 2% 2.638n ± 1% ~ (p=0.358 n=20)
Int32N2e9-32 2.662n ± 2% 2.618n ± 2% ~ (p=0.064 n=20)
Float32-32 2.272n ± 2% 2.239n ± 2% ~ (p=0.194 n=20)
Float64-32 2.272n ± 1% 2.286n ± 2% ~ (p=0.763 n=20)
ExpFloat64-32 3.762n ± 1% 3.744n ± 1% ~ (p=0.171 n=20)
NormFloat64-32 3.706n ± 1% 3.655n ± 2% ~ (p=0.066 n=20)
Perm3-32 32.93n ± 3% 34.62n ± 1% +5.13% (p=0.000 n=20)
Perm30-32 202.9n ± 1% 204.0n ± 1% ~ (p=0.482 n=20)
Perm30ViaShuffle-32 115.0n ± 1% 114.9n ± 1% ~ (p=0.358 n=20)
ShuffleOverhead-32 112.8n ± 1% 112.7n ± 1% ~ (p=0.692 n=20)
Concurrent-32 2.107n ± 0% 3.725n ± 1% +76.75% (p=0.000 n=20)
goos: darwin
goarch: arm64
pkg: math/rand/v2
│ bbb48afeb7.arm64 │ 5cf807d1ea.arm64 │
│ sec/op │ sec/op vs base │
ChaCha8-8 2.480n ± 0% 2.429n ± 0% -2.04% (p=0.000 n=20)
PCG_DXSM-8 2.531n ± 0% 2.530n ± 0% ~ (p=0.877 n=20)
SourceUint64-8 2.534n ± 0% 2.533n ± 0% ~ (p=0.732 n=20)
GlobalInt64-8 2.172n ± 1% 4.794n ± 0% +120.67% (p=0.000 n=20)
GlobalInt64Parallel-8 0.4320n ± 0% 0.9605n ± 0% +122.32% (p=0.000 n=20)
GlobalUint64-8 2.182n ± 0% 4.770n ± 0% +118.58% (p=0.000 n=20)
GlobalUint64Parallel-8 0.4307n ± 0% 0.9583n ± 0% +122.51% (p=0.000 n=20)
Int64-8 4.107n ± 0% 4.104n ± 0% ~ (p=0.416 n=20)
Uint64-8 4.080n ± 0% 4.080n ± 0% ~ (p=0.052 n=20)
GlobalIntN1000-8 2.814n ± 2% 5.643n ± 0% +100.50% (p=0.000 n=20)
IntN1000-8 4.141n ± 0% 4.139n ± 0% ~ (p=0.140 n=20)
Int64N1000-8 4.140n ± 0% 4.140n ± 0% ~ (p=0.313 n=20)
Int64N1e8-8 4.140n ± 0% 4.139n ± 0% ~ (p=0.103 n=20)
Int64N1e9-8 4.139n ± 0% 4.140n ± 0% ~ (p=0.761 n=20)
Int64N2e9-8 4.140n ± 0% 4.140n ± 0% ~ (p=0.636 n=20)
Int64N1e18-8 5.266n ± 0% 5.326n ± 1% +1.14% (p=0.001 n=20)
Int64N2e18-8 6.052n ± 0% 6.167n ± 0% +1.90% (p=0.000 n=20)
Int64N4e18-8 8.826n ± 0% 9.051n ± 0% +2.55% (p=0.000 n=20)
Int32N1000-8 4.127n ± 0% 4.132n ± 0% +0.12% (p=0.000 n=20)
Int32N1e8-8 4.126n ± 0% 4.131n ± 0% +0.12% (p=0.000 n=20)
Int32N1e9-8 4.127n ± 0% 4.132n ± 0% +0.12% (p=0.000 n=20)
Int32N2e9-8 4.132n ± 0% 4.131n ± 0% ~ (p=0.017 n=20)
Float32-8 4.109n ± 0% 4.105n ± 0% ~ (p=0.379 n=20)
Float64-8 4.107n ± 0% 4.106n ± 0% ~ (p=0.867 n=20)
ExpFloat64-8 5.339n ± 0% 5.383n ± 0% +0.82% (p=0.000 n=20)
NormFloat64-8 5.735n ± 0% 5.737n ± 1% ~ (p=0.856 n=20)
Perm3-8 26.65n ± 0% 26.80n ± 1% +0.58% (p=0.000 n=20)
Perm30-8 194.8n ± 1% 197.0n ± 0% +1.18% (p=0.000 n=20)
Perm30ViaShuffle-8 156.6n ± 0% 157.6n ± 1% +0.61% (p=0.000 n=20)
ShuffleOverhead-8 124.9n ± 0% 125.5n ± 0% +0.52% (p=0.000 n=20)
Concurrent-8 2.434n ± 3% 5.066n ± 0% +108.09% (p=0.000 n=20)
goos: linux
goarch: 386
pkg: math/rand/v2
cpu: AMD Ryzen 9 7950X 16-Core Processor
│ bbb48afeb7.386 │ 5cf807d1ea.386 │
│ sec/op │ sec/op vs base │
ChaCha8-32 11.295n ± 1% 4.748n ± 2% -57.96% (p=0.000 n=20)
PCG_DXSM-32 7.693n ± 1% 7.738n ± 2% ~ (p=0.542 n=20)
SourceUint64-32 7.658n ± 2% 7.622n ± 2% ~ (p=0.344 n=20)
GlobalInt64-32 3.473n ± 2% 7.526n ± 2% +116.73% (p=0.000 n=20)
GlobalInt64Parallel-32 0.3198n ± 0% 0.5444n ± 0% +70.22% (p=0.000 n=20)
GlobalUint64-32 3.612n ± 0% 7.575n ± 1% +109.69% (p=0.000 n=20)
GlobalUint64Parallel-32 0.3168n ± 0% 0.5403n ± 0% +70.51% (p=0.000 n=20)
Int64-32 7.673n ± 2% 7.789n ± 1% ~ (p=0.122 n=20)
Uint64-32 7.773n ± 1% 7.827n ± 2% ~ (p=0.920 n=20)
GlobalIntN1000-32 6.268n ± 1% 9.581n ± 1% +52.87% (p=0.000 n=20)
IntN1000-32 10.33n ± 2% 10.45n ± 1% ~ (p=0.233 n=20)
Int64N1000-32 10.98n ± 2% 11.01n ± 1% ~ (p=0.401 n=20)
Int64N1e8-32 11.19n ± 2% 10.97n ± 1% ~ (p=0.033 n=20)
Int64N1e9-32 11.06n ± 1% 11.08n ± 1% ~ (p=0.498 n=20)
Int64N2e9-32 11.10n ± 1% 11.01n ± 2% ~ (p=0.995 n=20)
Int64N1e18-32 15.23n ± 2% 15.04n ± 1% ~ (p=0.973 n=20)
Int64N2e18-32 15.89n ± 1% 15.85n ± 1% ~ (p=0.409 n=20)
Int64N4e18-32 18.96n ± 2% 19.34n ± 2% ~ (p=0.048 n=20)
Int32N1000-32 10.46n ± 2% 10.44n ± 2% ~ (p=0.480 n=20)
Int32N1e8-32 10.46n ± 2% 10.49n ± 2% ~ (p=0.951 n=20)
Int32N1e9-32 10.28n ± 2% 10.26n ± 1% ~ (p=0.431 n=20)
Int32N2e9-32 10.50n ± 2% 10.44n ± 2% ~ (p=0.249 n=20)
Float32-32 13.80n ± 2% 13.80n ± 2% ~ (p=0.751 n=20)
Float64-32 23.55n ± 2% 23.87n ± 0% ~ (p=0.408 n=20)
ExpFloat64-32 15.36n ± 1% 15.29n ± 2% ~ (p=0.316 n=20)
NormFloat64-32 13.57n ± 1% 13.79n ± 1% +1.66% (p=0.005 n=20)
Perm3-32 45.70n ± 2% 46.99n ± 2% +2.81% (p=0.001 n=20)
Perm30-32 399.0n ± 1% 403.8n ± 1% +1.19% (p=0.006 n=20)
Perm30ViaShuffle-32 349.0n ± 1% 350.4n ± 1% ~ (p=0.909 n=20)
ShuffleOverhead-32 322.3n ± 1% 323.8n ± 1% ~ (p=0.410 n=20)
Concurrent-32 3.331n ± 1% 7.312n ± 1% +119.50% (p=0.000 n=20)
For #61716.
Change-Id: Ibdddeed85c34d9ae397289dc899e04d4845f9ed2
Reviewed-on: https://go-review.googlesource.com/c/go/+/516860
Reviewed-by: Michael Pratt <mpratt@google.com>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
2023-08-06 13:26:28 +10:00
|
|
|
h.hash0 = uint32(rand())
|
2020-09-03 16:36:38 -04:00
|
|
|
|
2018-04-27 21:58:59 +02:00
|
|
|
// Keep the mapextra allocation but clear any extra information.
|
|
|
|
|
if h.extra != nil {
|
|
|
|
|
*h.extra = mapextra{}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// makeBucketArray clears the memory pointed to by h.buckets
|
|
|
|
|
// and recovers any overflow buckets by generating them
|
|
|
|
|
// as if h.buckets was newly alloced.
|
|
|
|
|
_, nextOverflow := makeBucketArray(t, h.B, h.buckets)
|
|
|
|
|
if nextOverflow != nil {
|
|
|
|
|
// If overflow buckets are created then h.extra
|
|
|
|
|
// will have been allocated during initial bucket creation.
|
|
|
|
|
h.extra.nextOverflow = nextOverflow
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if h.flags&hashWriting == 0 {
|
2022-03-04 13:24:04 -05:00
|
|
|
fatal("concurrent map writes")
|
2018-04-27 21:58:59 +02:00
|
|
|
}
|
|
|
|
|
h.flags &^= hashWriting
|
2017-04-18 15:23:24 -07:00
|
|
|
}
|
|
|
|
|
|
2014-07-16 14:16:19 -07:00
|
|
|
func hashGrow(t *maptype, h *hmap) {
|
runtime: limit the number of map overflow buckets
Consider repeatedly adding many items to a map
and then deleting them all, as in #16070. The map
itself doesn't need to grow above the high water
mark of number of items. However, due to random
collisions, the map can accumulate overflow
buckets.
Prior to this CL, those overflow buckets were
never removed, which led to a slow memory leak.
The problem with removing overflow buckets is
iterators. The obvious approach is to repack
keys and values and eliminate unused overflow
buckets. However, keys, values, and overflow
buckets cannot be manipulated without disrupting
iterators.
This CL takes a different approach, which is to
reuse the existing map growth mechanism,
which is well established, well tested, and
safe in the presence of iterators.
When a map has accumulated enough overflow buckets
we trigger map growth, but grow into a map of the
same size as before. The old overflow buckets will
be left behind for garbage collection.
For the code in #16070, instead of climbing
(very slowly) forever, memory usage now cycles
between 264mb and 483mb every 15 minutes or so.
To avoid increasing the size of maps,
the overflow bucket counter is only 16 bits.
For large maps, the counter is incremented
stochastically.
Fixes #16070
Change-Id: If551d77613ec6836907efca58bda3deee304297e
Reviewed-on: https://go-review.googlesource.com/25049
Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2016-07-19 11:47:53 -07:00
|
|
|
// If we've hit the load factor, get bigger.
|
|
|
|
|
// Otherwise, there are too many overflow buckets,
|
|
|
|
|
// so keep the same number of buckets and "grow" laterally.
|
|
|
|
|
bigger := uint8(1)
|
2017-09-01 12:32:38 -07:00
|
|
|
if !overLoadFactor(h.count+1, h.B) {
|
runtime: limit the number of map overflow buckets
Consider repeatedly adding many items to a map
and then deleting them all, as in #16070. The map
itself doesn't need to grow above the high water
mark of number of items. However, due to random
collisions, the map can accumulate overflow
buckets.
Prior to this CL, those overflow buckets were
never removed, which led to a slow memory leak.
The problem with removing overflow buckets is
iterators. The obvious approach is to repack
keys and values and eliminate unused overflow
buckets. However, keys, values, and overflow
buckets cannot be manipulated without disrupting
iterators.
This CL takes a different approach, which is to
reuse the existing map growth mechanism,
which is well established, well tested, and
safe in the presence of iterators.
When a map has accumulated enough overflow buckets
we trigger map growth, but grow into a map of the
same size as before. The old overflow buckets will
be left behind for garbage collection.
For the code in #16070, instead of climbing
(very slowly) forever, memory usage now cycles
between 264mb and 483mb every 15 minutes or so.
To avoid increasing the size of maps,
the overflow bucket counter is only 16 bits.
For large maps, the counter is incremented
stochastically.
Fixes #16070
Change-Id: If551d77613ec6836907efca58bda3deee304297e
Reviewed-on: https://go-review.googlesource.com/25049
Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2016-07-19 11:47:53 -07:00
|
|
|
bigger = 0
|
|
|
|
|
h.flags |= sameSizeGrow
|
2014-07-16 14:16:19 -07:00
|
|
|
}
|
|
|
|
|
oldbuckets := h.buckets
|
2018-04-27 21:58:59 +02:00
|
|
|
newbuckets, nextOverflow := makeBucketArray(t, h.B+bigger, nil)
|
2017-04-18 15:23:24 -07:00
|
|
|
|
2014-07-16 14:16:19 -07:00
|
|
|
flags := h.flags &^ (iterator | oldIterator)
|
|
|
|
|
if h.flags&iterator != 0 {
|
|
|
|
|
flags |= oldIterator
|
|
|
|
|
}
|
|
|
|
|
// commit the grow (atomic wrt gc)
|
runtime: limit the number of map overflow buckets
Consider repeatedly adding many items to a map
and then deleting them all, as in #16070. The map
itself doesn't need to grow above the high water
mark of number of items. However, due to random
collisions, the map can accumulate overflow
buckets.
Prior to this CL, those overflow buckets were
never removed, which led to a slow memory leak.
The problem with removing overflow buckets is
iterators. The obvious approach is to repack
keys and values and eliminate unused overflow
buckets. However, keys, values, and overflow
buckets cannot be manipulated without disrupting
iterators.
This CL takes a different approach, which is to
reuse the existing map growth mechanism,
which is well established, well tested, and
safe in the presence of iterators.
When a map has accumulated enough overflow buckets
we trigger map growth, but grow into a map of the
same size as before. The old overflow buckets will
be left behind for garbage collection.
For the code in #16070, instead of climbing
(very slowly) forever, memory usage now cycles
between 264mb and 483mb every 15 minutes or so.
To avoid increasing the size of maps,
the overflow bucket counter is only 16 bits.
For large maps, the counter is incremented
stochastically.
Fixes #16070
Change-Id: If551d77613ec6836907efca58bda3deee304297e
Reviewed-on: https://go-review.googlesource.com/25049
Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2016-07-19 11:47:53 -07:00
|
|
|
h.B += bigger
|
2014-07-16 14:16:19 -07:00
|
|
|
h.flags = flags
|
|
|
|
|
h.oldbuckets = oldbuckets
|
|
|
|
|
h.buckets = newbuckets
|
|
|
|
|
h.nevacuate = 0
|
runtime: limit the number of map overflow buckets
Consider repeatedly adding many items to a map
and then deleting them all, as in #16070. The map
itself doesn't need to grow above the high water
mark of number of items. However, due to random
collisions, the map can accumulate overflow
buckets.
Prior to this CL, those overflow buckets were
never removed, which led to a slow memory leak.
The problem with removing overflow buckets is
iterators. The obvious approach is to repack
keys and values and eliminate unused overflow
buckets. However, keys, values, and overflow
buckets cannot be manipulated without disrupting
iterators.
This CL takes a different approach, which is to
reuse the existing map growth mechanism,
which is well established, well tested, and
safe in the presence of iterators.
When a map has accumulated enough overflow buckets
we trigger map growth, but grow into a map of the
same size as before. The old overflow buckets will
be left behind for garbage collection.
For the code in #16070, instead of climbing
(very slowly) forever, memory usage now cycles
between 264mb and 483mb every 15 minutes or so.
To avoid increasing the size of maps,
the overflow bucket counter is only 16 bits.
For large maps, the counter is incremented
stochastically.
Fixes #16070
Change-Id: If551d77613ec6836907efca58bda3deee304297e
Reviewed-on: https://go-review.googlesource.com/25049
Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2016-07-19 11:47:53 -07:00
|
|
|
h.noverflow = 0
|
2014-07-16 14:16:19 -07:00
|
|
|
|
2017-09-10 12:55:16 +02:00
|
|
|
if h.extra != nil && h.extra.overflow != nil {
|
2015-01-26 21:04:41 +03:00
|
|
|
// Promote current overflow buckets to the old generation.
|
2017-09-10 12:55:16 +02:00
|
|
|
if h.extra.oldoverflow != nil {
|
|
|
|
|
throw("oldoverflow is not nil")
|
2015-01-26 21:04:41 +03:00
|
|
|
}
|
2017-09-10 12:55:16 +02:00
|
|
|
h.extra.oldoverflow = h.extra.overflow
|
|
|
|
|
h.extra.overflow = nil
|
2015-01-26 21:04:41 +03:00
|
|
|
}
|
2017-04-18 15:23:24 -07:00
|
|
|
if nextOverflow != nil {
|
|
|
|
|
if h.extra == nil {
|
|
|
|
|
h.extra = new(mapextra)
|
|
|
|
|
}
|
|
|
|
|
h.extra.nextOverflow = nextOverflow
|
|
|
|
|
}
|
2015-01-26 21:04:41 +03:00
|
|
|
|
2014-07-16 14:16:19 -07:00
|
|
|
// the actual copying of the hash table data is done incrementally
|
|
|
|
|
// by growWork() and evacuate().
|
|
|
|
|
}
|
|
|
|
|
|
runtime: limit the number of map overflow buckets
Consider repeatedly adding many items to a map
and then deleting them all, as in #16070. The map
itself doesn't need to grow above the high water
mark of number of items. However, due to random
collisions, the map can accumulate overflow
buckets.
Prior to this CL, those overflow buckets were
never removed, which led to a slow memory leak.
The problem with removing overflow buckets is
iterators. The obvious approach is to repack
keys and values and eliminate unused overflow
buckets. However, keys, values, and overflow
buckets cannot be manipulated without disrupting
iterators.
This CL takes a different approach, which is to
reuse the existing map growth mechanism,
which is well established, well tested, and
safe in the presence of iterators.
When a map has accumulated enough overflow buckets
we trigger map growth, but grow into a map of the
same size as before. The old overflow buckets will
be left behind for garbage collection.
For the code in #16070, instead of climbing
(very slowly) forever, memory usage now cycles
between 264mb and 483mb every 15 minutes or so.
To avoid increasing the size of maps,
the overflow bucket counter is only 16 bits.
For large maps, the counter is incremented
stochastically.
Fixes #16070
Change-Id: If551d77613ec6836907efca58bda3deee304297e
Reviewed-on: https://go-review.googlesource.com/25049
Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2016-07-19 11:47:53 -07:00
|
|
|
// overLoadFactor reports whether count items placed in 1<<B buckets is over loadFactor.
|
2017-08-14 10:16:21 +02:00
|
|
|
func overLoadFactor(count int, B uint8) bool {
|
2024-01-31 03:51:34 +00:00
|
|
|
return count > abi.MapBucketCount && uintptr(count) > loadFactorNum*(bucketShift(B)/loadFactorDen)
|
runtime: limit the number of map overflow buckets
Consider repeatedly adding many items to a map
and then deleting them all, as in #16070. The map
itself doesn't need to grow above the high water
mark of number of items. However, due to random
collisions, the map can accumulate overflow
buckets.
Prior to this CL, those overflow buckets were
never removed, which led to a slow memory leak.
The problem with removing overflow buckets is
iterators. The obvious approach is to repack
keys and values and eliminate unused overflow
buckets. However, keys, values, and overflow
buckets cannot be manipulated without disrupting
iterators.
This CL takes a different approach, which is to
reuse the existing map growth mechanism,
which is well established, well tested, and
safe in the presence of iterators.
When a map has accumulated enough overflow buckets
we trigger map growth, but grow into a map of the
same size as before. The old overflow buckets will
be left behind for garbage collection.
For the code in #16070, instead of climbing
(very slowly) forever, memory usage now cycles
between 264mb and 483mb every 15 minutes or so.
To avoid increasing the size of maps,
the overflow bucket counter is only 16 bits.
For large maps, the counter is incremented
stochastically.
Fixes #16070
Change-Id: If551d77613ec6836907efca58bda3deee304297e
Reviewed-on: https://go-review.googlesource.com/25049
Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2016-07-19 11:47:53 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// tooManyOverflowBuckets reports whether noverflow buckets is too many for a map with 1<<B buckets.
|
|
|
|
|
// Note that most of these overflow buckets must be in sparse use;
|
|
|
|
|
// if use was dense, then we'd have already triggered regular map growth.
|
|
|
|
|
func tooManyOverflowBuckets(noverflow uint16, B uint8) bool {
|
|
|
|
|
// If the threshold is too low, we do extraneous work.
|
|
|
|
|
// If the threshold is too high, maps that grow and shrink can hold on to lots of unused memory.
|
|
|
|
|
// "too many" means (approximately) as many overflow buckets as regular buckets.
|
|
|
|
|
// See incrnoverflow for more details.
|
2017-08-10 08:11:46 -07:00
|
|
|
if B > 15 {
|
|
|
|
|
B = 15
|
runtime: limit the number of map overflow buckets
Consider repeatedly adding many items to a map
and then deleting them all, as in #16070. The map
itself doesn't need to grow above the high water
mark of number of items. However, due to random
collisions, the map can accumulate overflow
buckets.
Prior to this CL, those overflow buckets were
never removed, which led to a slow memory leak.
The problem with removing overflow buckets is
iterators. The obvious approach is to repack
keys and values and eliminate unused overflow
buckets. However, keys, values, and overflow
buckets cannot be manipulated without disrupting
iterators.
This CL takes a different approach, which is to
reuse the existing map growth mechanism,
which is well established, well tested, and
safe in the presence of iterators.
When a map has accumulated enough overflow buckets
we trigger map growth, but grow into a map of the
same size as before. The old overflow buckets will
be left behind for garbage collection.
For the code in #16070, instead of climbing
(very slowly) forever, memory usage now cycles
between 264mb and 483mb every 15 minutes or so.
To avoid increasing the size of maps,
the overflow bucket counter is only 16 bits.
For large maps, the counter is incremented
stochastically.
Fixes #16070
Change-Id: If551d77613ec6836907efca58bda3deee304297e
Reviewed-on: https://go-review.googlesource.com/25049
Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2016-07-19 11:47:53 -07:00
|
|
|
}
|
2017-08-10 08:11:46 -07:00
|
|
|
// The compiler doesn't see here that B < 16; mask B to generate shorter shift code.
|
|
|
|
|
return noverflow >= uint16(1)<<(B&15)
|
runtime: limit the number of map overflow buckets
Consider repeatedly adding many items to a map
and then deleting them all, as in #16070. The map
itself doesn't need to grow above the high water
mark of number of items. However, due to random
collisions, the map can accumulate overflow
buckets.
Prior to this CL, those overflow buckets were
never removed, which led to a slow memory leak.
The problem with removing overflow buckets is
iterators. The obvious approach is to repack
keys and values and eliminate unused overflow
buckets. However, keys, values, and overflow
buckets cannot be manipulated without disrupting
iterators.
This CL takes a different approach, which is to
reuse the existing map growth mechanism,
which is well established, well tested, and
safe in the presence of iterators.
When a map has accumulated enough overflow buckets
we trigger map growth, but grow into a map of the
same size as before. The old overflow buckets will
be left behind for garbage collection.
For the code in #16070, instead of climbing
(very slowly) forever, memory usage now cycles
between 264mb and 483mb every 15 minutes or so.
To avoid increasing the size of maps,
the overflow bucket counter is only 16 bits.
For large maps, the counter is incremented
stochastically.
Fixes #16070
Change-Id: If551d77613ec6836907efca58bda3deee304297e
Reviewed-on: https://go-review.googlesource.com/25049
Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2016-07-19 11:47:53 -07:00
|
|
|
}
|
2014-07-16 14:16:19 -07:00
|
|
|
|
runtime: limit the number of map overflow buckets
Consider repeatedly adding many items to a map
and then deleting them all, as in #16070. The map
itself doesn't need to grow above the high water
mark of number of items. However, due to random
collisions, the map can accumulate overflow
buckets.
Prior to this CL, those overflow buckets were
never removed, which led to a slow memory leak.
The problem with removing overflow buckets is
iterators. The obvious approach is to repack
keys and values and eliminate unused overflow
buckets. However, keys, values, and overflow
buckets cannot be manipulated without disrupting
iterators.
This CL takes a different approach, which is to
reuse the existing map growth mechanism,
which is well established, well tested, and
safe in the presence of iterators.
When a map has accumulated enough overflow buckets
we trigger map growth, but grow into a map of the
same size as before. The old overflow buckets will
be left behind for garbage collection.
For the code in #16070, instead of climbing
(very slowly) forever, memory usage now cycles
between 264mb and 483mb every 15 minutes or so.
To avoid increasing the size of maps,
the overflow bucket counter is only 16 bits.
For large maps, the counter is incremented
stochastically.
Fixes #16070
Change-Id: If551d77613ec6836907efca58bda3deee304297e
Reviewed-on: https://go-review.googlesource.com/25049
Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2016-07-19 11:47:53 -07:00
|
|
|
// growing reports whether h is growing. The growth may be to the same size or bigger.
|
|
|
|
|
func (h *hmap) growing() bool {
|
|
|
|
|
return h.oldbuckets != nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// sameSizeGrow reports whether the current growth is to a map of the same size.
|
|
|
|
|
func (h *hmap) sameSizeGrow() bool {
|
|
|
|
|
return h.flags&sameSizeGrow != 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// noldbuckets calculates the number of buckets prior to the current map growth.
|
|
|
|
|
func (h *hmap) noldbuckets() uintptr {
|
|
|
|
|
oldB := h.B
|
|
|
|
|
if !h.sameSizeGrow() {
|
|
|
|
|
oldB--
|
|
|
|
|
}
|
2017-08-20 11:47:50 -07:00
|
|
|
return bucketShift(oldB)
|
runtime: limit the number of map overflow buckets
Consider repeatedly adding many items to a map
and then deleting them all, as in #16070. The map
itself doesn't need to grow above the high water
mark of number of items. However, due to random
collisions, the map can accumulate overflow
buckets.
Prior to this CL, those overflow buckets were
never removed, which led to a slow memory leak.
The problem with removing overflow buckets is
iterators. The obvious approach is to repack
keys and values and eliminate unused overflow
buckets. However, keys, values, and overflow
buckets cannot be manipulated without disrupting
iterators.
This CL takes a different approach, which is to
reuse the existing map growth mechanism,
which is well established, well tested, and
safe in the presence of iterators.
When a map has accumulated enough overflow buckets
we trigger map growth, but grow into a map of the
same size as before. The old overflow buckets will
be left behind for garbage collection.
For the code in #16070, instead of climbing
(very slowly) forever, memory usage now cycles
between 264mb and 483mb every 15 minutes or so.
To avoid increasing the size of maps,
the overflow bucket counter is only 16 bits.
For large maps, the counter is incremented
stochastically.
Fixes #16070
Change-Id: If551d77613ec6836907efca58bda3deee304297e
Reviewed-on: https://go-review.googlesource.com/25049
Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2016-07-19 11:47:53 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// oldbucketmask provides a mask that can be applied to calculate n % noldbuckets().
|
|
|
|
|
func (h *hmap) oldbucketmask() uintptr {
|
|
|
|
|
return h.noldbuckets() - 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func growWork(t *maptype, h *hmap, bucket uintptr) {
|
2014-07-16 14:16:19 -07:00
|
|
|
// make sure we evacuate the oldbucket corresponding
|
|
|
|
|
// to the bucket we're about to use
|
runtime: limit the number of map overflow buckets
Consider repeatedly adding many items to a map
and then deleting them all, as in #16070. The map
itself doesn't need to grow above the high water
mark of number of items. However, due to random
collisions, the map can accumulate overflow
buckets.
Prior to this CL, those overflow buckets were
never removed, which led to a slow memory leak.
The problem with removing overflow buckets is
iterators. The obvious approach is to repack
keys and values and eliminate unused overflow
buckets. However, keys, values, and overflow
buckets cannot be manipulated without disrupting
iterators.
This CL takes a different approach, which is to
reuse the existing map growth mechanism,
which is well established, well tested, and
safe in the presence of iterators.
When a map has accumulated enough overflow buckets
we trigger map growth, but grow into a map of the
same size as before. The old overflow buckets will
be left behind for garbage collection.
For the code in #16070, instead of climbing
(very slowly) forever, memory usage now cycles
between 264mb and 483mb every 15 minutes or so.
To avoid increasing the size of maps,
the overflow bucket counter is only 16 bits.
For large maps, the counter is incremented
stochastically.
Fixes #16070
Change-Id: If551d77613ec6836907efca58bda3deee304297e
Reviewed-on: https://go-review.googlesource.com/25049
Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2016-07-19 11:47:53 -07:00
|
|
|
evacuate(t, h, bucket&h.oldbucketmask())
|
2014-07-16 14:16:19 -07:00
|
|
|
|
|
|
|
|
// evacuate one more oldbucket to make progress on growing
|
runtime: limit the number of map overflow buckets
Consider repeatedly adding many items to a map
and then deleting them all, as in #16070. The map
itself doesn't need to grow above the high water
mark of number of items. However, due to random
collisions, the map can accumulate overflow
buckets.
Prior to this CL, those overflow buckets were
never removed, which led to a slow memory leak.
The problem with removing overflow buckets is
iterators. The obvious approach is to repack
keys and values and eliminate unused overflow
buckets. However, keys, values, and overflow
buckets cannot be manipulated without disrupting
iterators.
This CL takes a different approach, which is to
reuse the existing map growth mechanism,
which is well established, well tested, and
safe in the presence of iterators.
When a map has accumulated enough overflow buckets
we trigger map growth, but grow into a map of the
same size as before. The old overflow buckets will
be left behind for garbage collection.
For the code in #16070, instead of climbing
(very slowly) forever, memory usage now cycles
between 264mb and 483mb every 15 minutes or so.
To avoid increasing the size of maps,
the overflow bucket counter is only 16 bits.
For large maps, the counter is incremented
stochastically.
Fixes #16070
Change-Id: If551d77613ec6836907efca58bda3deee304297e
Reviewed-on: https://go-review.googlesource.com/25049
Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2016-07-19 11:47:53 -07:00
|
|
|
if h.growing() {
|
2014-07-16 14:16:19 -07:00
|
|
|
evacuate(t, h, h.nevacuate)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-14 11:00:34 -08:00
|
|
|
func bucketEvacuated(t *maptype, h *hmap, bucket uintptr) bool {
|
2023-04-25 19:14:05 -04:00
|
|
|
b := (*bmap)(add(h.oldbuckets, bucket*uintptr(t.BucketSize)))
|
2017-02-14 11:00:34 -08:00
|
|
|
return evacuated(b)
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-17 18:25:54 -07:00
|
|
|
// evacDst is an evacuation destination.
|
|
|
|
|
type evacDst struct {
|
|
|
|
|
b *bmap // current destination bucket
|
2019-04-22 13:37:08 -07:00
|
|
|
i int // key/elem index into b
|
2017-08-17 18:25:54 -07:00
|
|
|
k unsafe.Pointer // pointer to current key storage
|
2019-04-22 13:37:08 -07:00
|
|
|
e unsafe.Pointer // pointer to current elem storage
|
2017-08-17 18:25:54 -07:00
|
|
|
}
|
|
|
|
|
|
2014-07-16 14:16:19 -07:00
|
|
|
func evacuate(t *maptype, h *hmap, oldbucket uintptr) {
|
2023-04-25 19:14:05 -04:00
|
|
|
b := (*bmap)(add(h.oldbuckets, oldbucket*uintptr(t.BucketSize)))
|
runtime: limit the number of map overflow buckets
Consider repeatedly adding many items to a map
and then deleting them all, as in #16070. The map
itself doesn't need to grow above the high water
mark of number of items. However, due to random
collisions, the map can accumulate overflow
buckets.
Prior to this CL, those overflow buckets were
never removed, which led to a slow memory leak.
The problem with removing overflow buckets is
iterators. The obvious approach is to repack
keys and values and eliminate unused overflow
buckets. However, keys, values, and overflow
buckets cannot be manipulated without disrupting
iterators.
This CL takes a different approach, which is to
reuse the existing map growth mechanism,
which is well established, well tested, and
safe in the presence of iterators.
When a map has accumulated enough overflow buckets
we trigger map growth, but grow into a map of the
same size as before. The old overflow buckets will
be left behind for garbage collection.
For the code in #16070, instead of climbing
(very slowly) forever, memory usage now cycles
between 264mb and 483mb every 15 minutes or so.
To avoid increasing the size of maps,
the overflow bucket counter is only 16 bits.
For large maps, the counter is incremented
stochastically.
Fixes #16070
Change-Id: If551d77613ec6836907efca58bda3deee304297e
Reviewed-on: https://go-review.googlesource.com/25049
Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2016-07-19 11:47:53 -07:00
|
|
|
newbit := h.noldbuckets()
|
2014-07-16 14:16:19 -07:00
|
|
|
if !evacuated(b) {
|
|
|
|
|
// TODO: reuse overflow buckets instead of using new ones, if there
|
|
|
|
|
// is no iterator using the old buckets. (If !oldIterator.)
|
|
|
|
|
|
2017-08-10 07:16:27 -07:00
|
|
|
// xy contains the x and y (low and high) evacuation destinations.
|
|
|
|
|
var xy [2]evacDst
|
|
|
|
|
x := &xy[0]
|
2023-04-25 19:14:05 -04:00
|
|
|
x.b = (*bmap)(add(h.buckets, oldbucket*uintptr(t.BucketSize)))
|
2017-08-10 07:16:27 -07:00
|
|
|
x.k = add(unsafe.Pointer(x.b), dataOffset)
|
2024-01-31 03:51:34 +00:00
|
|
|
x.e = add(x.k, abi.MapBucketCount*uintptr(t.KeySize))
|
2017-08-10 07:16:27 -07:00
|
|
|
|
runtime: limit the number of map overflow buckets
Consider repeatedly adding many items to a map
and then deleting them all, as in #16070. The map
itself doesn't need to grow above the high water
mark of number of items. However, due to random
collisions, the map can accumulate overflow
buckets.
Prior to this CL, those overflow buckets were
never removed, which led to a slow memory leak.
The problem with removing overflow buckets is
iterators. The obvious approach is to repack
keys and values and eliminate unused overflow
buckets. However, keys, values, and overflow
buckets cannot be manipulated without disrupting
iterators.
This CL takes a different approach, which is to
reuse the existing map growth mechanism,
which is well established, well tested, and
safe in the presence of iterators.
When a map has accumulated enough overflow buckets
we trigger map growth, but grow into a map of the
same size as before. The old overflow buckets will
be left behind for garbage collection.
For the code in #16070, instead of climbing
(very slowly) forever, memory usage now cycles
between 264mb and 483mb every 15 minutes or so.
To avoid increasing the size of maps,
the overflow bucket counter is only 16 bits.
For large maps, the counter is incremented
stochastically.
Fixes #16070
Change-Id: If551d77613ec6836907efca58bda3deee304297e
Reviewed-on: https://go-review.googlesource.com/25049
Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2016-07-19 11:47:53 -07:00
|
|
|
if !h.sameSizeGrow() {
|
|
|
|
|
// Only calculate y pointers if we're growing bigger.
|
|
|
|
|
// Otherwise GC can see bad pointers.
|
2017-08-10 07:16:27 -07:00
|
|
|
y := &xy[1]
|
2023-04-25 19:14:05 -04:00
|
|
|
y.b = (*bmap)(add(h.buckets, (oldbucket+newbit)*uintptr(t.BucketSize)))
|
2017-08-10 07:16:27 -07:00
|
|
|
y.k = add(unsafe.Pointer(y.b), dataOffset)
|
2024-01-31 03:51:34 +00:00
|
|
|
y.e = add(y.k, abi.MapBucketCount*uintptr(t.KeySize))
|
runtime: limit the number of map overflow buckets
Consider repeatedly adding many items to a map
and then deleting them all, as in #16070. The map
itself doesn't need to grow above the high water
mark of number of items. However, due to random
collisions, the map can accumulate overflow
buckets.
Prior to this CL, those overflow buckets were
never removed, which led to a slow memory leak.
The problem with removing overflow buckets is
iterators. The obvious approach is to repack
keys and values and eliminate unused overflow
buckets. However, keys, values, and overflow
buckets cannot be manipulated without disrupting
iterators.
This CL takes a different approach, which is to
reuse the existing map growth mechanism,
which is well established, well tested, and
safe in the presence of iterators.
When a map has accumulated enough overflow buckets
we trigger map growth, but grow into a map of the
same size as before. The old overflow buckets will
be left behind for garbage collection.
For the code in #16070, instead of climbing
(very slowly) forever, memory usage now cycles
between 264mb and 483mb every 15 minutes or so.
To avoid increasing the size of maps,
the overflow bucket counter is only 16 bits.
For large maps, the counter is incremented
stochastically.
Fixes #16070
Change-Id: If551d77613ec6836907efca58bda3deee304297e
Reviewed-on: https://go-review.googlesource.com/25049
Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2016-07-19 11:47:53 -07:00
|
|
|
}
|
2017-08-10 07:16:27 -07:00
|
|
|
|
2014-12-19 20:44:18 -08:00
|
|
|
for ; b != nil; b = b.overflow(t) {
|
2014-07-16 14:16:19 -07:00
|
|
|
k := add(unsafe.Pointer(b), dataOffset)
|
2024-01-31 03:51:34 +00:00
|
|
|
e := add(k, abi.MapBucketCount*uintptr(t.KeySize))
|
|
|
|
|
for i := 0; i < abi.MapBucketCount; i, k, e = i+1, add(k, uintptr(t.KeySize)), add(e, uintptr(t.ValueSize)) {
|
2014-07-16 14:16:19 -07:00
|
|
|
top := b.tophash[i]
|
2018-10-15 15:14:48 -07:00
|
|
|
if isEmpty(top) {
|
2014-07-16 14:16:19 -07:00
|
|
|
b.tophash[i] = evacuatedEmpty
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if top < minTopHash {
|
2014-12-27 20:58:00 -08:00
|
|
|
throw("bad map state")
|
2014-07-16 14:16:19 -07:00
|
|
|
}
|
|
|
|
|
k2 := k
|
2023-04-25 19:14:05 -04:00
|
|
|
if t.IndirectKey() {
|
2014-07-16 14:16:19 -07:00
|
|
|
k2 = *((*unsafe.Pointer)(k2))
|
|
|
|
|
}
|
2017-08-10 07:16:27 -07:00
|
|
|
var useY uint8
|
runtime: limit the number of map overflow buckets
Consider repeatedly adding many items to a map
and then deleting them all, as in #16070. The map
itself doesn't need to grow above the high water
mark of number of items. However, due to random
collisions, the map can accumulate overflow
buckets.
Prior to this CL, those overflow buckets were
never removed, which led to a slow memory leak.
The problem with removing overflow buckets is
iterators. The obvious approach is to repack
keys and values and eliminate unused overflow
buckets. However, keys, values, and overflow
buckets cannot be manipulated without disrupting
iterators.
This CL takes a different approach, which is to
reuse the existing map growth mechanism,
which is well established, well tested, and
safe in the presence of iterators.
When a map has accumulated enough overflow buckets
we trigger map growth, but grow into a map of the
same size as before. The old overflow buckets will
be left behind for garbage collection.
For the code in #16070, instead of climbing
(very slowly) forever, memory usage now cycles
between 264mb and 483mb every 15 minutes or so.
To avoid increasing the size of maps,
the overflow bucket counter is only 16 bits.
For large maps, the counter is incremented
stochastically.
Fixes #16070
Change-Id: If551d77613ec6836907efca58bda3deee304297e
Reviewed-on: https://go-review.googlesource.com/25049
Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2016-07-19 11:47:53 -07:00
|
|
|
if !h.sameSizeGrow() {
|
|
|
|
|
// Compute hash to make our evacuation decision (whether we need
|
2019-04-22 13:37:08 -07:00
|
|
|
// to send this key/elem to bucket x or bucket y).
|
2023-04-25 19:14:05 -04:00
|
|
|
hash := t.Hasher(k2, uintptr(h.hash0))
|
|
|
|
|
if h.flags&iterator != 0 && !t.ReflexiveKey() && !t.Key.Equal(k2, k2) {
|
2017-08-10 06:46:36 -07:00
|
|
|
// If key != key (NaNs), then the hash could be (and probably
|
|
|
|
|
// will be) entirely different from the old hash. Moreover,
|
|
|
|
|
// it isn't reproducible. Reproducibility is required in the
|
|
|
|
|
// presence of iterators, as our evacuation decision must
|
|
|
|
|
// match whatever decision the iterator made.
|
|
|
|
|
// Fortunately, we have the freedom to send these keys either
|
|
|
|
|
// way. Also, tophash is meaningless for these kinds of keys.
|
|
|
|
|
// We let the low bit of tophash drive the evacuation decision.
|
|
|
|
|
// We recompute a new random tophash for the next level so
|
|
|
|
|
// these keys will get evenly distributed across all buckets
|
|
|
|
|
// after multiple grows.
|
2017-08-17 17:41:44 -07:00
|
|
|
useY = top & 1
|
2017-06-07 09:55:05 -07:00
|
|
|
top = tophash(hash)
|
2017-08-17 17:41:44 -07:00
|
|
|
} else {
|
|
|
|
|
if hash&newbit != 0 {
|
|
|
|
|
useY = 1
|
|
|
|
|
}
|
2014-07-16 14:16:19 -07:00
|
|
|
}
|
2017-08-10 07:16:27 -07:00
|
|
|
}
|
|
|
|
|
|
2018-10-15 15:14:48 -07:00
|
|
|
if evacuatedX+1 != evacuatedY || evacuatedX^1 != evacuatedY {
|
2017-09-08 23:05:19 +02:00
|
|
|
throw("bad evacuatedN")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
b.tophash[i] = evacuatedX + useY // evacuatedX + 1 == evacuatedY
|
2017-08-10 07:16:27 -07:00
|
|
|
dst := &xy[useY] // evacuation destination
|
|
|
|
|
|
2024-01-31 03:51:34 +00:00
|
|
|
if dst.i == abi.MapBucketCount {
|
2017-08-10 07:16:27 -07:00
|
|
|
dst.b = h.newoverflow(t, dst.b)
|
|
|
|
|
dst.i = 0
|
|
|
|
|
dst.k = add(unsafe.Pointer(dst.b), dataOffset)
|
2024-01-31 03:51:34 +00:00
|
|
|
dst.e = add(dst.k, abi.MapBucketCount*uintptr(t.KeySize))
|
2017-08-10 07:16:27 -07:00
|
|
|
}
|
2024-01-31 03:51:34 +00:00
|
|
|
dst.b.tophash[dst.i&(abi.MapBucketCount-1)] = top // mask dst.i as an optimization, to avoid a bounds check
|
2023-04-25 19:14:05 -04:00
|
|
|
if t.IndirectKey() {
|
2017-08-10 07:16:27 -07:00
|
|
|
*(*unsafe.Pointer)(dst.k) = k2 // copy pointer
|
2014-07-16 14:16:19 -07:00
|
|
|
} else {
|
2023-04-25 19:14:05 -04:00
|
|
|
typedmemmove(t.Key, dst.k, k) // copy elem
|
2017-08-10 07:16:27 -07:00
|
|
|
}
|
2023-04-25 19:14:05 -04:00
|
|
|
if t.IndirectElem() {
|
2019-04-22 13:37:08 -07:00
|
|
|
*(*unsafe.Pointer)(dst.e) = *(*unsafe.Pointer)(e)
|
2017-08-10 07:16:27 -07:00
|
|
|
} else {
|
2023-04-25 19:14:05 -04:00
|
|
|
typedmemmove(t.Elem, dst.e, e)
|
2017-08-10 07:16:27 -07:00
|
|
|
}
|
|
|
|
|
dst.i++
|
2017-08-17 23:13:57 -07:00
|
|
|
// These updates might push these pointers past the end of the
|
2019-04-22 13:37:08 -07:00
|
|
|
// key or elem arrays. That's ok, as we have the overflow pointer
|
2017-08-17 23:13:57 -07:00
|
|
|
// at the end of the bucket to protect against pointing past the
|
|
|
|
|
// end of the bucket.
|
2023-04-25 19:14:05 -04:00
|
|
|
dst.k = add(dst.k, uintptr(t.KeySize))
|
|
|
|
|
dst.e = add(dst.e, uintptr(t.ValueSize))
|
2014-07-16 14:16:19 -07:00
|
|
|
}
|
|
|
|
|
}
|
2019-04-22 13:37:08 -07:00
|
|
|
// Unlink the overflow buckets & clear key/elem to help GC.
|
2024-02-27 21:51:31 +00:00
|
|
|
if h.flags&oldIterator == 0 && t.Bucket.Pointers() {
|
2023-04-25 19:14:05 -04:00
|
|
|
b := add(h.oldbuckets, oldbucket*uintptr(t.BucketSize))
|
2016-09-26 13:10:41 -04:00
|
|
|
// Preserve b.tophash because the evacuation
|
|
|
|
|
// state is maintained there.
|
2017-08-19 10:45:38 -07:00
|
|
|
ptr := add(b, dataOffset)
|
2023-04-25 19:14:05 -04:00
|
|
|
n := uintptr(t.BucketSize) - dataOffset
|
2017-08-19 10:45:38 -07:00
|
|
|
memclrHasPointers(ptr, n)
|
2014-07-16 14:16:19 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if oldbucket == h.nevacuate {
|
2017-08-17 10:37:17 -07:00
|
|
|
advanceEvacuationMark(h, t, newbit)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func advanceEvacuationMark(h *hmap, t *maptype, newbit uintptr) {
|
|
|
|
|
h.nevacuate++
|
|
|
|
|
// Experiments suggest that 1024 is overkill by at least an order of magnitude.
|
|
|
|
|
// Put it in there as a safeguard anyway, to ensure O(1) behavior.
|
|
|
|
|
stop := h.nevacuate + 1024
|
|
|
|
|
if stop > newbit {
|
|
|
|
|
stop = newbit
|
|
|
|
|
}
|
|
|
|
|
for h.nevacuate != stop && bucketEvacuated(t, h, h.nevacuate) {
|
2017-08-17 10:24:13 -07:00
|
|
|
h.nevacuate++
|
2017-08-17 10:37:17 -07:00
|
|
|
}
|
|
|
|
|
if h.nevacuate == newbit { // newbit == # of oldbuckets
|
|
|
|
|
// Growing is all done. Free old main bucket array.
|
|
|
|
|
h.oldbuckets = nil
|
|
|
|
|
// Can discard old overflow buckets as well.
|
|
|
|
|
// If they are still referenced by an iterator,
|
|
|
|
|
// then the iterator holds a pointers to the slice.
|
|
|
|
|
if h.extra != nil {
|
2017-09-10 12:55:16 +02:00
|
|
|
h.extra.oldoverflow = nil
|
2014-07-16 14:16:19 -07:00
|
|
|
}
|
2017-08-17 10:37:17 -07:00
|
|
|
h.flags &^= sameSizeGrow
|
2014-07-16 14:16:19 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-01 23:21:55 +00:00
|
|
|
// Reflect stubs. Called from ../reflect/asm_*.s
|
2014-07-16 14:16:19 -07:00
|
|
|
|
2024-05-21 22:38:02 -04:00
|
|
|
// reflect_makemap is for package reflect,
|
|
|
|
|
// but widely used packages access it using linkname.
|
|
|
|
|
// Notable members of the hall of shame include:
|
2024-05-22 15:46:02 -04:00
|
|
|
// - gitee.com/quant1x/gox
|
2024-05-21 22:38:02 -04:00
|
|
|
// - github.com/modern-go/reflect2
|
2024-05-21 23:02:51 -04:00
|
|
|
// - github.com/goccy/go-json
|
2024-05-22 17:09:02 -04:00
|
|
|
// - github.com/RomiChan/protobuf
|
2024-05-22 00:14:42 -04:00
|
|
|
// - github.com/segmentio/encoding
|
2024-05-22 23:06:30 -04:00
|
|
|
// - github.com/v2pro/plz
|
2024-05-21 22:38:02 -04:00
|
|
|
//
|
|
|
|
|
// Do not remove or change the type signature.
|
|
|
|
|
// See go.dev/issue/67401.
|
|
|
|
|
//
|
2014-12-22 13:27:53 -05:00
|
|
|
//go:linkname reflect_makemap reflect.makemap
|
2017-03-17 20:10:38 -07:00
|
|
|
func reflect_makemap(t *maptype, cap int) *hmap {
|
2017-08-29 13:31:45 +02:00
|
|
|
// Check invariants and reflects math.
|
2023-04-25 19:14:05 -04:00
|
|
|
if t.Key.Equal == nil {
|
2017-09-02 17:32:58 +02:00
|
|
|
throw("runtime.reflect_makemap: unsupported map key type")
|
|
|
|
|
}
|
2024-01-31 03:51:34 +00:00
|
|
|
if t.Key.Size_ > abi.MapMaxKeyBytes && (!t.IndirectKey() || t.KeySize != uint8(goarch.PtrSize)) ||
|
|
|
|
|
t.Key.Size_ <= abi.MapMaxKeyBytes && (t.IndirectKey() || t.KeySize != uint8(t.Key.Size_)) {
|
2017-08-29 13:31:45 +02:00
|
|
|
throw("key size wrong")
|
|
|
|
|
}
|
2024-01-31 03:51:34 +00:00
|
|
|
if t.Elem.Size_ > abi.MapMaxElemBytes && (!t.IndirectElem() || t.ValueSize != uint8(goarch.PtrSize)) ||
|
|
|
|
|
t.Elem.Size_ <= abi.MapMaxElemBytes && (t.IndirectElem() || t.ValueSize != uint8(t.Elem.Size_)) {
|
2019-04-22 13:37:08 -07:00
|
|
|
throw("elem size wrong")
|
2017-08-29 13:31:45 +02:00
|
|
|
}
|
2024-01-31 03:51:34 +00:00
|
|
|
if t.Key.Align_ > abi.MapBucketCount {
|
2017-08-29 13:31:45 +02:00
|
|
|
throw("key align too big")
|
|
|
|
|
}
|
2024-01-31 03:51:34 +00:00
|
|
|
if t.Elem.Align_ > abi.MapBucketCount {
|
2019-04-22 13:37:08 -07:00
|
|
|
throw("elem align too big")
|
2017-08-29 13:31:45 +02:00
|
|
|
}
|
2023-04-25 19:14:05 -04:00
|
|
|
if t.Key.Size_%uintptr(t.Key.Align_) != 0 {
|
2017-08-29 13:31:45 +02:00
|
|
|
throw("key size not a multiple of key align")
|
|
|
|
|
}
|
2023-04-25 19:14:05 -04:00
|
|
|
if t.Elem.Size_%uintptr(t.Elem.Align_) != 0 {
|
2019-04-22 13:37:08 -07:00
|
|
|
throw("elem size not a multiple of elem align")
|
2017-08-29 13:31:45 +02:00
|
|
|
}
|
2024-01-31 03:51:34 +00:00
|
|
|
if abi.MapBucketCount < 8 {
|
2017-08-29 13:31:45 +02:00
|
|
|
throw("bucketsize too small for proper alignment")
|
|
|
|
|
}
|
2023-04-25 19:14:05 -04:00
|
|
|
if dataOffset%uintptr(t.Key.Align_) != 0 {
|
2017-08-29 13:31:45 +02:00
|
|
|
throw("need padding in bucket (key)")
|
|
|
|
|
}
|
2023-04-25 19:14:05 -04:00
|
|
|
if dataOffset%uintptr(t.Elem.Align_) != 0 {
|
2019-04-22 13:37:08 -07:00
|
|
|
throw("need padding in bucket (elem)")
|
2017-08-29 13:31:45 +02:00
|
|
|
}
|
|
|
|
|
|
2017-08-14 10:16:21 +02:00
|
|
|
return makemap(t, cap, nil)
|
2014-07-16 14:16:19 -07:00
|
|
|
}
|
|
|
|
|
|
2024-05-21 22:38:02 -04:00
|
|
|
// reflect_mapaccess is for package reflect,
|
|
|
|
|
// but widely used packages access it using linkname.
|
|
|
|
|
// Notable members of the hall of shame include:
|
2024-05-22 15:46:02 -04:00
|
|
|
// - gitee.com/quant1x/gox
|
2024-05-21 22:38:02 -04:00
|
|
|
// - github.com/modern-go/reflect2
|
2024-05-22 23:06:30 -04:00
|
|
|
// - github.com/v2pro/plz
|
2024-05-21 22:38:02 -04:00
|
|
|
//
|
|
|
|
|
// Do not remove or change the type signature.
|
|
|
|
|
// See go.dev/issue/67401.
|
|
|
|
|
//
|
2014-12-22 13:27:53 -05:00
|
|
|
//go:linkname reflect_mapaccess reflect.mapaccess
|
2014-07-16 14:16:19 -07:00
|
|
|
func reflect_mapaccess(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {
|
2019-04-22 13:37:08 -07:00
|
|
|
elem, ok := mapaccess2(t, h, key)
|
2014-07-16 14:16:19 -07:00
|
|
|
if !ok {
|
|
|
|
|
// reflect wants nil for a missing element
|
2019-04-22 13:37:08 -07:00
|
|
|
elem = nil
|
2014-07-16 14:16:19 -07:00
|
|
|
}
|
2019-04-22 13:37:08 -07:00
|
|
|
return elem
|
2014-07-16 14:16:19 -07:00
|
|
|
}
|
|
|
|
|
|
2021-08-26 19:13:22 -07:00
|
|
|
//go:linkname reflect_mapaccess_faststr reflect.mapaccess_faststr
|
|
|
|
|
func reflect_mapaccess_faststr(t *maptype, h *hmap, key string) unsafe.Pointer {
|
|
|
|
|
elem, ok := mapaccess2_faststr(t, h, key)
|
|
|
|
|
if !ok {
|
|
|
|
|
// reflect wants nil for a missing element
|
|
|
|
|
elem = nil
|
|
|
|
|
}
|
|
|
|
|
return elem
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-22 15:46:02 -04:00
|
|
|
// reflect_mapassign is for package reflect,
|
|
|
|
|
// but widely used packages access it using linkname.
|
|
|
|
|
// Notable members of the hall of shame include:
|
|
|
|
|
// - gitee.com/quant1x/gox
|
2024-05-22 23:06:30 -04:00
|
|
|
// - github.com/v2pro/plz
|
2024-05-22 15:46:02 -04:00
|
|
|
//
|
|
|
|
|
// Do not remove or change the type signature.
|
|
|
|
|
//
|
2022-05-27 15:44:40 -04:00
|
|
|
//go:linkname reflect_mapassign reflect.mapassign0
|
2019-04-22 13:37:08 -07:00
|
|
|
func reflect_mapassign(t *maptype, h *hmap, key unsafe.Pointer, elem unsafe.Pointer) {
|
2016-10-11 08:36:38 -07:00
|
|
|
p := mapassign(t, h, key)
|
2023-04-25 19:14:05 -04:00
|
|
|
typedmemmove(t.Elem, p, elem)
|
2014-07-16 14:16:19 -07:00
|
|
|
}
|
|
|
|
|
|
2022-05-27 15:44:40 -04:00
|
|
|
//go:linkname reflect_mapassign_faststr reflect.mapassign_faststr0
|
2021-08-26 19:13:22 -07:00
|
|
|
func reflect_mapassign_faststr(t *maptype, h *hmap, key string, elem unsafe.Pointer) {
|
|
|
|
|
p := mapassign_faststr(t, h, key)
|
2023-04-25 19:14:05 -04:00
|
|
|
typedmemmove(t.Elem, p, elem)
|
2021-08-26 19:13:22 -07:00
|
|
|
}
|
|
|
|
|
|
2014-12-22 13:27:53 -05:00
|
|
|
//go:linkname reflect_mapdelete reflect.mapdelete
|
2014-07-16 14:16:19 -07:00
|
|
|
func reflect_mapdelete(t *maptype, h *hmap, key unsafe.Pointer) {
|
|
|
|
|
mapdelete(t, h, key)
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-26 19:13:22 -07:00
|
|
|
//go:linkname reflect_mapdelete_faststr reflect.mapdelete_faststr
|
|
|
|
|
func reflect_mapdelete_faststr(t *maptype, h *hmap, key string) {
|
|
|
|
|
mapdelete_faststr(t, h, key)
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-21 22:38:02 -04:00
|
|
|
// reflect_mapiterinit is for package reflect,
|
|
|
|
|
// but widely used packages access it using linkname.
|
|
|
|
|
// Notable members of the hall of shame include:
|
|
|
|
|
// - github.com/modern-go/reflect2
|
2024-05-22 15:46:02 -04:00
|
|
|
// - gitee.com/quant1x/gox
|
2024-05-22 23:06:30 -04:00
|
|
|
// - github.com/v2pro/plz
|
|
|
|
|
// - github.com/wI2L/jettison
|
2024-05-21 22:38:02 -04:00
|
|
|
//
|
|
|
|
|
// Do not remove or change the type signature.
|
|
|
|
|
// See go.dev/issue/67401.
|
|
|
|
|
//
|
2014-12-22 13:27:53 -05:00
|
|
|
//go:linkname reflect_mapiterinit reflect.mapiterinit
|
2021-05-20 09:57:04 -07:00
|
|
|
func reflect_mapiterinit(t *maptype, h *hmap, it *hiter) {
|
2014-07-16 14:16:19 -07:00
|
|
|
mapiterinit(t, h, it)
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-21 22:38:02 -04:00
|
|
|
// reflect_mapiternext is for package reflect,
|
|
|
|
|
// but widely used packages access it using linkname.
|
|
|
|
|
// Notable members of the hall of shame include:
|
2024-05-22 15:46:02 -04:00
|
|
|
// - gitee.com/quant1x/gox
|
2024-05-21 22:38:02 -04:00
|
|
|
// - github.com/modern-go/reflect2
|
2024-05-21 23:02:51 -04:00
|
|
|
// - github.com/goccy/go-json
|
2024-05-22 23:06:30 -04:00
|
|
|
// - github.com/v2pro/plz
|
|
|
|
|
// - github.com/wI2L/jettison
|
2024-05-21 22:38:02 -04:00
|
|
|
//
|
|
|
|
|
// Do not remove or change the type signature.
|
|
|
|
|
// See go.dev/issue/67401.
|
|
|
|
|
//
|
2014-12-22 13:27:53 -05:00
|
|
|
//go:linkname reflect_mapiternext reflect.mapiternext
|
2014-07-16 14:16:19 -07:00
|
|
|
func reflect_mapiternext(it *hiter) {
|
|
|
|
|
mapiternext(it)
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-21 23:02:51 -04:00
|
|
|
// reflect_mapiterkey is for package reflect,
|
|
|
|
|
// but widely used packages access it using linkname.
|
|
|
|
|
// Notable members of the hall of shame include:
|
|
|
|
|
// - github.com/goccy/go-json
|
2024-05-21 23:50:52 -04:00
|
|
|
// - gonum.org/v1/gonum
|
2024-05-21 23:02:51 -04:00
|
|
|
//
|
|
|
|
|
// Do not remove or change the type signature.
|
|
|
|
|
// See go.dev/issue/67401.
|
|
|
|
|
//
|
2014-12-22 13:27:53 -05:00
|
|
|
//go:linkname reflect_mapiterkey reflect.mapiterkey
|
2014-07-16 14:16:19 -07:00
|
|
|
func reflect_mapiterkey(it *hiter) unsafe.Pointer {
|
|
|
|
|
return it.key
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-21 23:02:51 -04:00
|
|
|
// reflect_mapiterelem is for package reflect,
|
|
|
|
|
// but widely used packages access it using linkname.
|
|
|
|
|
// Notable members of the hall of shame include:
|
|
|
|
|
// - github.com/goccy/go-json
|
2024-05-21 23:50:52 -04:00
|
|
|
// - gonum.org/v1/gonum
|
2024-05-21 23:02:51 -04:00
|
|
|
//
|
|
|
|
|
// Do not remove or change the type signature.
|
|
|
|
|
// See go.dev/issue/67401.
|
|
|
|
|
//
|
2019-04-22 13:37:08 -07:00
|
|
|
//go:linkname reflect_mapiterelem reflect.mapiterelem
|
|
|
|
|
func reflect_mapiterelem(it *hiter) unsafe.Pointer {
|
|
|
|
|
return it.elem
|
2016-11-23 15:34:08 -05:00
|
|
|
}
|
|
|
|
|
|
2024-05-21 23:02:51 -04:00
|
|
|
// reflect_maplen is for package reflect,
|
|
|
|
|
// but widely used packages access it using linkname.
|
|
|
|
|
// Notable members of the hall of shame include:
|
|
|
|
|
// - github.com/goccy/go-json
|
2024-05-22 23:06:30 -04:00
|
|
|
// - github.com/wI2L/jettison
|
2024-05-21 23:02:51 -04:00
|
|
|
//
|
|
|
|
|
// Do not remove or change the type signature.
|
|
|
|
|
// See go.dev/issue/67401.
|
|
|
|
|
//
|
2014-12-22 13:27:53 -05:00
|
|
|
//go:linkname reflect_maplen reflect.maplen
|
2014-07-16 14:16:19 -07:00
|
|
|
func reflect_maplen(h *hmap) int {
|
|
|
|
|
if h == nil {
|
|
|
|
|
return 0
|
|
|
|
|
}
|
|
|
|
|
if raceenabled {
|
2017-09-22 15:16:26 -04:00
|
|
|
callerpc := getcallerpc()
|
2021-05-21 13:37:19 -04:00
|
|
|
racereadpc(unsafe.Pointer(h), callerpc, abi.FuncPCABIInternal(reflect_maplen))
|
2014-07-16 14:16:19 -07:00
|
|
|
}
|
|
|
|
|
return h.count
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-15 18:14:13 +07:00
|
|
|
//go:linkname reflect_mapclear reflect.mapclear
|
|
|
|
|
func reflect_mapclear(t *maptype, h *hmap) {
|
|
|
|
|
mapclear(t, h)
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-25 17:39:11 +00:00
|
|
|
//go:linkname reflectlite_maplen internal/reflectlite.maplen
|
|
|
|
|
func reflectlite_maplen(h *hmap) int {
|
|
|
|
|
if h == nil {
|
|
|
|
|
return 0
|
|
|
|
|
}
|
|
|
|
|
if raceenabled {
|
|
|
|
|
callerpc := getcallerpc()
|
2021-05-21 13:37:19 -04:00
|
|
|
racereadpc(unsafe.Pointer(h), callerpc, abi.FuncPCABIInternal(reflect_maplen))
|
2019-03-25 17:39:11 +00:00
|
|
|
}
|
|
|
|
|
return h.count
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-25 10:46:08 -05:00
|
|
|
// mapinitnoop is a no-op function known the Go linker; if a given global
|
|
|
|
|
// map (of the right size) is determined to be dead, the linker will
|
|
|
|
|
// rewrite the relocation (from the package init func) from the outlined
|
|
|
|
|
// map init function to this symbol. Defined in assembly so as to avoid
|
|
|
|
|
// complications with instrumentation (coverage, etc).
|
|
|
|
|
func mapinitnoop()
|
2023-02-27 07:15:50 +08:00
|
|
|
|
|
|
|
|
// 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) {
|
2024-01-31 03:51:34 +00:00
|
|
|
for i := 0; i < abi.MapBucketCount; i++ {
|
2023-02-27 07:15:50 +08:00
|
|
|
if isEmpty(src.tophash[i]) {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-31 03:51:34 +00:00
|
|
|
for ; pos < abi.MapBucketCount; pos++ {
|
2023-02-27 07:15:50 +08:00
|
|
|
if isEmpty(dst.tophash[pos]) {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-31 03:51:34 +00:00
|
|
|
if pos == abi.MapBucketCount {
|
2023-02-27 07:15:50 +08:00
|
|
|
dst = h.newoverflow(t, dst)
|
|
|
|
|
pos = 0
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-25 19:14:05 -04:00
|
|
|
srcK := add(unsafe.Pointer(src), dataOffset+uintptr(i)*uintptr(t.KeySize))
|
2024-01-31 03:51:34 +00:00
|
|
|
srcEle := add(unsafe.Pointer(src), dataOffset+abi.MapBucketCount*uintptr(t.KeySize)+uintptr(i)*uintptr(t.ValueSize))
|
2023-04-25 19:14:05 -04:00
|
|
|
dstK := add(unsafe.Pointer(dst), dataOffset+uintptr(pos)*uintptr(t.KeySize))
|
2024-01-31 03:51:34 +00:00
|
|
|
dstEle := add(unsafe.Pointer(dst), dataOffset+abi.MapBucketCount*uintptr(t.KeySize)+uintptr(pos)*uintptr(t.ValueSize))
|
2023-02-27 07:15:50 +08:00
|
|
|
|
|
|
|
|
dst.tophash[pos] = src.tophash[i]
|
2023-04-25 19:14:05 -04:00
|
|
|
if t.IndirectKey() {
|
2023-11-30 22:59:04 -08:00
|
|
|
srcK = *(*unsafe.Pointer)(srcK)
|
|
|
|
|
if t.NeedKeyUpdate() {
|
|
|
|
|
kStore := newobject(t.Key)
|
|
|
|
|
typedmemmove(t.Key, kStore, srcK)
|
|
|
|
|
srcK = kStore
|
|
|
|
|
}
|
|
|
|
|
// Note: if NeedKeyUpdate is false, then the memory
|
|
|
|
|
// used to store the key is immutable, so we can share
|
|
|
|
|
// it between the original map and its clone.
|
|
|
|
|
*(*unsafe.Pointer)(dstK) = srcK
|
2023-02-27 07:15:50 +08:00
|
|
|
} else {
|
2023-04-25 19:14:05 -04:00
|
|
|
typedmemmove(t.Key, dstK, srcK)
|
2023-02-27 07:15:50 +08:00
|
|
|
}
|
2023-04-25 19:14:05 -04:00
|
|
|
if t.IndirectElem() {
|
2023-11-30 22:59:04 -08:00
|
|
|
srcEle = *(*unsafe.Pointer)(srcEle)
|
|
|
|
|
eStore := newobject(t.Elem)
|
|
|
|
|
typedmemmove(t.Elem, eStore, srcEle)
|
|
|
|
|
*(*unsafe.Pointer)(dstEle) = eStore
|
2023-02-27 07:15:50 +08:00
|
|
|
} else {
|
2023-04-25 19:14:05 -04:00
|
|
|
typedmemmove(t.Elem, dstEle, srcEle)
|
2023-02-27 07:15:50 +08:00
|
|
|
}
|
|
|
|
|
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
|
2024-02-01 10:21:14 +08:00
|
|
|
// flags do not need to be copied here, just like a new map has no flags.
|
2023-02-27 07:15:50 +08:00
|
|
|
|
|
|
|
|
if src.count == 0 {
|
|
|
|
|
return dst
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if src.flags&hashWriting != 0 {
|
|
|
|
|
fatal("concurrent map clone and map write")
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-30 22:59:04 -08:00
|
|
|
if src.B == 0 && !(t.IndirectKey() && t.NeedKeyUpdate()) && !t.IndirectElem() {
|
|
|
|
|
// Quick copy for small maps.
|
2023-04-25 19:14:05 -04:00
|
|
|
dst.buckets = newobject(t.Bucket)
|
2023-02-27 07:15:50 +08:00
|
|
|
dst.count = src.count
|
2023-04-25 19:14:05 -04:00
|
|
|
typedmemmove(t.Bucket, dst.buckets, src.buckets)
|
2023-02-27 07:15:50 +08:00
|
|
|
return dst
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if dst.B == 0 {
|
2023-04-25 19:14:05 -04:00
|
|
|
dst.buckets = newobject(t.Bucket)
|
2023-02-27 07:15:50 +08:00
|
|
|
}
|
|
|
|
|
dstArraySize := int(bucketShift(dst.B))
|
|
|
|
|
srcArraySize := int(bucketShift(src.B))
|
|
|
|
|
for i := 0; i < dstArraySize; i++ {
|
2023-04-25 19:14:05 -04:00
|
|
|
dstBmap := (*bmap)(add(dst.buckets, uintptr(i*int(t.BucketSize))))
|
2023-02-27 07:15:50 +08:00
|
|
|
pos := 0
|
|
|
|
|
for j := 0; j < srcArraySize; j += dstArraySize {
|
2023-04-25 19:14:05 -04:00
|
|
|
srcBmap := (*bmap)(add(src.buckets, uintptr((i+j)*int(t.BucketSize))))
|
2023-02-27 07:15:50 +08:00
|
|
|
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++ {
|
2023-04-25 19:14:05 -04:00
|
|
|
srcBmap := (*bmap)(add(srcOldbuckets, uintptr(i*int(t.BucketSize))))
|
2023-02-27 07:15:50 +08:00
|
|
|
if evacuated(srcBmap) {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if oldB >= dst.B { // main bucket bits in dst is less than oldB bits in src
|
2023-08-23 15:19:15 -07:00
|
|
|
dstBmap := (*bmap)(add(dst.buckets, (uintptr(i)&bucketMask(dst.B))*uintptr(t.BucketSize)))
|
2023-02-27 07:15:50 +08:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-30 22:59:04 -08:00
|
|
|
// oldB < dst.B, so a single source bucket may go to multiple destination buckets.
|
|
|
|
|
// Process entries one at a time.
|
2023-02-27 07:15:50 +08:00
|
|
|
for srcBmap != nil {
|
|
|
|
|
// move from oldBlucket to new bucket
|
2024-01-31 03:51:34 +00:00
|
|
|
for i := uintptr(0); i < abi.MapBucketCount; i++ {
|
2023-02-27 07:15:50 +08:00
|
|
|
if isEmpty(srcBmap.tophash[i]) {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if src.flags&hashWriting != 0 {
|
|
|
|
|
fatal("concurrent map clone and map write")
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-25 19:14:05 -04:00
|
|
|
srcK := add(unsafe.Pointer(srcBmap), dataOffset+i*uintptr(t.KeySize))
|
|
|
|
|
if t.IndirectKey() {
|
2023-02-27 07:15:50 +08:00
|
|
|
srcK = *((*unsafe.Pointer)(srcK))
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-31 03:51:34 +00:00
|
|
|
srcEle := add(unsafe.Pointer(srcBmap), dataOffset+abi.MapBucketCount*uintptr(t.KeySize)+i*uintptr(t.ValueSize))
|
2023-04-25 19:14:05 -04:00
|
|
|
if t.IndirectElem() {
|
2023-02-27 07:15:50 +08:00
|
|
|
srcEle = *((*unsafe.Pointer)(srcEle))
|
|
|
|
|
}
|
|
|
|
|
dstEle := mapassign(t, dst, srcK)
|
2023-04-25 19:14:05 -04:00
|
|
|
typedmemmove(t.Elem, dstEle, srcEle)
|
2023-02-27 07:15:50 +08:00
|
|
|
}
|
|
|
|
|
srcBmap = srcBmap.overflow(t)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return dst
|
|
|
|
|
}
|
2023-04-02 15:49:58 +08:00
|
|
|
|
|
|
|
|
// 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)
|
math/rand, math/rand/v2: use ChaCha8 for global rand
Move ChaCha8 code into internal/chacha8rand and use it to implement
runtime.rand, which is used for the unseeded global source for
both math/rand and math/rand/v2. This also affects the calculation of
the start point for iteration over very very large maps (when the
32-bit fastrand is not big enough).
The benefit is that misuse of the global random number generators
in math/rand and math/rand/v2 in contexts where non-predictable
randomness is important for security reasons is no longer a
security problem, removing a common mistake among programmers
who are unaware of the different kinds of randomness.
The cost is an extra 304 bytes per thread stored in the m struct
plus 2-3ns more per random uint64 due to the more sophisticated
algorithm. Using PCG looks like it would cost about the same,
although I haven't benchmarked that.
Before this, the math/rand and math/rand/v2 global generator
was wyrand (https://github.com/wangyi-fudan/wyhash).
For math/rand, using wyrand instead of the Mitchell/Reeds/Thompson
ALFG was justifiable, since the latter was not any better.
But for math/rand/v2, the global generator really should be
at least as good as one of the well-studied, specific algorithms
provided directly by the package, and it's not.
(Wyrand is still reasonable for scheduling and cache decisions.)
Good randomness does have a cost: about twice wyrand.
Also rationalize the various runtime rand references.
goos: linux
goarch: amd64
pkg: math/rand/v2
cpu: AMD Ryzen 9 7950X 16-Core Processor
│ bbb48afeb7.amd64 │ 5cf807d1ea.amd64 │
│ sec/op │ sec/op vs base │
ChaCha8-32 1.862n ± 2% 1.861n ± 2% ~ (p=0.825 n=20)
PCG_DXSM-32 1.471n ± 1% 1.460n ± 2% ~ (p=0.153 n=20)
SourceUint64-32 1.636n ± 2% 1.582n ± 1% -3.30% (p=0.000 n=20)
GlobalInt64-32 2.087n ± 1% 3.663n ± 1% +75.54% (p=0.000 n=20)
GlobalInt64Parallel-32 0.1042n ± 1% 0.2026n ± 1% +94.48% (p=0.000 n=20)
GlobalUint64-32 2.263n ± 2% 3.724n ± 1% +64.57% (p=0.000 n=20)
GlobalUint64Parallel-32 0.1019n ± 1% 0.1973n ± 1% +93.67% (p=0.000 n=20)
Int64-32 1.771n ± 1% 1.774n ± 1% ~ (p=0.449 n=20)
Uint64-32 1.863n ± 2% 1.866n ± 1% ~ (p=0.364 n=20)
GlobalIntN1000-32 3.134n ± 3% 4.730n ± 2% +50.95% (p=0.000 n=20)
IntN1000-32 2.489n ± 1% 2.489n ± 1% ~ (p=0.683 n=20)
Int64N1000-32 2.521n ± 1% 2.516n ± 1% ~ (p=0.394 n=20)
Int64N1e8-32 2.479n ± 1% 2.478n ± 2% ~ (p=0.743 n=20)
Int64N1e9-32 2.530n ± 2% 2.514n ± 2% ~ (p=0.193 n=20)
Int64N2e9-32 2.501n ± 1% 2.494n ± 1% ~ (p=0.616 n=20)
Int64N1e18-32 3.227n ± 1% 3.205n ± 1% ~ (p=0.101 n=20)
Int64N2e18-32 3.647n ± 1% 3.599n ± 1% ~ (p=0.019 n=20)
Int64N4e18-32 5.135n ± 1% 5.069n ± 2% ~ (p=0.034 n=20)
Int32N1000-32 2.657n ± 1% 2.637n ± 1% ~ (p=0.180 n=20)
Int32N1e8-32 2.636n ± 1% 2.636n ± 1% ~ (p=0.763 n=20)
Int32N1e9-32 2.660n ± 2% 2.638n ± 1% ~ (p=0.358 n=20)
Int32N2e9-32 2.662n ± 2% 2.618n ± 2% ~ (p=0.064 n=20)
Float32-32 2.272n ± 2% 2.239n ± 2% ~ (p=0.194 n=20)
Float64-32 2.272n ± 1% 2.286n ± 2% ~ (p=0.763 n=20)
ExpFloat64-32 3.762n ± 1% 3.744n ± 1% ~ (p=0.171 n=20)
NormFloat64-32 3.706n ± 1% 3.655n ± 2% ~ (p=0.066 n=20)
Perm3-32 32.93n ± 3% 34.62n ± 1% +5.13% (p=0.000 n=20)
Perm30-32 202.9n ± 1% 204.0n ± 1% ~ (p=0.482 n=20)
Perm30ViaShuffle-32 115.0n ± 1% 114.9n ± 1% ~ (p=0.358 n=20)
ShuffleOverhead-32 112.8n ± 1% 112.7n ± 1% ~ (p=0.692 n=20)
Concurrent-32 2.107n ± 0% 3.725n ± 1% +76.75% (p=0.000 n=20)
goos: darwin
goarch: arm64
pkg: math/rand/v2
│ bbb48afeb7.arm64 │ 5cf807d1ea.arm64 │
│ sec/op │ sec/op vs base │
ChaCha8-8 2.480n ± 0% 2.429n ± 0% -2.04% (p=0.000 n=20)
PCG_DXSM-8 2.531n ± 0% 2.530n ± 0% ~ (p=0.877 n=20)
SourceUint64-8 2.534n ± 0% 2.533n ± 0% ~ (p=0.732 n=20)
GlobalInt64-8 2.172n ± 1% 4.794n ± 0% +120.67% (p=0.000 n=20)
GlobalInt64Parallel-8 0.4320n ± 0% 0.9605n ± 0% +122.32% (p=0.000 n=20)
GlobalUint64-8 2.182n ± 0% 4.770n ± 0% +118.58% (p=0.000 n=20)
GlobalUint64Parallel-8 0.4307n ± 0% 0.9583n ± 0% +122.51% (p=0.000 n=20)
Int64-8 4.107n ± 0% 4.104n ± 0% ~ (p=0.416 n=20)
Uint64-8 4.080n ± 0% 4.080n ± 0% ~ (p=0.052 n=20)
GlobalIntN1000-8 2.814n ± 2% 5.643n ± 0% +100.50% (p=0.000 n=20)
IntN1000-8 4.141n ± 0% 4.139n ± 0% ~ (p=0.140 n=20)
Int64N1000-8 4.140n ± 0% 4.140n ± 0% ~ (p=0.313 n=20)
Int64N1e8-8 4.140n ± 0% 4.139n ± 0% ~ (p=0.103 n=20)
Int64N1e9-8 4.139n ± 0% 4.140n ± 0% ~ (p=0.761 n=20)
Int64N2e9-8 4.140n ± 0% 4.140n ± 0% ~ (p=0.636 n=20)
Int64N1e18-8 5.266n ± 0% 5.326n ± 1% +1.14% (p=0.001 n=20)
Int64N2e18-8 6.052n ± 0% 6.167n ± 0% +1.90% (p=0.000 n=20)
Int64N4e18-8 8.826n ± 0% 9.051n ± 0% +2.55% (p=0.000 n=20)
Int32N1000-8 4.127n ± 0% 4.132n ± 0% +0.12% (p=0.000 n=20)
Int32N1e8-8 4.126n ± 0% 4.131n ± 0% +0.12% (p=0.000 n=20)
Int32N1e9-8 4.127n ± 0% 4.132n ± 0% +0.12% (p=0.000 n=20)
Int32N2e9-8 4.132n ± 0% 4.131n ± 0% ~ (p=0.017 n=20)
Float32-8 4.109n ± 0% 4.105n ± 0% ~ (p=0.379 n=20)
Float64-8 4.107n ± 0% 4.106n ± 0% ~ (p=0.867 n=20)
ExpFloat64-8 5.339n ± 0% 5.383n ± 0% +0.82% (p=0.000 n=20)
NormFloat64-8 5.735n ± 0% 5.737n ± 1% ~ (p=0.856 n=20)
Perm3-8 26.65n ± 0% 26.80n ± 1% +0.58% (p=0.000 n=20)
Perm30-8 194.8n ± 1% 197.0n ± 0% +1.18% (p=0.000 n=20)
Perm30ViaShuffle-8 156.6n ± 0% 157.6n ± 1% +0.61% (p=0.000 n=20)
ShuffleOverhead-8 124.9n ± 0% 125.5n ± 0% +0.52% (p=0.000 n=20)
Concurrent-8 2.434n ± 3% 5.066n ± 0% +108.09% (p=0.000 n=20)
goos: linux
goarch: 386
pkg: math/rand/v2
cpu: AMD Ryzen 9 7950X 16-Core Processor
│ bbb48afeb7.386 │ 5cf807d1ea.386 │
│ sec/op │ sec/op vs base │
ChaCha8-32 11.295n ± 1% 4.748n ± 2% -57.96% (p=0.000 n=20)
PCG_DXSM-32 7.693n ± 1% 7.738n ± 2% ~ (p=0.542 n=20)
SourceUint64-32 7.658n ± 2% 7.622n ± 2% ~ (p=0.344 n=20)
GlobalInt64-32 3.473n ± 2% 7.526n ± 2% +116.73% (p=0.000 n=20)
GlobalInt64Parallel-32 0.3198n ± 0% 0.5444n ± 0% +70.22% (p=0.000 n=20)
GlobalUint64-32 3.612n ± 0% 7.575n ± 1% +109.69% (p=0.000 n=20)
GlobalUint64Parallel-32 0.3168n ± 0% 0.5403n ± 0% +70.51% (p=0.000 n=20)
Int64-32 7.673n ± 2% 7.789n ± 1% ~ (p=0.122 n=20)
Uint64-32 7.773n ± 1% 7.827n ± 2% ~ (p=0.920 n=20)
GlobalIntN1000-32 6.268n ± 1% 9.581n ± 1% +52.87% (p=0.000 n=20)
IntN1000-32 10.33n ± 2% 10.45n ± 1% ~ (p=0.233 n=20)
Int64N1000-32 10.98n ± 2% 11.01n ± 1% ~ (p=0.401 n=20)
Int64N1e8-32 11.19n ± 2% 10.97n ± 1% ~ (p=0.033 n=20)
Int64N1e9-32 11.06n ± 1% 11.08n ± 1% ~ (p=0.498 n=20)
Int64N2e9-32 11.10n ± 1% 11.01n ± 2% ~ (p=0.995 n=20)
Int64N1e18-32 15.23n ± 2% 15.04n ± 1% ~ (p=0.973 n=20)
Int64N2e18-32 15.89n ± 1% 15.85n ± 1% ~ (p=0.409 n=20)
Int64N4e18-32 18.96n ± 2% 19.34n ± 2% ~ (p=0.048 n=20)
Int32N1000-32 10.46n ± 2% 10.44n ± 2% ~ (p=0.480 n=20)
Int32N1e8-32 10.46n ± 2% 10.49n ± 2% ~ (p=0.951 n=20)
Int32N1e9-32 10.28n ± 2% 10.26n ± 1% ~ (p=0.431 n=20)
Int32N2e9-32 10.50n ± 2% 10.44n ± 2% ~ (p=0.249 n=20)
Float32-32 13.80n ± 2% 13.80n ± 2% ~ (p=0.751 n=20)
Float64-32 23.55n ± 2% 23.87n ± 0% ~ (p=0.408 n=20)
ExpFloat64-32 15.36n ± 1% 15.29n ± 2% ~ (p=0.316 n=20)
NormFloat64-32 13.57n ± 1% 13.79n ± 1% +1.66% (p=0.005 n=20)
Perm3-32 45.70n ± 2% 46.99n ± 2% +2.81% (p=0.001 n=20)
Perm30-32 399.0n ± 1% 403.8n ± 1% +1.19% (p=0.006 n=20)
Perm30ViaShuffle-32 349.0n ± 1% 350.4n ± 1% ~ (p=0.909 n=20)
ShuffleOverhead-32 322.3n ± 1% 323.8n ± 1% ~ (p=0.410 n=20)
Concurrent-32 3.331n ± 1% 7.312n ± 1% +119.50% (p=0.000 n=20)
For #61716.
Change-Id: Ibdddeed85c34d9ae397289dc899e04d4845f9ed2
Reviewed-on: https://go-review.googlesource.com/c/go/+/516860
Reviewed-by: Michael Pratt <mpratt@google.com>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
2023-08-06 13:26:28 +10:00
|
|
|
r := int(rand())
|
2024-01-31 03:51:34 +00:00
|
|
|
offset := uint8(r >> h.B & (abi.MapBucketCount - 1))
|
2023-04-02 15:49:58 +08:00
|
|
|
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 {
|
2024-01-31 03:51:34 +00:00
|
|
|
for i := uintptr(0); i < abi.MapBucketCount; i++ {
|
|
|
|
|
offi := (i + uintptr(offset)) & (abi.MapBucketCount - 1)
|
2023-04-02 15:49:58 +08:00
|
|
|
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")
|
|
|
|
|
}
|
2023-10-13 10:34:30 -07:00
|
|
|
typedmemmove(t.Key, add(s.array, uintptr(s.len)*uintptr(t.Key.Size())), k)
|
2023-04-02 15:49:58 +08:00
|
|
|
s.len++
|
|
|
|
|
}
|
|
|
|
|
b = b.overflow(t)
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-04-03 20:14:06 +08:00
|
|
|
|
|
|
|
|
// 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)
|
math/rand, math/rand/v2: use ChaCha8 for global rand
Move ChaCha8 code into internal/chacha8rand and use it to implement
runtime.rand, which is used for the unseeded global source for
both math/rand and math/rand/v2. This also affects the calculation of
the start point for iteration over very very large maps (when the
32-bit fastrand is not big enough).
The benefit is that misuse of the global random number generators
in math/rand and math/rand/v2 in contexts where non-predictable
randomness is important for security reasons is no longer a
security problem, removing a common mistake among programmers
who are unaware of the different kinds of randomness.
The cost is an extra 304 bytes per thread stored in the m struct
plus 2-3ns more per random uint64 due to the more sophisticated
algorithm. Using PCG looks like it would cost about the same,
although I haven't benchmarked that.
Before this, the math/rand and math/rand/v2 global generator
was wyrand (https://github.com/wangyi-fudan/wyhash).
For math/rand, using wyrand instead of the Mitchell/Reeds/Thompson
ALFG was justifiable, since the latter was not any better.
But for math/rand/v2, the global generator really should be
at least as good as one of the well-studied, specific algorithms
provided directly by the package, and it's not.
(Wyrand is still reasonable for scheduling and cache decisions.)
Good randomness does have a cost: about twice wyrand.
Also rationalize the various runtime rand references.
goos: linux
goarch: amd64
pkg: math/rand/v2
cpu: AMD Ryzen 9 7950X 16-Core Processor
│ bbb48afeb7.amd64 │ 5cf807d1ea.amd64 │
│ sec/op │ sec/op vs base │
ChaCha8-32 1.862n ± 2% 1.861n ± 2% ~ (p=0.825 n=20)
PCG_DXSM-32 1.471n ± 1% 1.460n ± 2% ~ (p=0.153 n=20)
SourceUint64-32 1.636n ± 2% 1.582n ± 1% -3.30% (p=0.000 n=20)
GlobalInt64-32 2.087n ± 1% 3.663n ± 1% +75.54% (p=0.000 n=20)
GlobalInt64Parallel-32 0.1042n ± 1% 0.2026n ± 1% +94.48% (p=0.000 n=20)
GlobalUint64-32 2.263n ± 2% 3.724n ± 1% +64.57% (p=0.000 n=20)
GlobalUint64Parallel-32 0.1019n ± 1% 0.1973n ± 1% +93.67% (p=0.000 n=20)
Int64-32 1.771n ± 1% 1.774n ± 1% ~ (p=0.449 n=20)
Uint64-32 1.863n ± 2% 1.866n ± 1% ~ (p=0.364 n=20)
GlobalIntN1000-32 3.134n ± 3% 4.730n ± 2% +50.95% (p=0.000 n=20)
IntN1000-32 2.489n ± 1% 2.489n ± 1% ~ (p=0.683 n=20)
Int64N1000-32 2.521n ± 1% 2.516n ± 1% ~ (p=0.394 n=20)
Int64N1e8-32 2.479n ± 1% 2.478n ± 2% ~ (p=0.743 n=20)
Int64N1e9-32 2.530n ± 2% 2.514n ± 2% ~ (p=0.193 n=20)
Int64N2e9-32 2.501n ± 1% 2.494n ± 1% ~ (p=0.616 n=20)
Int64N1e18-32 3.227n ± 1% 3.205n ± 1% ~ (p=0.101 n=20)
Int64N2e18-32 3.647n ± 1% 3.599n ± 1% ~ (p=0.019 n=20)
Int64N4e18-32 5.135n ± 1% 5.069n ± 2% ~ (p=0.034 n=20)
Int32N1000-32 2.657n ± 1% 2.637n ± 1% ~ (p=0.180 n=20)
Int32N1e8-32 2.636n ± 1% 2.636n ± 1% ~ (p=0.763 n=20)
Int32N1e9-32 2.660n ± 2% 2.638n ± 1% ~ (p=0.358 n=20)
Int32N2e9-32 2.662n ± 2% 2.618n ± 2% ~ (p=0.064 n=20)
Float32-32 2.272n ± 2% 2.239n ± 2% ~ (p=0.194 n=20)
Float64-32 2.272n ± 1% 2.286n ± 2% ~ (p=0.763 n=20)
ExpFloat64-32 3.762n ± 1% 3.744n ± 1% ~ (p=0.171 n=20)
NormFloat64-32 3.706n ± 1% 3.655n ± 2% ~ (p=0.066 n=20)
Perm3-32 32.93n ± 3% 34.62n ± 1% +5.13% (p=0.000 n=20)
Perm30-32 202.9n ± 1% 204.0n ± 1% ~ (p=0.482 n=20)
Perm30ViaShuffle-32 115.0n ± 1% 114.9n ± 1% ~ (p=0.358 n=20)
ShuffleOverhead-32 112.8n ± 1% 112.7n ± 1% ~ (p=0.692 n=20)
Concurrent-32 2.107n ± 0% 3.725n ± 1% +76.75% (p=0.000 n=20)
goos: darwin
goarch: arm64
pkg: math/rand/v2
│ bbb48afeb7.arm64 │ 5cf807d1ea.arm64 │
│ sec/op │ sec/op vs base │
ChaCha8-8 2.480n ± 0% 2.429n ± 0% -2.04% (p=0.000 n=20)
PCG_DXSM-8 2.531n ± 0% 2.530n ± 0% ~ (p=0.877 n=20)
SourceUint64-8 2.534n ± 0% 2.533n ± 0% ~ (p=0.732 n=20)
GlobalInt64-8 2.172n ± 1% 4.794n ± 0% +120.67% (p=0.000 n=20)
GlobalInt64Parallel-8 0.4320n ± 0% 0.9605n ± 0% +122.32% (p=0.000 n=20)
GlobalUint64-8 2.182n ± 0% 4.770n ± 0% +118.58% (p=0.000 n=20)
GlobalUint64Parallel-8 0.4307n ± 0% 0.9583n ± 0% +122.51% (p=0.000 n=20)
Int64-8 4.107n ± 0% 4.104n ± 0% ~ (p=0.416 n=20)
Uint64-8 4.080n ± 0% 4.080n ± 0% ~ (p=0.052 n=20)
GlobalIntN1000-8 2.814n ± 2% 5.643n ± 0% +100.50% (p=0.000 n=20)
IntN1000-8 4.141n ± 0% 4.139n ± 0% ~ (p=0.140 n=20)
Int64N1000-8 4.140n ± 0% 4.140n ± 0% ~ (p=0.313 n=20)
Int64N1e8-8 4.140n ± 0% 4.139n ± 0% ~ (p=0.103 n=20)
Int64N1e9-8 4.139n ± 0% 4.140n ± 0% ~ (p=0.761 n=20)
Int64N2e9-8 4.140n ± 0% 4.140n ± 0% ~ (p=0.636 n=20)
Int64N1e18-8 5.266n ± 0% 5.326n ± 1% +1.14% (p=0.001 n=20)
Int64N2e18-8 6.052n ± 0% 6.167n ± 0% +1.90% (p=0.000 n=20)
Int64N4e18-8 8.826n ± 0% 9.051n ± 0% +2.55% (p=0.000 n=20)
Int32N1000-8 4.127n ± 0% 4.132n ± 0% +0.12% (p=0.000 n=20)
Int32N1e8-8 4.126n ± 0% 4.131n ± 0% +0.12% (p=0.000 n=20)
Int32N1e9-8 4.127n ± 0% 4.132n ± 0% +0.12% (p=0.000 n=20)
Int32N2e9-8 4.132n ± 0% 4.131n ± 0% ~ (p=0.017 n=20)
Float32-8 4.109n ± 0% 4.105n ± 0% ~ (p=0.379 n=20)
Float64-8 4.107n ± 0% 4.106n ± 0% ~ (p=0.867 n=20)
ExpFloat64-8 5.339n ± 0% 5.383n ± 0% +0.82% (p=0.000 n=20)
NormFloat64-8 5.735n ± 0% 5.737n ± 1% ~ (p=0.856 n=20)
Perm3-8 26.65n ± 0% 26.80n ± 1% +0.58% (p=0.000 n=20)
Perm30-8 194.8n ± 1% 197.0n ± 0% +1.18% (p=0.000 n=20)
Perm30ViaShuffle-8 156.6n ± 0% 157.6n ± 1% +0.61% (p=0.000 n=20)
ShuffleOverhead-8 124.9n ± 0% 125.5n ± 0% +0.52% (p=0.000 n=20)
Concurrent-8 2.434n ± 3% 5.066n ± 0% +108.09% (p=0.000 n=20)
goos: linux
goarch: 386
pkg: math/rand/v2
cpu: AMD Ryzen 9 7950X 16-Core Processor
│ bbb48afeb7.386 │ 5cf807d1ea.386 │
│ sec/op │ sec/op vs base │
ChaCha8-32 11.295n ± 1% 4.748n ± 2% -57.96% (p=0.000 n=20)
PCG_DXSM-32 7.693n ± 1% 7.738n ± 2% ~ (p=0.542 n=20)
SourceUint64-32 7.658n ± 2% 7.622n ± 2% ~ (p=0.344 n=20)
GlobalInt64-32 3.473n ± 2% 7.526n ± 2% +116.73% (p=0.000 n=20)
GlobalInt64Parallel-32 0.3198n ± 0% 0.5444n ± 0% +70.22% (p=0.000 n=20)
GlobalUint64-32 3.612n ± 0% 7.575n ± 1% +109.69% (p=0.000 n=20)
GlobalUint64Parallel-32 0.3168n ± 0% 0.5403n ± 0% +70.51% (p=0.000 n=20)
Int64-32 7.673n ± 2% 7.789n ± 1% ~ (p=0.122 n=20)
Uint64-32 7.773n ± 1% 7.827n ± 2% ~ (p=0.920 n=20)
GlobalIntN1000-32 6.268n ± 1% 9.581n ± 1% +52.87% (p=0.000 n=20)
IntN1000-32 10.33n ± 2% 10.45n ± 1% ~ (p=0.233 n=20)
Int64N1000-32 10.98n ± 2% 11.01n ± 1% ~ (p=0.401 n=20)
Int64N1e8-32 11.19n ± 2% 10.97n ± 1% ~ (p=0.033 n=20)
Int64N1e9-32 11.06n ± 1% 11.08n ± 1% ~ (p=0.498 n=20)
Int64N2e9-32 11.10n ± 1% 11.01n ± 2% ~ (p=0.995 n=20)
Int64N1e18-32 15.23n ± 2% 15.04n ± 1% ~ (p=0.973 n=20)
Int64N2e18-32 15.89n ± 1% 15.85n ± 1% ~ (p=0.409 n=20)
Int64N4e18-32 18.96n ± 2% 19.34n ± 2% ~ (p=0.048 n=20)
Int32N1000-32 10.46n ± 2% 10.44n ± 2% ~ (p=0.480 n=20)
Int32N1e8-32 10.46n ± 2% 10.49n ± 2% ~ (p=0.951 n=20)
Int32N1e9-32 10.28n ± 2% 10.26n ± 1% ~ (p=0.431 n=20)
Int32N2e9-32 10.50n ± 2% 10.44n ± 2% ~ (p=0.249 n=20)
Float32-32 13.80n ± 2% 13.80n ± 2% ~ (p=0.751 n=20)
Float64-32 23.55n ± 2% 23.87n ± 0% ~ (p=0.408 n=20)
ExpFloat64-32 15.36n ± 1% 15.29n ± 2% ~ (p=0.316 n=20)
NormFloat64-32 13.57n ± 1% 13.79n ± 1% +1.66% (p=0.005 n=20)
Perm3-32 45.70n ± 2% 46.99n ± 2% +2.81% (p=0.001 n=20)
Perm30-32 399.0n ± 1% 403.8n ± 1% +1.19% (p=0.006 n=20)
Perm30ViaShuffle-32 349.0n ± 1% 350.4n ± 1% ~ (p=0.909 n=20)
ShuffleOverhead-32 322.3n ± 1% 323.8n ± 1% ~ (p=0.410 n=20)
Concurrent-32 3.331n ± 1% 7.312n ± 1% +119.50% (p=0.000 n=20)
For #61716.
Change-Id: Ibdddeed85c34d9ae397289dc899e04d4845f9ed2
Reviewed-on: https://go-review.googlesource.com/c/go/+/516860
Reviewed-by: Michael Pratt <mpratt@google.com>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
2023-08-06 13:26:28 +10:00
|
|
|
r := int(rand())
|
2024-01-31 03:51:34 +00:00
|
|
|
offset := uint8(r >> h.B & (abi.MapBucketCount - 1))
|
2023-04-03 20:14:06 +08:00
|
|
|
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 {
|
2024-01-31 03:51:34 +00:00
|
|
|
for i := uintptr(0); i < abi.MapBucketCount; i++ {
|
|
|
|
|
offi := (i + uintptr(offset)) & (abi.MapBucketCount - 1)
|
2023-04-03 20:14:06 +08:00
|
|
|
if isEmpty(b.tophash[offi]) {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if h.flags&hashWriting != 0 {
|
|
|
|
|
fatal("concurrent map read and map write")
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-31 03:51:34 +00:00
|
|
|
ele := add(unsafe.Pointer(b), dataOffset+abi.MapBucketCount*uintptr(t.KeySize)+offi*uintptr(t.ValueSize))
|
2023-04-03 20:14:06 +08:00
|
|
|
if t.IndirectElem() {
|
|
|
|
|
ele = *((*unsafe.Pointer)(ele))
|
|
|
|
|
}
|
|
|
|
|
if s.len >= s.cap {
|
|
|
|
|
fatal("concurrent map read and map write")
|
|
|
|
|
}
|
2023-10-13 10:34:30 -07:00
|
|
|
typedmemmove(t.Elem, add(s.array, uintptr(s.len)*uintptr(t.Elem.Size())), ele)
|
2023-04-03 20:14:06 +08:00
|
|
|
s.len++
|
|
|
|
|
}
|
|
|
|
|
b = b.overflow(t)
|
|
|
|
|
}
|
|
|
|
|
}
|