runtime: implement growing hashmaps

Add support for growing hashmaps beyond their initial size.
Этот коммит содержится в:
Ayke van Laethem 2019-05-13 20:18:31 +02:00 коммит произвёл Ron Evans
родитель 55fc7b904a
коммит 763b9d7d10
3 изменённых файлов: 67 добавлений и 19 удалений

Просмотреть файл

@ -89,13 +89,20 @@ func hashmapLen(m *hashmap) int {
// Set a specified key to a given value. Grow the map if necessary.
//go:nobounds
func hashmapSet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint32, keyEqual func(x, y unsafe.Pointer, n uintptr) bool) {
tophash := hashmapTopHash(hash)
if m.buckets == nil {
// No bucket was allocated yet, do so now.
m.buckets = unsafe.Pointer(hashmapInsertIntoNewBucket(m, key, value, tophash))
return
}
numBuckets := uintptr(1) << m.bucketBits
bucketNumber := (uintptr(hash) & (numBuckets - 1))
bucketSize := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*8
bucketAddr := uintptr(m.buckets) + bucketSize*bucketNumber
bucket := (*hashmapBucket)(unsafe.Pointer(bucketAddr))
tophash := hashmapTopHash(hash)
var lastBucket *hashmapBucket
// See whether the key already exists somewhere.
var emptySlotKey unsafe.Pointer
@ -104,9 +111,9 @@ func hashmapSet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint3
for bucket != nil {
for i := uintptr(0); i < 8; i++ {
slotKeyOffset := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*uintptr(i)
slotKey := unsafe.Pointer(bucketAddr + slotKeyOffset)
slotKey := unsafe.Pointer(uintptr(unsafe.Pointer(bucket)) + slotKeyOffset)
slotValueOffset := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*uintptr(i)
slotValue := unsafe.Pointer(bucketAddr + slotValueOffset)
slotValue := unsafe.Pointer(uintptr(unsafe.Pointer(bucket)) + slotValueOffset)
if bucket.tophash[i] == 0 && emptySlotKey == nil {
// Found an empty slot, store it for if we couldn't find an
// existing slot.
@ -115,7 +122,7 @@ func hashmapSet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint3
emptySlotTophash = &bucket.tophash[i]
}
if bucket.tophash[i] == tophash {
// Could be an existing value that's the same.
// Could be an existing key that's the same.
if keyEqual(key, slotKey, uintptr(m.keySize)) {
// found same key, replace it
memcpy(slotValue, value, uintptr(m.valueSize))
@ -123,16 +130,37 @@ func hashmapSet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint3
}
}
}
lastBucket = bucket
bucket = bucket.next
}
if emptySlotKey != nil {
m.count++
memcpy(emptySlotKey, key, uintptr(m.keySize))
memcpy(emptySlotValue, value, uintptr(m.valueSize))
*emptySlotTophash = tophash
if emptySlotKey == nil {
// Add a new bucket to the bucket chain.
// TODO: rebalance if necessary to avoid O(n) insert and lookup time.
lastBucket.next = (*hashmapBucket)(hashmapInsertIntoNewBucket(m, key, value, tophash))
return
}
panic("todo: hashmap: grow bucket")
m.count++
memcpy(emptySlotKey, key, uintptr(m.keySize))
memcpy(emptySlotValue, value, uintptr(m.valueSize))
*emptySlotTophash = tophash
}
// hashmapInsertIntoNewBucket creates a new bucket, inserts the given key and
// value into the bucket, and returns a pointer to this bucket.
func hashmapInsertIntoNewBucket(m *hashmap, key, value unsafe.Pointer, tophash uint8) *hashmapBucket {
bucketBufSize := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*8
bucketBuf := alloc(bucketBufSize)
// Insert into the first slot, which is empty as it has just been allocated.
slotKeyOffset := unsafe.Sizeof(hashmapBucket{})
slotKey := unsafe.Pointer(uintptr(bucketBuf) + slotKeyOffset)
slotValueOffset := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8
slotValue := unsafe.Pointer(uintptr(bucketBuf) + slotValueOffset)
m.count++
memcpy(slotKey, key, uintptr(m.keySize))
memcpy(slotValue, value, uintptr(m.valueSize))
bucket := (*hashmapBucket)(bucketBuf)
bucket.tophash[0] = tophash
return bucket
}
// Get the value of a specified key, or zero the value if not found.

35
testdata/map.go предоставленный
Просмотреть файл

@ -50,15 +50,15 @@ func main() {
// test preallocated map
squares := make(map[int]int, 200)
for i := 0; i < 100; i++ {
squares[i] = i*i
for j := 0; j <= i; j++ {
if v := squares[j]; v != j*j {
println("unexpected value read back from squares map:", j, v)
}
}
}
testBigMap(squares, 100)
println("tested preallocated map")
// test growing maps
squares = make(map[int]int, 0)
testBigMap(squares, 10)
squares = make(map[int]int, 20)
testBigMap(squares, 40)
println("tested growing of a map")
}
func readMap(m map[string]int, key string) {
@ -73,3 +73,22 @@ func lookup(m map[string]int, key string) {
value, ok := m[key]
println("lookup with comma-ok:", key, value, ok)
}
func testBigMap(squares map[int]int, n int) {
for i := 0; i < n; i++ {
if len(squares) != i {
println("unexpected length:", len(squares), "at i =", i)
}
squares[i] = i*i
for j := 0; j <= i; j++ {
if v, ok := squares[j]; !ok || v != j*j {
if !ok {
println("key not found in squares map:", j)
} else {
println("unexpected value read back from squares map:", j, v)
}
return
}
}
}
}

1
testdata/map.txt предоставленный
Просмотреть файл

@ -55,3 +55,4 @@ true false 0
4321
5555
tested preallocated map
tested growing of a map