Skip to content

Commit

Permalink
add bulk deletion feature
Browse files Browse the repository at this point in the history
  • Loading branch information
alphadose committed Oct 3, 2022
1 parent 2c0bada commit 8cde6bd
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 49 deletions.
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,19 +41,21 @@ func main() {

mep.Set(2, "two")
mep.Set(3, "three")
mep.Set(4, "four")

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

// delete values
mep.Del(1)
mep.Del(2)
mep.Del(3)
mep.Del(1) // delete a value
mep.Del(0) // delete is safe even if a key doesn't exists

// bulk deletion is supported too in the same API call
// has better performance than deleting keys one by one
mep.Del(2, 3, 4)

if mep.Len() == 0 {
println("cleanup complete")
}
Expand Down
6 changes: 2 additions & 4 deletions e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,14 +102,12 @@ func TestDelete(t *testing.T) {
m.Set(1, cat)
m.Set(2, tiger)
m.Del(0)
m.Del(3)
m.Del(3, 4, 5)
if m.Len() != 2 {
t.Error("map should contain exactly two elements.")
}

m.Del(1)
m.Del(1)
m.Del(2)
m.Del(1, 2, 1)

if m.Len() != 0 {
t.Error("map should be empty.")
Expand Down
3 changes: 3 additions & 0 deletions examples/concurrent_deletion.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// from bug https://github.com/alphadose/haxmap/issues/14
// fixed in v1.0.1 and above

package main

import (
Expand Down
13 changes: 8 additions & 5 deletions examples/simple.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ func main() {

// set a value (overwrites existing value if present)
mep.Set(1, "one")

// get the value and print it
val, ok := mep.Get(1)
if ok {
Expand All @@ -20,19 +21,21 @@ func main() {

mep.Set(2, "two")
mep.Set(3, "three")
mep.Set(4, "four")

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

// delete values
mep.Del(1)
mep.Del(2)
mep.Del(3)
mep.Del(1) // delete a value
mep.Del(0) // delete is safe even if a key doesn't exists

// bulk deletion is supported too in the same API call
// has better performance than deleting keys one by one
mep.Del(2, 3, 4)

if mep.Len() == 0 {
println("cleanup complete")
}
Expand Down
93 changes: 57 additions & 36 deletions map.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package haxmap

import (
"reflect"
"sort"
"strconv"
"sync/atomic"
"unsafe"
Expand Down Expand Up @@ -49,6 +50,12 @@ type (
resizing atomicUint32
numItems atomicUintptr
}

// used in deletion of map elements
deletionRequest[K hashable] struct {
keyHash uintptr
key K
}
)

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

// Del lazily deletes the key from the map
// does nothing if key is absemt
func (m *Map[K, V]) Del(key K) {
var (
h = m.hasher(key)
elem = m.metadata.Load().indexElement(h)
)

loop:
for ; elem != nil; elem = elem.next() {
if elem.keyHash == h && elem.key == key {
break loop
}
if elem.keyHash > h {
return
}
}
if elem == nil {
// Del deletes key/keys from the map
// Bulk deletion is more efficient than deleting keys one by one
func (m *Map[K, V]) Del(keys ...K) {
if len(keys) == 0 {
return
}
// mark node for deletion
elem.remove()

for iter := m.listHead.next(); iter != nil; iter = iter.next() {
var (
size = len(keys)
delQ = make([]deletionRequest[K], size)
elem = m.listHead.next()
iter = 0
)
for idx := 0; idx < size; idx++ {
delQ[idx].keyHash, delQ[idx].key = m.hasher(keys[idx]), keys[idx]
}

// remove node from map index if exists
for {
data := m.metadata.Load()
index := elem.keyHash >> data.keyshifts
ptr := (*unsafe.Pointer)(unsafe.Pointer(uintptr(data.data) + index*intSizeBytes))

next := elem.next()
if next != nil && elem.keyHash>>data.keyshifts != index {
next = nil // do not set index to next item if it's not the same slice index
// sort in ascending order of keyhash
sort.Slice(delQ, func(i, j int) bool {
return delQ[i].keyHash < delQ[j].keyHash
})

for elem != nil && iter < size {
if elem.keyHash == delQ[iter].keyHash && elem.key == delQ[iter].key {
elem.remove() // mark node for deletion
m.removeItemFromIndex(elem)
iter++
elem = elem.next()
} else if elem.keyHash > delQ[iter].keyHash {
iter++
} else {
elem = elem.next()
}
atomic.CompareAndSwapPointer(ptr, unsafe.Pointer(elem), unsafe.Pointer(next))
}

if data == m.metadata.Load() { // check that no resize happened
m.numItems.Add(marked)
return
}
// iterate list from start to end to remove the marked nodes
for iter := m.listHead; iter != nil; iter = iter.next() {
}
}

Expand Down Expand Up @@ -218,6 +219,26 @@ func (m *Map[K, V]) fillIndexItems(mapData *metadata[K, V]) {
}
}

// removeItemFromIndex removes an item from the map index
func (m *Map[K, V]) removeItemFromIndex(item *element[K, V]) {
for {
data := m.metadata.Load()
index := item.keyHash >> data.keyshifts
ptr := (*unsafe.Pointer)(unsafe.Pointer(uintptr(data.data) + index*intSizeBytes))

next := item.next()
if next != nil && item.keyHash>>data.keyshifts != index {
next = nil // do not set index to next item if it's not the same slice index
}
atomic.CompareAndSwapPointer(ptr, unsafe.Pointer(item), unsafe.Pointer(next))

if data == m.metadata.Load() { // check that no resize happened
m.numItems.Add(marked)
return
}
}
}

// grow to the new size
func (m *Map[K, V]) grow(newSize uintptr) {
for {
Expand Down

0 comments on commit 8cde6bd

Please sign in to comment.