Skip to content

Commit 8cde6bd

Browse files
committed
add bulk deletion feature
1 parent 2c0bada commit 8cde6bd

File tree

5 files changed

+76
-49
lines changed

5 files changed

+76
-49
lines changed

README.md

+6-4
Original file line numberDiff line numberDiff line change
@@ -41,19 +41,21 @@ func main() {
4141

4242
mep.Set(2, "two")
4343
mep.Set(3, "three")
44+
mep.Set(4, "four")
4445

4546
// ForEach loop to iterate over all key-value pairs and execute the given lambda
4647
mep.ForEach(func(key int, value string) bool {
4748
fmt.Printf("Key -> %d | Value -> %s\n", key, value)
4849
return true // return `true` to continue iteration and `false` to break iteration
4950
})
5051

51-
// delete values
52-
mep.Del(1)
53-
mep.Del(2)
54-
mep.Del(3)
52+
mep.Del(1) // delete a value
5553
mep.Del(0) // delete is safe even if a key doesn't exists
5654

55+
// bulk deletion is supported too in the same API call
56+
// has better performance than deleting keys one by one
57+
mep.Del(2, 3, 4)
58+
5759
if mep.Len() == 0 {
5860
println("cleanup complete")
5961
}

e2e_test.go

+2-4
Original file line numberDiff line numberDiff line change
@@ -102,14 +102,12 @@ func TestDelete(t *testing.T) {
102102
m.Set(1, cat)
103103
m.Set(2, tiger)
104104
m.Del(0)
105-
m.Del(3)
105+
m.Del(3, 4, 5)
106106
if m.Len() != 2 {
107107
t.Error("map should contain exactly two elements.")
108108
}
109109

110-
m.Del(1)
111-
m.Del(1)
112-
m.Del(2)
110+
m.Del(1, 2, 1)
113111

114112
if m.Len() != 0 {
115113
t.Error("map should be empty.")

examples/concurrent_deletion.go

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// from bug https://github.com/alphadose/haxmap/issues/14
2+
// fixed in v1.0.1 and above
3+
14
package main
25

36
import (

examples/simple.go

+8-5
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ func main() {
1212

1313
// set a value (overwrites existing value if present)
1414
mep.Set(1, "one")
15+
1516
// get the value and print it
1617
val, ok := mep.Get(1)
1718
if ok {
@@ -20,19 +21,21 @@ func main() {
2021

2122
mep.Set(2, "two")
2223
mep.Set(3, "three")
24+
mep.Set(4, "four")
2325

2426
// ForEach loop to iterate over all key-value pairs and execute the given lambda
2527
mep.ForEach(func(key int, value string) bool {
2628
fmt.Printf("Key -> %d | Value -> %s\n", key, value)
27-
return true
29+
return true // return `true` to continue iteration and `false` to break iteration
2830
})
2931

30-
// delete values
31-
mep.Del(1)
32-
mep.Del(2)
33-
mep.Del(3)
32+
mep.Del(1) // delete a value
3433
mep.Del(0) // delete is safe even if a key doesn't exists
3534

35+
// bulk deletion is supported too in the same API call
36+
// has better performance than deleting keys one by one
37+
mep.Del(2, 3, 4)
38+
3639
if mep.Len() == 0 {
3740
println("cleanup complete")
3841
}

map.go

+57-36
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package haxmap
22

33
import (
44
"reflect"
5+
"sort"
56
"strconv"
67
"sync/atomic"
78
"unsafe"
@@ -49,6 +50,12 @@ type (
4950
resizing atomicUint32
5051
numItems atomicUintptr
5152
}
53+
54+
// used in deletion of map elements
55+
deletionRequest[K hashable] struct {
56+
keyHash uintptr
57+
key K
58+
}
5259
)
5360

5461
// New returns a new HashMap instance with an optional specific initialization size
@@ -64,48 +71,42 @@ func New[K hashable, V any](size ...uintptr) *Map[K, V] {
6471
return m
6572
}
6673

67-
// Del lazily deletes the key from the map
68-
// does nothing if key is absemt
69-
func (m *Map[K, V]) Del(key K) {
70-
var (
71-
h = m.hasher(key)
72-
elem = m.metadata.Load().indexElement(h)
73-
)
74-
75-
loop:
76-
for ; elem != nil; elem = elem.next() {
77-
if elem.keyHash == h && elem.key == key {
78-
break loop
79-
}
80-
if elem.keyHash > h {
81-
return
82-
}
83-
}
84-
if elem == nil {
74+
// Del deletes key/keys from the map
75+
// Bulk deletion is more efficient than deleting keys one by one
76+
func (m *Map[K, V]) Del(keys ...K) {
77+
if len(keys) == 0 {
8578
return
8679
}
87-
// mark node for deletion
88-
elem.remove()
89-
90-
for iter := m.listHead.next(); iter != nil; iter = iter.next() {
80+
var (
81+
size = len(keys)
82+
delQ = make([]deletionRequest[K], size)
83+
elem = m.listHead.next()
84+
iter = 0
85+
)
86+
for idx := 0; idx < size; idx++ {
87+
delQ[idx].keyHash, delQ[idx].key = m.hasher(keys[idx]), keys[idx]
9188
}
9289

93-
// remove node from map index if exists
94-
for {
95-
data := m.metadata.Load()
96-
index := elem.keyHash >> data.keyshifts
97-
ptr := (*unsafe.Pointer)(unsafe.Pointer(uintptr(data.data) + index*intSizeBytes))
98-
99-
next := elem.next()
100-
if next != nil && elem.keyHash>>data.keyshifts != index {
101-
next = nil // do not set index to next item if it's not the same slice index
90+
// sort in ascending order of keyhash
91+
sort.Slice(delQ, func(i, j int) bool {
92+
return delQ[i].keyHash < delQ[j].keyHash
93+
})
94+
95+
for elem != nil && iter < size {
96+
if elem.keyHash == delQ[iter].keyHash && elem.key == delQ[iter].key {
97+
elem.remove() // mark node for deletion
98+
m.removeItemFromIndex(elem)
99+
iter++
100+
elem = elem.next()
101+
} else if elem.keyHash > delQ[iter].keyHash {
102+
iter++
103+
} else {
104+
elem = elem.next()
102105
}
103-
atomic.CompareAndSwapPointer(ptr, unsafe.Pointer(elem), unsafe.Pointer(next))
106+
}
104107

105-
if data == m.metadata.Load() { // check that no resize happened
106-
m.numItems.Add(marked)
107-
return
108-
}
108+
// iterate list from start to end to remove the marked nodes
109+
for iter := m.listHead; iter != nil; iter = iter.next() {
109110
}
110111
}
111112

@@ -218,6 +219,26 @@ func (m *Map[K, V]) fillIndexItems(mapData *metadata[K, V]) {
218219
}
219220
}
220221

222+
// removeItemFromIndex removes an item from the map index
223+
func (m *Map[K, V]) removeItemFromIndex(item *element[K, V]) {
224+
for {
225+
data := m.metadata.Load()
226+
index := item.keyHash >> data.keyshifts
227+
ptr := (*unsafe.Pointer)(unsafe.Pointer(uintptr(data.data) + index*intSizeBytes))
228+
229+
next := item.next()
230+
if next != nil && item.keyHash>>data.keyshifts != index {
231+
next = nil // do not set index to next item if it's not the same slice index
232+
}
233+
atomic.CompareAndSwapPointer(ptr, unsafe.Pointer(item), unsafe.Pointer(next))
234+
235+
if data == m.metadata.Load() { // check that no resize happened
236+
m.numItems.Add(marked)
237+
return
238+
}
239+
}
240+
}
241+
221242
// grow to the new size
222243
func (m *Map[K, V]) grow(newSize uintptr) {
223244
for {

0 commit comments

Comments
 (0)