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. // Set a specified key to a given value. Grow the map if necessary.
//go:nobounds //go:nobounds
func hashmapSet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint32, keyEqual func(x, y unsafe.Pointer, n uintptr) bool) { 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 numBuckets := uintptr(1) << m.bucketBits
bucketNumber := (uintptr(hash) & (numBuckets - 1)) bucketNumber := (uintptr(hash) & (numBuckets - 1))
bucketSize := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*8 bucketSize := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*8
bucketAddr := uintptr(m.buckets) + bucketSize*bucketNumber bucketAddr := uintptr(m.buckets) + bucketSize*bucketNumber
bucket := (*hashmapBucket)(unsafe.Pointer(bucketAddr)) bucket := (*hashmapBucket)(unsafe.Pointer(bucketAddr))
var lastBucket *hashmapBucket
tophash := hashmapTopHash(hash)
// See whether the key already exists somewhere. // See whether the key already exists somewhere.
var emptySlotKey unsafe.Pointer var emptySlotKey unsafe.Pointer
@ -104,9 +111,9 @@ func hashmapSet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint3
for bucket != nil { for bucket != nil {
for i := uintptr(0); i < 8; i++ { for i := uintptr(0); i < 8; i++ {
slotKeyOffset := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*uintptr(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) 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 { if bucket.tophash[i] == 0 && emptySlotKey == nil {
// Found an empty slot, store it for if we couldn't find an // Found an empty slot, store it for if we couldn't find an
// existing slot. // existing slot.
@ -115,7 +122,7 @@ func hashmapSet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint3
emptySlotTophash = &bucket.tophash[i] emptySlotTophash = &bucket.tophash[i]
} }
if bucket.tophash[i] == tophash { 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)) { if keyEqual(key, slotKey, uintptr(m.keySize)) {
// found same key, replace it // found same key, replace it
memcpy(slotValue, value, uintptr(m.valueSize)) 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 bucket = bucket.next
} }
if emptySlotKey != nil { if emptySlotKey == nil {
m.count++ // Add a new bucket to the bucket chain.
memcpy(emptySlotKey, key, uintptr(m.keySize)) // TODO: rebalance if necessary to avoid O(n) insert and lookup time.
memcpy(emptySlotValue, value, uintptr(m.valueSize)) lastBucket.next = (*hashmapBucket)(hashmapInsertIntoNewBucket(m, key, value, tophash))
*emptySlotTophash = tophash
return 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. // 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 // test preallocated map
squares := make(map[int]int, 200) squares := make(map[int]int, 200)
for i := 0; i < 100; i++ { testBigMap(squares, 100)
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)
}
}
}
println("tested preallocated map") 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) { func readMap(m map[string]int, key string) {
@ -73,3 +73,22 @@ func lookup(m map[string]int, key string) {
value, ok := m[key] value, ok := m[key]
println("lookup with comma-ok:", key, value, ok) 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 4321
5555 5555
tested preallocated map tested preallocated map
tested growing of a map