Skip to content

Commit

Permalink
perf(slice): make the IndexOf function thread-safe (#263)
Browse files Browse the repository at this point in the history
  • Loading branch information
cannian1 authored Nov 6, 2024
1 parent 8bbae69 commit a7fecfc
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 6 deletions.
36 changes: 31 additions & 5 deletions slice/slice.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"reflect"
"sort"
"strings"
"sync"
"time"

"github.com/duke-git/lancet/v2/random"
Expand All @@ -21,6 +22,7 @@ import (
var (
memoryHashMap = make(map[string]map[any]int)
memoryHashCounter = make(map[string]int)
muForMemoryHash sync.RWMutex
)

// Contain check if the target value is in the slice or not.
Expand Down Expand Up @@ -1174,23 +1176,46 @@ func IndexOf[T comparable](arr []T, val T) int {
limit := 10
// gets the hash value of the array as the key of the hash table.
key := fmt.Sprintf("%p", arr)

muForMemoryHash.RLock()
// determines whether the hash table is empty. If so, the hash table is created.
if memoryHashMap[key] == nil {
memoryHashMap[key] = make(map[any]int)
// iterate through the array, adding the value and index of each element to the hash table.
for i := len(arr) - 1; i >= 0; i-- {
memoryHashMap[key][arr[i]] = i

muForMemoryHash.RUnlock()
muForMemoryHash.Lock()

if memoryHashMap[key] == nil {
memoryHashMap[key] = make(map[any]int)
// iterate through the array, adding the value and index of each element to the hash table.
for i := len(arr) - 1; i >= 0; i-- {
memoryHashMap[key][arr[i]] = i
}
}

muForMemoryHash.Unlock()
} else {
muForMemoryHash.RUnlock()
}

muForMemoryHash.Lock()
// update the hash table counter.
memoryHashCounter[key]++
muForMemoryHash.Unlock()

// use the hash table to find the specified value. If found, the index is returned.
if index, ok := memoryHashMap[key][val]; ok {
muForMemoryHash.RLock()
index, ok := memoryHashMap[key][val]
muForMemoryHash.RUnlock()

if ok {
muForMemoryHash.RLock()
// calculate the memory usage of the hash table.
size := len(memoryHashMap)
muForMemoryHash.RUnlock()

// If the memory usage of the hash table exceeds the memory limit, the hash table with the lowest counter is cleared.
if size > limit {
muForMemoryHash.Lock()
var minKey string
var minVal int
for k, v := range memoryHashCounter {
Expand All @@ -1204,6 +1229,7 @@ func IndexOf[T comparable](arr []T, val T) int {
}
delete(memoryHashMap, minKey)
delete(memoryHashCounter, minKey)
muForMemoryHash.Unlock()
}
return index
}
Expand Down
2 changes: 1 addition & 1 deletion slice/slice_concurrent.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ func FilterConcurrent[T any](slice []T, predicate func(index int, item T) bool,
return result
}

// UniqueByParallel removes duplicate elements from the slice by parallel
// UniqueByConcurrent removes duplicate elements from the slice by parallel
// The comparator function is used to compare the elements
// The numThreads parameter specifies the number of threads to use
// If numThreads is less than or equal to 0, it will be set to 1
Expand Down
32 changes: 32 additions & 0 deletions slice/slice_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"reflect"
"strconv"
"strings"
"sync"
"testing"

"github.com/duke-git/lancet/v2/internal"
Expand Down Expand Up @@ -1367,6 +1368,37 @@ func TestIndexOf(t *testing.T) {
assert.Equal(-1, IndexOf(arr3, "r"))
assert.Equal(2, memoryHashCounter[key3])
assert.Equal(0, memoryHashCounter[minKey])

arr4 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

const numGoroutines = 100
var wg sync.WaitGroup
wg.Add(numGoroutines)

for i := 0; i < numGoroutines; i++ {
go func(i int) {
defer wg.Done()
index := IndexOf(arr4, i%10+1)
assert.Equal(i%10, index)
}(i)
}
wg.Wait()
}

func BenchmarkIndexOfDifferentSizes(b *testing.B) {
sizes := []int{10, 100, 1000, 10000, 100000}
for _, size := range sizes {
arr := make([]int, size)
for i := 0; i < len(arr); i++ {
arr[i] = i
}

b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_ = IndexOf(arr, size/2) // 查找数组中间的元素
}
})
}
}

func TestLastIndexOf(t *testing.T) {
Expand Down

0 comments on commit a7fecfc

Please sign in to comment.