``` feat(pet): 重构宠物繁殖系统,添加蛋孵化功能
This commit is contained in:
46
common/utils/concurrent-swiss-map/.golangci.yml
Normal file
46
common/utils/concurrent-swiss-map/.golangci.yml
Normal file
@@ -0,0 +1,46 @@
|
||||
run:
|
||||
skip-dirs:
|
||||
- swiss
|
||||
- swiss/simd
|
||||
- maphash
|
||||
skip-files:
|
||||
- "concurrent_swiss_map_benchmark_test.go"
|
||||
skip-dirs-use-default: false
|
||||
|
||||
linters-settings:
|
||||
lll:
|
||||
line-length: 140
|
||||
funlen:
|
||||
lines: 70
|
||||
|
||||
linters:
|
||||
disable-all: true
|
||||
enable:
|
||||
- bodyclose
|
||||
- depguard
|
||||
- errcheck
|
||||
- exhaustive
|
||||
- funlen
|
||||
- goconst
|
||||
- gocritic
|
||||
- gocyclo
|
||||
- revive
|
||||
- gosimple
|
||||
- govet
|
||||
- gosec
|
||||
- ineffassign
|
||||
- lll
|
||||
- misspell
|
||||
- nakedret
|
||||
- gofumpt
|
||||
- staticcheck
|
||||
- stylecheck
|
||||
- typecheck
|
||||
- unconvert
|
||||
- unparam
|
||||
- whitespace
|
||||
|
||||
service:
|
||||
golangci-lint-version: 1.50.x # use the fixed version to not introduce new linters unexpectedly
|
||||
prepare:
|
||||
- echo "here I can run custom commands, but no preparation needed for this repo"
|
||||
27
common/utils/concurrent-swiss-map/.goreleaser.yml
Normal file
27
common/utils/concurrent-swiss-map/.goreleaser.yml
Normal file
@@ -0,0 +1,27 @@
|
||||
project_name: concurrent-swiss-map
|
||||
|
||||
release:
|
||||
github:
|
||||
name: concurrent-swiss-map
|
||||
owner: mhmtszr
|
||||
|
||||
before:
|
||||
hooks:
|
||||
- go mod tidy
|
||||
|
||||
builds:
|
||||
- skip: true
|
||||
|
||||
changelog:
|
||||
sort: asc
|
||||
use: github
|
||||
filters:
|
||||
exclude:
|
||||
- '^test:'
|
||||
- '^docs:'
|
||||
- '^chore:'
|
||||
- 'merge conflict'
|
||||
- Merge pull request
|
||||
- Merge remote-tracking branch
|
||||
- Merge branch
|
||||
- go mod tidy
|
||||
21
common/utils/concurrent-swiss-map/LICENSE
Normal file
21
common/utils/concurrent-swiss-map/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Mehmet Sezer
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
103
common/utils/concurrent-swiss-map/README.md
Normal file
103
common/utils/concurrent-swiss-map/README.md
Normal file
@@ -0,0 +1,103 @@
|
||||
# Concurrent Swiss Map [![GoDoc][doc-img]][doc] [![Build Status][ci-img]][ci] [![Coverage Status][cov-img]][cov] [![Go Report Card][go-report-img]][go-report]
|
||||
|
||||
**Concurrent Swiss Map** is an open-source Go library that provides a high-performance, thread-safe generic concurrent hash map implementation designed to handle concurrent access efficiently. It's built with a focus on simplicity, speed, and reliability, making it a solid choice for scenarios where concurrent access to a hash map is crucial.
|
||||
|
||||
Uses [dolthub/swiss](https://github.com/dolthub/swiss) map implementation under the hood.
|
||||
|
||||
## Installation
|
||||
|
||||
Supports 1.18+ Go versions because of Go Generics
|
||||
|
||||
```
|
||||
go get github.com/mhmtszr/concurrent-swiss-map
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
New functions will be added soon...
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"hash/fnv"
|
||||
|
||||
csmap "github.com/mhmtszr/concurrent-swiss-map"
|
||||
)
|
||||
|
||||
func main() {
|
||||
myMap := csmap.New[string, int](
|
||||
// set the number of map shards. the default value is 32.
|
||||
csmap.WithShardCount[string, int](32),
|
||||
|
||||
// if don't set custom hasher, use the built-in maphash.
|
||||
csmap.WithCustomHasher[string, int](func(key string) uint64 {
|
||||
hash := fnv.New64a()
|
||||
hash.Write([]byte(key))
|
||||
return hash.Sum64()
|
||||
}),
|
||||
|
||||
// set the total capacity, every shard map has total capacity/shard count capacity. the default value is 0.
|
||||
csmap.WithSize[string, int](1000),
|
||||
)
|
||||
|
||||
key := "swiss-map"
|
||||
myMap.Store(key, 10)
|
||||
|
||||
val, ok := myMap.Load(key)
|
||||
println("load val:", val, "exists:", ok)
|
||||
|
||||
deleted := myMap.Delete(key)
|
||||
println("deleted:", deleted)
|
||||
|
||||
ok = myMap.Has(key)
|
||||
println("has:", ok)
|
||||
|
||||
empty := myMap.IsEmpty()
|
||||
println("empty:", empty)
|
||||
|
||||
myMap.SetIfAbsent(key, 11)
|
||||
|
||||
myMap.Range(func(key string, value int) (stop bool) {
|
||||
println("range:", key, value)
|
||||
return true
|
||||
})
|
||||
|
||||
count := myMap.Count()
|
||||
println("count:", count)
|
||||
|
||||
// Output:
|
||||
// load val: 10 exists: true
|
||||
// deleted: true
|
||||
// has: false
|
||||
// empty: true
|
||||
// range: swiss-map 11
|
||||
// count: 1
|
||||
}
|
||||
```
|
||||
|
||||
## Basic Architecture
|
||||

|
||||
|
||||
## Benchmark Test
|
||||
Benchmark was made on:
|
||||
- Apple M1 Max
|
||||
- 32 GB memory
|
||||
|
||||
Benchmark test results can be obtained by running [this file](concurrent_swiss_map_benchmark_test.go) on local computers.
|
||||
|
||||

|
||||
|
||||
### Benchmark Results
|
||||
|
||||
- Memory usage of the concurrent swiss map is better than other map implementations in all checked test scenarios.
|
||||
- In high concurrent systems, the concurrent swiss map is faster, but in systems containing few concurrent operations, it works similarly to RWMutexMap.
|
||||
|
||||
[doc-img]: https://godoc.org/github.com/mhmtszr/concurrent-swiss-map?status.svg
|
||||
[doc]: https://godoc.org/github.com/mhmtszr/concurrent-swiss-map
|
||||
[ci-img]: https://github.com/mhmtszr/concurrent-swiss-map/actions/workflows/build-test.yml/badge.svg
|
||||
[ci]: https://github.com/mhmtszr/concurrent-swiss-map/actions/workflows/build-test.yml
|
||||
[cov-img]: https://codecov.io/gh/mhmtszr/concurrent-swiss-map/branch/master/graph/badge.svg
|
||||
[cov]: https://codecov.io/gh/mhmtszr/concurrent-swiss-map
|
||||
[go-report-img]: https://goreportcard.com/badge/github.com/mhmtszr/concurrent-swiss-map
|
||||
[go-report]: https://goreportcard.com/report/github.com/mhmtszr/concurrent-swiss-map
|
||||
BIN
common/utils/concurrent-swiss-map/benchmark.png
Normal file
BIN
common/utils/concurrent-swiss-map/benchmark.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 158 KiB |
286
common/utils/concurrent-swiss-map/concurrent_swiss_map.go
Normal file
286
common/utils/concurrent-swiss-map/concurrent_swiss_map.go
Normal file
@@ -0,0 +1,286 @@
|
||||
package csmap
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"sync"
|
||||
|
||||
"github.com/mhmtszr/concurrent-swiss-map/maphash"
|
||||
"github.com/panjf2000/ants/v2"
|
||||
|
||||
"github.com/mhmtszr/concurrent-swiss-map/swiss"
|
||||
)
|
||||
|
||||
type CsMap[K comparable, V any] struct {
|
||||
hasher func(key K) uint64
|
||||
shards []shard[K, V]
|
||||
shardCount uint64
|
||||
size uint64
|
||||
}
|
||||
|
||||
type HashShardPair[K comparable, V any] struct {
|
||||
shard shard[K, V]
|
||||
hash uint64
|
||||
}
|
||||
|
||||
type shard[K comparable, V any] struct {
|
||||
items *swiss.Map[K, V]
|
||||
*sync.RWMutex
|
||||
}
|
||||
|
||||
// OptFunc is a type that is used in New function for passing options.
|
||||
type OptFunc[K comparable, V any] func(o *CsMap[K, V])
|
||||
|
||||
// New function creates *CsMap[K, V].
|
||||
func New[K comparable, V any](options ...OptFunc[K, V]) *CsMap[K, V] {
|
||||
m := CsMap[K, V]{
|
||||
hasher: maphash.NewHasher[K]().Hash,
|
||||
shardCount: 32,
|
||||
}
|
||||
for _, option := range options {
|
||||
option(&m)
|
||||
}
|
||||
|
||||
m.shards = make([]shard[K, V], m.shardCount)
|
||||
|
||||
for i := 0; i < int(m.shardCount); i++ {
|
||||
m.shards[i] = shard[K, V]{items: swiss.NewMap[K, V](uint32((m.size / m.shardCount) + 1)), RWMutex: &sync.RWMutex{}}
|
||||
}
|
||||
return &m
|
||||
}
|
||||
|
||||
// Create creates *CsMap.
|
||||
//
|
||||
// Deprecated: New function should be used instead.
|
||||
func Create[K comparable, V any](options ...func(options *CsMap[K, V])) *CsMap[K, V] {
|
||||
m := CsMap[K, V]{
|
||||
hasher: maphash.NewHasher[K]().Hash,
|
||||
shardCount: 32,
|
||||
}
|
||||
for _, option := range options {
|
||||
option(&m)
|
||||
}
|
||||
|
||||
m.shards = make([]shard[K, V], m.shardCount)
|
||||
|
||||
for i := 0; i < int(m.shardCount); i++ {
|
||||
m.shards[i] = shard[K, V]{items: swiss.NewMap[K, V](uint32((m.size / m.shardCount) + 1)), RWMutex: &sync.RWMutex{}}
|
||||
}
|
||||
return &m
|
||||
}
|
||||
|
||||
func WithShardCount[K comparable, V any](count uint64) func(csMap *CsMap[K, V]) {
|
||||
return func(csMap *CsMap[K, V]) {
|
||||
csMap.shardCount = count
|
||||
}
|
||||
}
|
||||
|
||||
func WithCustomHasher[K comparable, V any](h func(key K) uint64) func(csMap *CsMap[K, V]) {
|
||||
return func(csMap *CsMap[K, V]) {
|
||||
csMap.hasher = h
|
||||
}
|
||||
}
|
||||
|
||||
func WithSize[K comparable, V any](size uint64) func(csMap *CsMap[K, V]) {
|
||||
return func(csMap *CsMap[K, V]) {
|
||||
csMap.size = size
|
||||
}
|
||||
}
|
||||
|
||||
func (m *CsMap[K, V]) getShard(key K) HashShardPair[K, V] {
|
||||
u := m.hasher(key)
|
||||
return HashShardPair[K, V]{
|
||||
hash: u,
|
||||
shard: m.shards[u%m.shardCount],
|
||||
}
|
||||
}
|
||||
|
||||
func (m *CsMap[K, V]) Store(key K, value V) {
|
||||
hashShardPair := m.getShard(key)
|
||||
shard := hashShardPair.shard
|
||||
shard.Lock()
|
||||
shard.items.PutWithHash(key, value, hashShardPair.hash)
|
||||
shard.Unlock()
|
||||
}
|
||||
|
||||
func (m *CsMap[K, V]) Delete(key K) bool {
|
||||
hashShardPair := m.getShard(key)
|
||||
shard := hashShardPair.shard
|
||||
shard.Lock()
|
||||
defer shard.Unlock()
|
||||
return shard.items.DeleteWithHash(key, hashShardPair.hash)
|
||||
}
|
||||
|
||||
func (m *CsMap[K, V]) DeleteIf(key K, condition func(value V) bool) bool {
|
||||
hashShardPair := m.getShard(key)
|
||||
shard := hashShardPair.shard
|
||||
shard.Lock()
|
||||
defer shard.Unlock()
|
||||
value, ok := shard.items.GetWithHash(key, hashShardPair.hash)
|
||||
if ok && condition(value) {
|
||||
return shard.items.DeleteWithHash(key, hashShardPair.hash)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *CsMap[K, V]) Load(key K) (V, bool) {
|
||||
hashShardPair := m.getShard(key)
|
||||
shard := hashShardPair.shard
|
||||
shard.RLock()
|
||||
defer shard.RUnlock()
|
||||
return shard.items.GetWithHash(key, hashShardPair.hash)
|
||||
}
|
||||
|
||||
func (m *CsMap[K, V]) Has(key K) bool {
|
||||
hashShardPair := m.getShard(key)
|
||||
shard := hashShardPair.shard
|
||||
shard.RLock()
|
||||
defer shard.RUnlock()
|
||||
return shard.items.HasWithHash(key, hashShardPair.hash)
|
||||
}
|
||||
|
||||
func (m *CsMap[K, V]) Clear() {
|
||||
for i := range m.shards {
|
||||
shard := m.shards[i]
|
||||
|
||||
shard.Lock()
|
||||
shard.items.Clear()
|
||||
shard.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (m *CsMap[K, V]) Count() int {
|
||||
count := 0
|
||||
for i := range m.shards {
|
||||
shard := m.shards[i]
|
||||
shard.RLock()
|
||||
count += shard.items.Count()
|
||||
shard.RUnlock()
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
func (m *CsMap[K, V]) SetIfAbsent(key K, value V) {
|
||||
hashShardPair := m.getShard(key)
|
||||
shard := hashShardPair.shard
|
||||
shard.Lock()
|
||||
_, ok := shard.items.GetWithHash(key, hashShardPair.hash)
|
||||
if !ok {
|
||||
shard.items.PutWithHash(key, value, hashShardPair.hash)
|
||||
}
|
||||
shard.Unlock()
|
||||
}
|
||||
|
||||
func (m *CsMap[K, V]) SetIf(key K, conditionFn func(previousVale V, previousFound bool) (value V, set bool)) {
|
||||
hashShardPair := m.getShard(key)
|
||||
shard := hashShardPair.shard
|
||||
shard.Lock()
|
||||
value, found := shard.items.GetWithHash(key, hashShardPair.hash)
|
||||
value, ok := conditionFn(value, found)
|
||||
if ok {
|
||||
shard.items.PutWithHash(key, value, hashShardPair.hash)
|
||||
}
|
||||
shard.Unlock()
|
||||
}
|
||||
|
||||
func (m *CsMap[K, V]) SetIfPresent(key K, value V) {
|
||||
hashShardPair := m.getShard(key)
|
||||
shard := hashShardPair.shard
|
||||
shard.Lock()
|
||||
_, ok := shard.items.GetWithHash(key, hashShardPair.hash)
|
||||
if ok {
|
||||
shard.items.PutWithHash(key, value, hashShardPair.hash)
|
||||
}
|
||||
shard.Unlock()
|
||||
}
|
||||
|
||||
func (m *CsMap[K, V]) IsEmpty() bool {
|
||||
return m.Count() == 0
|
||||
}
|
||||
|
||||
type Tuple[K comparable, V any] struct {
|
||||
Key K
|
||||
Val V
|
||||
}
|
||||
|
||||
// Range If the callback function returns true iteration will stop.
|
||||
func (m *CsMap[K, V]) Range(f func(key K, value V) (stop bool)) {
|
||||
ch := make(chan Tuple[K, V], m.Count())
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
listenCompleted := m.listen(f, ch)
|
||||
m.produce(ctx, ch)
|
||||
listenCompleted.Wait()
|
||||
}
|
||||
|
||||
func (m *CsMap[K, V]) MarshalJSON() ([]byte, error) {
|
||||
tmp := make(map[K]V, m.Count())
|
||||
m.Range(func(key K, value V) (stop bool) {
|
||||
tmp[key] = value
|
||||
return false
|
||||
})
|
||||
return json.Marshal(tmp)
|
||||
}
|
||||
|
||||
func (m *CsMap[K, V]) UnmarshalJSON(b []byte) error {
|
||||
tmp := make(map[K]V, m.Count())
|
||||
|
||||
if err := json.Unmarshal(b, &tmp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for key, val := range tmp {
|
||||
m.Store(key, val)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *CsMap[K, V]) produce(ctx context.Context, ch chan Tuple[K, V]) {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(m.shards))
|
||||
|
||||
var producepool, _ = ants.NewPoolWithFuncGeneric(-1, func(i int) {
|
||||
defer wg.Done()
|
||||
|
||||
shard := m.shards[i]
|
||||
shard.RLock()
|
||||
shard.items.Iter(func(k K, v V) (stop bool) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return true
|
||||
default:
|
||||
ch <- Tuple[K, V]{Key: k, Val: v}
|
||||
}
|
||||
return false
|
||||
})
|
||||
shard.RUnlock()
|
||||
})
|
||||
|
||||
for i := range m.shards {
|
||||
producepool.Invoke(i)
|
||||
}
|
||||
|
||||
pool.Submit(func() {
|
||||
wg.Wait()
|
||||
close(ch)
|
||||
})
|
||||
}
|
||||
|
||||
var pool, _ = ants.NewPool(-1)
|
||||
|
||||
func (m *CsMap[K, V]) listen(f func(key K, value V) (stop bool), ch chan Tuple[K, V]) *sync.WaitGroup {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
pool.Submit(func() {
|
||||
defer wg.Done()
|
||||
for t := range ch {
|
||||
if stop := f(t.Key, t.Val); stop {
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return &wg
|
||||
}
|
||||
@@ -0,0 +1,533 @@
|
||||
//nolint:all
|
||||
package csmap_test
|
||||
|
||||
// import (
|
||||
// "fmt"
|
||||
// "runtime"
|
||||
// "strconv"
|
||||
// "sync"
|
||||
// "testing"
|
||||
//
|
||||
// "github.com/mhmtszr/concurrent-swiss-map"
|
||||
//)
|
||||
//
|
||||
|
||||
// var table = []struct {
|
||||
// total int
|
||||
// deletion int
|
||||
// }{
|
||||
// {
|
||||
// total: 100,
|
||||
// deletion: 100,
|
||||
// },
|
||||
// {
|
||||
// total: 5000000,
|
||||
// deletion: 5000000,
|
||||
// },
|
||||
//}
|
||||
|
||||
// func PrintMemUsage() {
|
||||
// var m runtime.MemStats
|
||||
// runtime.ReadMemStats(&m)
|
||||
// // For info on each, see: https://golang.org/pkg/runtime/#MemStats
|
||||
// fmt.Printf("Alloc = %v MiB", bToMb(m.Alloc))
|
||||
// fmt.Printf("\tTotalAlloc = %v MiB", bToMb(m.TotalAlloc))
|
||||
// fmt.Printf("\tSys = %v MiB", bToMb(m.Sys))
|
||||
// fmt.Printf("\tNumGC = %v\n", m.NumGC)
|
||||
//}
|
||||
//
|
||||
// func bToMb(b uint64) uint64 {
|
||||
// return b / 1024 / 1024
|
||||
//}
|
||||
|
||||
// func BenchmarkConcurrentSwissMapGoMaxProcs1(b *testing.B) {
|
||||
// runtime.GOMAXPROCS(1)
|
||||
// debug.SetGCPercent(-1)
|
||||
// debug.SetMemoryLimit(math.MaxInt64)
|
||||
// for _, v := range table {
|
||||
// b.Run(fmt.Sprintf("total: %d deletion: %d", v.total, v.deletion), func(b *testing.B) {
|
||||
// for i := 0; i < b.N; i++ {
|
||||
// m1 := csmap.Create[int, string]()
|
||||
// var wg sync.WaitGroup
|
||||
// wg.Add(3)
|
||||
// go func() {
|
||||
// defer wg.Done()
|
||||
// var wg2 sync.WaitGroup
|
||||
// wg2.Add(v.total)
|
||||
// for i := 0; i < v.total; i++ {
|
||||
// i := i
|
||||
// go func() {
|
||||
// defer wg2.Done()
|
||||
// m1.Store(i, strconv.Itoa(i))
|
||||
// }()
|
||||
// }
|
||||
// wg2.Wait()
|
||||
// }()
|
||||
//
|
||||
// go func() {
|
||||
// defer wg.Done()
|
||||
// var wg2 sync.WaitGroup
|
||||
// wg2.Add(v.total)
|
||||
// for i := 0; i < v.total; i++ {
|
||||
// i := i
|
||||
// go func() {
|
||||
// defer wg2.Done()
|
||||
// m1.Store(i, strconv.Itoa(i))
|
||||
// }()
|
||||
// }
|
||||
// wg2.Wait()
|
||||
// }()
|
||||
//
|
||||
// go func() {
|
||||
// defer wg.Done()
|
||||
// var wg2 sync.WaitGroup
|
||||
// wg2.Add(v.total)
|
||||
// for i := 0; i < v.total; i++ {
|
||||
// i := i
|
||||
// go func() {
|
||||
// defer wg2.Done()
|
||||
// m1.Store(10, strconv.Itoa(i))
|
||||
// m1.Delete(10)
|
||||
// }()
|
||||
// }
|
||||
// wg2.Wait()
|
||||
// }()
|
||||
// wg.Wait()
|
||||
//
|
||||
// wg.Add(v.deletion + v.total)
|
||||
// for i := 0; i < v.deletion; i++ {
|
||||
// i := i
|
||||
// go func() {
|
||||
// defer wg.Done()
|
||||
// m1.Delete(i)
|
||||
// }()
|
||||
// }
|
||||
//
|
||||
// for i := 0; i < v.total; i++ {
|
||||
// i := i
|
||||
// go func() {
|
||||
// defer wg.Done()
|
||||
// m1.Load(i)
|
||||
// }()
|
||||
// }
|
||||
// wg.Wait()
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// PrintMemUsage()
|
||||
//}
|
||||
|
||||
// func BenchmarkSyncMapGoMaxProcs1(b *testing.B) {
|
||||
// runtime.GOMAXPROCS(1)
|
||||
// debug.SetGCPercent(-1)
|
||||
// debug.SetMemoryLimit(math.MaxInt64)
|
||||
// for _, v := range table {
|
||||
// b.Run(fmt.Sprintf("total: %d deletion: %d", v.total, v.deletion), func(b *testing.B) {
|
||||
// for i := 0; i < b.N; i++ {
|
||||
// var m1 sync.Map
|
||||
// var wg sync.WaitGroup
|
||||
// wg.Add(3)
|
||||
// go func() {
|
||||
// defer wg.Done()
|
||||
// var wg2 sync.WaitGroup
|
||||
// wg2.Add(v.total)
|
||||
// for i := 0; i < v.total; i++ {
|
||||
// i := i
|
||||
// go func() {
|
||||
// defer wg2.Done()
|
||||
// m1.Store(i, strconv.Itoa(i))
|
||||
// }()
|
||||
// }
|
||||
// wg2.Wait()
|
||||
// }()
|
||||
//
|
||||
// go func() {
|
||||
// defer wg.Done()
|
||||
// var wg2 sync.WaitGroup
|
||||
// wg2.Add(v.total)
|
||||
// for i := 0; i < v.total; i++ {
|
||||
// i := i
|
||||
// go func() {
|
||||
// defer wg2.Done()
|
||||
// m1.Store(i, strconv.Itoa(i))
|
||||
// }()
|
||||
// }
|
||||
// wg2.Wait()
|
||||
// }()
|
||||
//
|
||||
// go func() {
|
||||
// defer wg.Done()
|
||||
// var wg2 sync.WaitGroup
|
||||
// wg2.Add(v.total)
|
||||
// for i := 0; i < v.total; i++ {
|
||||
// i := i
|
||||
// go func() {
|
||||
// defer wg2.Done()
|
||||
// m1.Store(10, strconv.Itoa(i))
|
||||
// m1.Delete(10)
|
||||
// }()
|
||||
// }
|
||||
// wg2.Wait()
|
||||
// }()
|
||||
// wg.Wait()
|
||||
//
|
||||
// wg.Add(v.deletion + v.total)
|
||||
// for i := 0; i < v.deletion; i++ {
|
||||
// i := i
|
||||
// go func() {
|
||||
// defer wg.Done()
|
||||
// m1.Delete(i)
|
||||
// }()
|
||||
// }
|
||||
//
|
||||
// for i := 0; i < v.total; i++ {
|
||||
// i := i
|
||||
// go func() {
|
||||
// defer wg.Done()
|
||||
// m1.Load(i)
|
||||
// }()
|
||||
// }
|
||||
// wg.Wait()
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// PrintMemUsage()
|
||||
//}
|
||||
|
||||
// func BenchmarkRWMutexMapGoMaxProcs1(b *testing.B) {
|
||||
// runtime.GOMAXPROCS(1)
|
||||
// debug.SetGCPercent(-1)
|
||||
// debug.SetMemoryLimit(math.MaxInt64)
|
||||
// for _, v := range table {
|
||||
// b.Run(fmt.Sprintf("total: %d deletion: %d", v.total, v.deletion), func(b *testing.B) {
|
||||
// for i := 0; i < b.N; i++ {
|
||||
// m1 := CreateTestRWMutexMap()
|
||||
// var wg sync.WaitGroup
|
||||
// wg.Add(3)
|
||||
// go func() {
|
||||
// defer wg.Done()
|
||||
// var wg2 sync.WaitGroup
|
||||
// wg2.Add(v.total)
|
||||
// for i := 0; i < v.total; i++ {
|
||||
// i := i
|
||||
// go func() {
|
||||
// defer wg2.Done()
|
||||
// m1.Store(i, strconv.Itoa(i))
|
||||
// }()
|
||||
// }
|
||||
// wg2.Wait()
|
||||
// }()
|
||||
//
|
||||
// go func() {
|
||||
// defer wg.Done()
|
||||
// var wg2 sync.WaitGroup
|
||||
// wg2.Add(v.total)
|
||||
// for i := 0; i < v.total; i++ {
|
||||
// i := i
|
||||
// go func() {
|
||||
// defer wg2.Done()
|
||||
// m1.Store(i, strconv.Itoa(i))
|
||||
// }()
|
||||
// }
|
||||
// wg2.Wait()
|
||||
// }()
|
||||
//
|
||||
// go func() {
|
||||
// defer wg.Done()
|
||||
// var wg2 sync.WaitGroup
|
||||
// wg2.Add(v.total)
|
||||
// for i := 0; i < v.total; i++ {
|
||||
// i := i
|
||||
// go func() {
|
||||
// defer wg2.Done()
|
||||
// m1.Store(10, strconv.Itoa(i))
|
||||
// m1.Delete(10)
|
||||
// }()
|
||||
// }
|
||||
// wg2.Wait()
|
||||
// }()
|
||||
// wg.Wait()
|
||||
//
|
||||
// wg.Add(v.deletion + v.total)
|
||||
// for i := 0; i < v.deletion; i++ {
|
||||
// i := i
|
||||
// go func() {
|
||||
// defer wg.Done()
|
||||
// m1.Delete(i)
|
||||
// }()
|
||||
// }
|
||||
//
|
||||
// for i := 0; i < v.total; i++ {
|
||||
// i := i
|
||||
// go func() {
|
||||
// defer wg.Done()
|
||||
// m1.Load(i)
|
||||
// }()
|
||||
// }
|
||||
// wg.Wait()
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// PrintMemUsage()
|
||||
//}
|
||||
|
||||
// func BenchmarkConcurrentSwissMapGoMaxProcsCore(b *testing.B) {
|
||||
// debug.SetGCPercent(-1)
|
||||
// debug.SetMemoryLimit(math.MaxInt64)
|
||||
// for _, v := range table {
|
||||
// b.Run(fmt.Sprintf("total: %d deletion: %d", v.total, v.deletion), func(b *testing.B) {
|
||||
// for i := 0; i < b.N; i++ {
|
||||
// m1 := csmap.Create[int, string]()
|
||||
// var wg sync.WaitGroup
|
||||
// wg.Add(3)
|
||||
// go func() {
|
||||
// defer wg.Done()
|
||||
// var wg2 sync.WaitGroup
|
||||
// wg2.Add(v.total)
|
||||
// for i := 0; i < v.total; i++ {
|
||||
// i := i
|
||||
// go func() {
|
||||
// defer wg2.Done()
|
||||
// m1.Store(i, strconv.Itoa(i))
|
||||
// }()
|
||||
// }
|
||||
// wg2.Wait()
|
||||
// }()
|
||||
//
|
||||
// go func() {
|
||||
// defer wg.Done()
|
||||
// var wg2 sync.WaitGroup
|
||||
// wg2.Add(v.total)
|
||||
// for i := 0; i < v.total; i++ {
|
||||
// i := i
|
||||
// go func() {
|
||||
// defer wg2.Done()
|
||||
// m1.Store(i, strconv.Itoa(i))
|
||||
// }()
|
||||
// }
|
||||
// wg2.Wait()
|
||||
// }()
|
||||
//
|
||||
// go func() {
|
||||
// defer wg.Done()
|
||||
// var wg2 sync.WaitGroup
|
||||
// wg2.Add(v.total)
|
||||
// for i := 0; i < v.total; i++ {
|
||||
// i := i
|
||||
// go func() {
|
||||
// defer wg2.Done()
|
||||
// m1.Store(10, strconv.Itoa(i))
|
||||
// m1.Delete(10)
|
||||
// }()
|
||||
// }
|
||||
// wg2.Wait()
|
||||
// }()
|
||||
// wg.Wait()
|
||||
//
|
||||
// wg.Add(v.deletion + v.total)
|
||||
// for i := 0; i < v.deletion; i++ {
|
||||
// i := i
|
||||
// go func() {
|
||||
// defer wg.Done()
|
||||
// m1.Delete(i)
|
||||
// }()
|
||||
// }
|
||||
//
|
||||
// for i := 0; i < v.total; i++ {
|
||||
// i := i
|
||||
// go func() {
|
||||
// defer wg.Done()
|
||||
// m1.Load(i)
|
||||
// }()
|
||||
// }
|
||||
// wg.Wait()
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// PrintMemUsage()
|
||||
//}
|
||||
|
||||
// func BenchmarkSyncMapGoMaxProcsCore(b *testing.B) {
|
||||
// debug.SetGCPercent(-1)
|
||||
// debug.SetMemoryLimit(math.MaxInt64)
|
||||
// for _, v := range table {
|
||||
// b.Run(fmt.Sprintf("total: %d deletion: %d", v.total, v.deletion), func(b *testing.B) {
|
||||
// for i := 0; i < b.N; i++ {
|
||||
// var m1 sync.Map
|
||||
// var wg sync.WaitGroup
|
||||
// wg.Add(3)
|
||||
// go func() {
|
||||
// defer wg.Done()
|
||||
// var wg2 sync.WaitGroup
|
||||
// wg2.Add(v.total)
|
||||
// for i := 0; i < v.total; i++ {
|
||||
// i := i
|
||||
// go func() {
|
||||
// defer wg2.Done()
|
||||
// m1.Store(i, strconv.Itoa(i))
|
||||
// }()
|
||||
// }
|
||||
// wg2.Wait()
|
||||
// }()
|
||||
//
|
||||
// go func() {
|
||||
// defer wg.Done()
|
||||
// var wg2 sync.WaitGroup
|
||||
// wg2.Add(v.total)
|
||||
// for i := 0; i < v.total; i++ {
|
||||
// i := i
|
||||
// go func() {
|
||||
// defer wg2.Done()
|
||||
// m1.Store(i, strconv.Itoa(i))
|
||||
// }()
|
||||
// }
|
||||
// wg2.Wait()
|
||||
// }()
|
||||
//
|
||||
// go func() {
|
||||
// defer wg.Done()
|
||||
// var wg2 sync.WaitGroup
|
||||
// wg2.Add(v.total)
|
||||
// for i := 0; i < v.total; i++ {
|
||||
// i := i
|
||||
// go func() {
|
||||
// defer wg2.Done()
|
||||
// m1.Store(10, strconv.Itoa(i))
|
||||
// m1.Delete(10)
|
||||
// }()
|
||||
// }
|
||||
// wg2.Wait()
|
||||
// }()
|
||||
// wg.Wait()
|
||||
//
|
||||
// wg.Add(v.deletion + v.total)
|
||||
// for i := 0; i < v.deletion; i++ {
|
||||
// i := i
|
||||
// go func() {
|
||||
// defer wg.Done()
|
||||
// m1.Delete(i)
|
||||
// }()
|
||||
// }
|
||||
//
|
||||
// for i := 0; i < v.total; i++ {
|
||||
// i := i
|
||||
// go func() {
|
||||
// defer wg.Done()
|
||||
// m1.Load(i)
|
||||
// }()
|
||||
// }
|
||||
// wg.Wait()
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// PrintMemUsage()
|
||||
//}
|
||||
|
||||
// func BenchmarkRWMutexMapGoMaxProcsCore(b *testing.B) {
|
||||
// debug.SetGCPercent(-1)
|
||||
// debug.SetMemoryLimit(math.MaxInt64)
|
||||
// for _, v := range table {
|
||||
// b.Run(fmt.Sprintf("total: %d deletion: %d", v.total, v.deletion), func(b *testing.B) {
|
||||
// for i := 0; i < b.N; i++ {
|
||||
// m1 := CreateTestRWMutexMap()
|
||||
// var wg sync.WaitGroup
|
||||
// wg.Add(3)
|
||||
// go func() {
|
||||
// defer wg.Done()
|
||||
// var wg2 sync.WaitGroup
|
||||
// wg2.Add(v.total)
|
||||
// for i := 0; i < v.total; i++ {
|
||||
// i := i
|
||||
// go func() {
|
||||
// defer wg2.Done()
|
||||
// m1.Store(i, strconv.Itoa(i))
|
||||
// }()
|
||||
// }
|
||||
// wg2.Wait()
|
||||
// }()
|
||||
//
|
||||
// go func() {
|
||||
// defer wg.Done()
|
||||
// var wg2 sync.WaitGroup
|
||||
// wg2.Add(v.total)
|
||||
// for i := 0; i < v.total; i++ {
|
||||
// i := i
|
||||
// go func() {
|
||||
// defer wg2.Done()
|
||||
// m1.Store(i, strconv.Itoa(i))
|
||||
// }()
|
||||
// }
|
||||
// wg2.Wait()
|
||||
// }()
|
||||
//
|
||||
// go func() {
|
||||
// defer wg.Done()
|
||||
// var wg2 sync.WaitGroup
|
||||
// wg2.Add(v.total)
|
||||
// for i := 0; i < v.total; i++ {
|
||||
// i := i
|
||||
// go func() {
|
||||
// defer wg2.Done()
|
||||
// m1.Store(10, strconv.Itoa(i))
|
||||
// m1.Delete(10)
|
||||
// }()
|
||||
// }
|
||||
// wg2.Wait()
|
||||
// }()
|
||||
// wg.Wait()
|
||||
//
|
||||
// wg.Add(v.deletion + v.total)
|
||||
// for i := 0; i < v.deletion; i++ {
|
||||
// i := i
|
||||
// go func() {
|
||||
// defer wg.Done()
|
||||
// m1.Delete(i)
|
||||
// }()
|
||||
// }
|
||||
//
|
||||
// for i := 0; i < v.total; i++ {
|
||||
// i := i
|
||||
// go func() {
|
||||
// defer wg.Done()
|
||||
// m1.Load(i)
|
||||
// }()
|
||||
// }
|
||||
// wg.Wait()
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// PrintMemUsage()
|
||||
//}
|
||||
|
||||
// type TestRWMutexMap struct {
|
||||
// m map[int]string
|
||||
// sync.RWMutex
|
||||
//}
|
||||
//
|
||||
// func CreateTestRWMutexMap() *TestRWMutexMap {
|
||||
// return &TestRWMutexMap{
|
||||
// m: make(map[int]string),
|
||||
// }
|
||||
//}
|
||||
//
|
||||
// func (m *TestRWMutexMap) Store(key int, value string) {
|
||||
// m.Lock()
|
||||
// defer m.Unlock()
|
||||
// m.m[key] = value
|
||||
//}
|
||||
//
|
||||
// func (m *TestRWMutexMap) Delete(key int) {
|
||||
// m.Lock()
|
||||
// defer m.Unlock()
|
||||
// delete(m.m, key)
|
||||
//}
|
||||
//
|
||||
// func (m *TestRWMutexMap) Load(key int) *string {
|
||||
// m.RLock()
|
||||
// defer m.RUnlock()
|
||||
// s, ok := m.m[key]
|
||||
// if !ok {
|
||||
// return nil
|
||||
// }
|
||||
// return &s
|
||||
//}
|
||||
332
common/utils/concurrent-swiss-map/concurrent_swiss_map_test.go
Normal file
332
common/utils/concurrent-swiss-map/concurrent_swiss_map_test.go
Normal file
@@ -0,0 +1,332 @@
|
||||
package csmap_test
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
csmap "github.com/mhmtszr/concurrent-swiss-map"
|
||||
)
|
||||
|
||||
func TestHas(t *testing.T) {
|
||||
myMap := csmap.New[int, string]()
|
||||
myMap.Store(1, "test")
|
||||
if !myMap.Has(1) {
|
||||
t.Fatal("1 should exists")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoad(t *testing.T) {
|
||||
myMap := csmap.New[int, string]()
|
||||
myMap.Store(1, "test")
|
||||
v, ok := myMap.Load(1)
|
||||
v2, ok2 := myMap.Load(2)
|
||||
if v != "test" || !ok {
|
||||
t.Fatal("1 should test")
|
||||
}
|
||||
if v2 != "" || ok2 {
|
||||
t.Fatal("2 should not exist")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDelete(t *testing.T) {
|
||||
myMap := csmap.New[int, string]()
|
||||
myMap.Store(1, "test")
|
||||
ok1 := myMap.Delete(20)
|
||||
ok2 := myMap.Delete(1)
|
||||
if myMap.Has(1) {
|
||||
t.Fatal("1 should be deleted")
|
||||
}
|
||||
if ok1 {
|
||||
t.Fatal("ok1 should be false")
|
||||
}
|
||||
if !ok2 {
|
||||
t.Fatal("ok2 should be true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetIfAbsent(t *testing.T) {
|
||||
myMap := csmap.New[int, string]()
|
||||
myMap.SetIfAbsent(1, "test")
|
||||
if !myMap.Has(1) {
|
||||
t.Fatal("1 should be exist")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetIfPresent(t *testing.T) {
|
||||
myMap := csmap.New[int, string]()
|
||||
myMap.SetIfPresent(1, "test")
|
||||
if myMap.Has(1) {
|
||||
t.Fatal("1 should be not exist")
|
||||
}
|
||||
|
||||
myMap.Store(1, "test")
|
||||
myMap.SetIfPresent(1, "new-test")
|
||||
val, _ := myMap.Load(1)
|
||||
if val != "new-test" {
|
||||
t.Fatal("val should be new-test")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetIf(t *testing.T) {
|
||||
myMap := csmap.New[int, string]()
|
||||
valueA := "value a"
|
||||
myMap.SetIf(1, func(previousVale string, previousFound bool) (value string, set bool) {
|
||||
// operate like a SetIfAbsent...
|
||||
if !previousFound {
|
||||
return valueA, true
|
||||
}
|
||||
return "", false
|
||||
})
|
||||
value, _ := myMap.Load(1)
|
||||
if value != valueA {
|
||||
t.Fatal("value should value a")
|
||||
}
|
||||
|
||||
myMap.SetIf(1, func(previousVale string, previousFound bool) (value string, set bool) {
|
||||
// operate like a SetIfAbsent...
|
||||
if !previousFound {
|
||||
return "bad", true
|
||||
}
|
||||
return "", false
|
||||
})
|
||||
value, _ = myMap.Load(1)
|
||||
if value != valueA {
|
||||
t.Fatal("value should value a")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteIf(t *testing.T) {
|
||||
myMap := csmap.New[int, string]()
|
||||
myMap.Store(1, "value b")
|
||||
ok1 := myMap.DeleteIf(20, func(value string) bool {
|
||||
t.Fatal("condition function should not have been called")
|
||||
return false
|
||||
})
|
||||
if ok1 {
|
||||
t.Fatal("ok1 should be false")
|
||||
}
|
||||
|
||||
ok2 := myMap.DeleteIf(1, func(value string) bool {
|
||||
if value != "value b" {
|
||||
t.Fatal("condition function arg should be tests")
|
||||
}
|
||||
return false // don't delete
|
||||
})
|
||||
if ok2 {
|
||||
t.Fatal("ok1 should be false")
|
||||
}
|
||||
|
||||
ok3 := myMap.DeleteIf(1, func(value string) bool {
|
||||
if value != "value b" {
|
||||
t.Fatal("condition function arg should be tests")
|
||||
}
|
||||
return true // delete the entry
|
||||
})
|
||||
if !ok3 {
|
||||
t.Fatal("ok2 should be true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCount(t *testing.T) {
|
||||
myMap := csmap.New[int, string]()
|
||||
myMap.SetIfAbsent(1, "test")
|
||||
myMap.SetIfAbsent(2, "test2")
|
||||
if myMap.Count() != 2 {
|
||||
t.Fatal("count should be 2")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsEmpty(t *testing.T) {
|
||||
myMap := csmap.New[int, string]()
|
||||
if !myMap.IsEmpty() {
|
||||
t.Fatal("map should be empty")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRangeStop(t *testing.T) {
|
||||
myMap := csmap.New[int, string](
|
||||
csmap.WithShardCount[int, string](1),
|
||||
)
|
||||
myMap.SetIfAbsent(1, "test")
|
||||
myMap.SetIfAbsent(2, "test2")
|
||||
myMap.SetIfAbsent(3, "test2")
|
||||
total := 0
|
||||
myMap.Range(func(key int, value string) (stop bool) {
|
||||
total++
|
||||
return true
|
||||
})
|
||||
if total != 1 {
|
||||
t.Fatal("total should be 1")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRange(t *testing.T) {
|
||||
myMap := csmap.New[int, string]()
|
||||
myMap.SetIfAbsent(1, "test")
|
||||
myMap.SetIfAbsent(2, "test2")
|
||||
total := 0
|
||||
myMap.Range(func(key int, value string) (stop bool) {
|
||||
total++
|
||||
return
|
||||
})
|
||||
if total != 2 {
|
||||
t.Fatal("total should be 2")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCustomHasherWithRange(t *testing.T) {
|
||||
myMap := csmap.New[int, string](
|
||||
csmap.WithCustomHasher[int, string](func(key int) uint64 {
|
||||
return 0
|
||||
}),
|
||||
)
|
||||
myMap.SetIfAbsent(1, "test")
|
||||
myMap.SetIfAbsent(2, "test2")
|
||||
myMap.SetIfAbsent(3, "test2")
|
||||
myMap.SetIfAbsent(4, "test2")
|
||||
total := 0
|
||||
myMap.Range(func(key int, value string) (stop bool) {
|
||||
total++
|
||||
return true
|
||||
})
|
||||
if total != 1 {
|
||||
t.Fatal("total should be 1, because currently range stops current shard only.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteFromRange(t *testing.T) {
|
||||
myMap := csmap.New[string, int](
|
||||
csmap.WithSize[string, int](1024),
|
||||
)
|
||||
|
||||
myMap.Store("aaa", 10)
|
||||
myMap.Store("aab", 11)
|
||||
myMap.Store("aac", 15)
|
||||
myMap.Store("aad", 124)
|
||||
myMap.Store("aaf", 987)
|
||||
|
||||
myMap.Range(func(key string, value int) (stop bool) {
|
||||
if value > 20 {
|
||||
myMap.Delete(key)
|
||||
}
|
||||
return false
|
||||
})
|
||||
if myMap.Count() != 3 {
|
||||
t.Fatal("total should be 3, because currently range deletes values that bigger than 20.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshal(t *testing.T) {
|
||||
myMap := csmap.New[string, int](
|
||||
csmap.WithSize[string, int](1024),
|
||||
)
|
||||
|
||||
myMap.Store("aaa", 10)
|
||||
myMap.Store("aab", 11)
|
||||
|
||||
b, _ := myMap.MarshalJSON()
|
||||
|
||||
newMap := csmap.New[string, int](
|
||||
csmap.WithSize[string, int](1024),
|
||||
)
|
||||
|
||||
_ = newMap.UnmarshalJSON(b)
|
||||
|
||||
if myMap.Count() != 2 || !myMap.Has("aaa") || !myMap.Has("aab") {
|
||||
t.Fatal("count should be 2 after unmarshal")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBasicConcurrentWriteDeleteCount(t *testing.T) {
|
||||
myMap := csmap.New[int, string](
|
||||
csmap.WithShardCount[int, string](32),
|
||||
csmap.WithSize[int, string](1000),
|
||||
)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1000000)
|
||||
for i := 0; i < 1000000; i++ {
|
||||
i := i
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
myMap.Store(i, strconv.Itoa(i))
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
wg.Add(1000000)
|
||||
for i := 0; i < 1000000; i++ {
|
||||
i := i
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if !myMap.Has(i) {
|
||||
t.Error(strconv.Itoa(i) + " should exist")
|
||||
return
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
wg.Add(1000000)
|
||||
|
||||
for i := 0; i < 1000000; i++ {
|
||||
i := i
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
myMap.Delete(i)
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
wg.Add(1000000)
|
||||
|
||||
for i := 0; i < 1000000; i++ {
|
||||
i := i
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if myMap.Has(i) {
|
||||
t.Error(strconv.Itoa(i) + " should not exist")
|
||||
return
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestClear(t *testing.T) {
|
||||
myMap := csmap.New[int, string]()
|
||||
loop := 10000
|
||||
for i := 0; i < loop; i++ {
|
||||
myMap.Store(i, "test")
|
||||
}
|
||||
|
||||
myMap.Clear()
|
||||
|
||||
if !myMap.IsEmpty() {
|
||||
t.Fatal("count should be true")
|
||||
}
|
||||
|
||||
// store again
|
||||
for i := 0; i < loop; i++ {
|
||||
myMap.Store(i, "test")
|
||||
}
|
||||
|
||||
// get again
|
||||
for i := 0; i < loop; i++ {
|
||||
val, ok := myMap.Load(i)
|
||||
if ok != true {
|
||||
t.Fatal("ok should be true")
|
||||
}
|
||||
|
||||
if val != "test" {
|
||||
t.Fatal("val should be test")
|
||||
}
|
||||
}
|
||||
|
||||
// check again
|
||||
count := myMap.Count()
|
||||
if count != loop {
|
||||
t.Fatal("count should be 1000")
|
||||
}
|
||||
}
|
||||
57
common/utils/concurrent-swiss-map/example/base/base.go
Normal file
57
common/utils/concurrent-swiss-map/example/base/base.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"hash/fnv"
|
||||
|
||||
csmap "github.com/mhmtszr/concurrent-swiss-map"
|
||||
)
|
||||
|
||||
func main() {
|
||||
myMap := csmap.New[string, int](
|
||||
// set the number of map shards. the default value is 32.
|
||||
csmap.WithShardCount[string, int](32),
|
||||
|
||||
// if don't set custom hasher, use the built-in maphash.
|
||||
csmap.WithCustomHasher[string, int](func(key string) uint64 {
|
||||
hash := fnv.New64a()
|
||||
hash.Write([]byte(key))
|
||||
return hash.Sum64()
|
||||
}),
|
||||
|
||||
// set the total capacity, every shard map has total capacity/shard count capacity. the default value is 0.
|
||||
csmap.WithSize[string, int](1000),
|
||||
)
|
||||
|
||||
key := "swiss-map"
|
||||
myMap.Store(key, 10)
|
||||
|
||||
val, ok := myMap.Load(key)
|
||||
println("load val:", val, "exists:", ok)
|
||||
|
||||
deleted := myMap.Delete(key)
|
||||
println("deleted:", deleted)
|
||||
|
||||
ok = myMap.Has(key)
|
||||
println("has:", ok)
|
||||
|
||||
empty := myMap.IsEmpty()
|
||||
println("empty:", empty)
|
||||
|
||||
myMap.SetIfAbsent(key, 11)
|
||||
|
||||
myMap.Range(func(key string, value int) (stop bool) {
|
||||
println("range:", key, value)
|
||||
return true
|
||||
})
|
||||
|
||||
count := myMap.Count()
|
||||
println("count:", count)
|
||||
|
||||
// Output:
|
||||
// load val: 10 exists: true
|
||||
// deleted: true
|
||||
// has: false
|
||||
// empty: true
|
||||
// range: swiss-map 11
|
||||
// count: 1
|
||||
}
|
||||
3
common/utils/concurrent-swiss-map/go.mod
Normal file
3
common/utils/concurrent-swiss-map/go.mod
Normal file
@@ -0,0 +1,3 @@
|
||||
module github.com/mhmtszr/concurrent-swiss-map
|
||||
|
||||
go 1.18
|
||||
0
common/utils/concurrent-swiss-map/go.sum
Normal file
0
common/utils/concurrent-swiss-map/go.sum
Normal file
BIN
common/utils/concurrent-swiss-map/img.png
Normal file
BIN
common/utils/concurrent-swiss-map/img.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 118 KiB |
201
common/utils/concurrent-swiss-map/maphash/LICENSE
Normal file
201
common/utils/concurrent-swiss-map/maphash/LICENSE
Normal file
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
4
common/utils/concurrent-swiss-map/maphash/README.md
Normal file
4
common/utils/concurrent-swiss-map/maphash/README.md
Normal file
@@ -0,0 +1,4 @@
|
||||
# maphash
|
||||
|
||||
Hash any `comparable` type using Golang's fast runtime hash.
|
||||
Uses [AES](https://en.wikipedia.org/wiki/AES_instruction_set) instructions when available.
|
||||
48
common/utils/concurrent-swiss-map/maphash/hasher.go
Normal file
48
common/utils/concurrent-swiss-map/maphash/hasher.go
Normal file
@@ -0,0 +1,48 @@
|
||||
// Copyright 2022 Dolthub, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package maphash
|
||||
|
||||
import "unsafe"
|
||||
|
||||
// Hasher hashes values of type K.
|
||||
// Uses runtime AES-based hashing.
|
||||
type Hasher[K comparable] struct {
|
||||
hash hashfn
|
||||
seed uintptr
|
||||
}
|
||||
|
||||
// NewHasher creates a new Hasher[K] with a random seed.
|
||||
func NewHasher[K comparable]() Hasher[K] {
|
||||
return Hasher[K]{
|
||||
hash: getRuntimeHasher[K](),
|
||||
seed: newHashSeed(),
|
||||
}
|
||||
}
|
||||
|
||||
// NewSeed returns a copy of |h| with a new hash seed.
|
||||
func NewSeed[K comparable](h Hasher[K]) Hasher[K] {
|
||||
return Hasher[K]{
|
||||
hash: h.hash,
|
||||
seed: newHashSeed(),
|
||||
}
|
||||
}
|
||||
|
||||
// Hash hashes |key|.
|
||||
func (h Hasher[K]) Hash(key K) uint64 {
|
||||
// promise to the compiler that pointer
|
||||
// |p| does not escape the stack.
|
||||
p := noescape(unsafe.Pointer(&key))
|
||||
return uint64(h.hash(p, h.seed))
|
||||
}
|
||||
117
common/utils/concurrent-swiss-map/maphash/runtime.go
Normal file
117
common/utils/concurrent-swiss-map/maphash/runtime.go
Normal file
@@ -0,0 +1,117 @@
|
||||
// Copyright 2022 Dolthub, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// This file incorporates work covered by the following copyright and
|
||||
// permission notice:
|
||||
//
|
||||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.18 || go1.19
|
||||
// +build go1.18 go1.19
|
||||
|
||||
package maphash
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type hashfn func(unsafe.Pointer, uintptr) uintptr
|
||||
|
||||
func getRuntimeHasher[K comparable]() (h hashfn) {
|
||||
a := any(make(map[K]struct{}))
|
||||
i := (*mapiface)(unsafe.Pointer(&a))
|
||||
h = i.typ.hasher
|
||||
return
|
||||
}
|
||||
|
||||
//nolint:gosec
|
||||
var hashSeed = rand.Int()
|
||||
|
||||
func newHashSeed() uintptr {
|
||||
return uintptr(hashSeed)
|
||||
}
|
||||
|
||||
// noescape hides a pointer from escape analysis. It is the identity function
|
||||
// but escape analysis doesn't think the output depends on the input.
|
||||
// noescape is inlined and currently compiles down to zero instructions.
|
||||
// USE CAREFULLY!
|
||||
// This was copied from the runtime (via pkg "strings"); see issues 23382 and 7921.
|
||||
//
|
||||
//go:nosplit
|
||||
//go:nocheckptr
|
||||
//nolint:staticcheck
|
||||
func noescape(p unsafe.Pointer) unsafe.Pointer {
|
||||
x := uintptr(p)
|
||||
return unsafe.Pointer(x ^ 0)
|
||||
}
|
||||
|
||||
type mapiface struct {
|
||||
typ *maptype
|
||||
val *hmap
|
||||
}
|
||||
|
||||
// go/src/runtime/type.go
|
||||
type maptype struct {
|
||||
typ _type
|
||||
key *_type
|
||||
elem *_type
|
||||
bucket *_type
|
||||
// function for hashing keys (ptr to key, seed) -> hash
|
||||
hasher func(unsafe.Pointer, uintptr) uintptr
|
||||
keysize uint8
|
||||
elemsize uint8
|
||||
bucketsize uint16
|
||||
flags uint32
|
||||
}
|
||||
|
||||
// go/src/runtime/map.go
|
||||
type hmap struct {
|
||||
count int
|
||||
flags uint8
|
||||
B uint8
|
||||
noverflow uint16
|
||||
// hash seed
|
||||
hash0 uint32
|
||||
buckets unsafe.Pointer
|
||||
oldbuckets unsafe.Pointer
|
||||
nevacuate uintptr
|
||||
// true type is *mapextra
|
||||
// but we don't need this data
|
||||
extra unsafe.Pointer
|
||||
}
|
||||
|
||||
// go/src/runtime/type.go
|
||||
type (
|
||||
tflag uint8
|
||||
nameOff int32
|
||||
typeOff int32
|
||||
)
|
||||
|
||||
// go/src/runtime/type.go
|
||||
type _type struct {
|
||||
size uintptr
|
||||
ptrdata uintptr
|
||||
hash uint32
|
||||
tflag tflag
|
||||
align uint8
|
||||
fieldAlign uint8
|
||||
kind uint8
|
||||
equal func(unsafe.Pointer, unsafe.Pointer) bool
|
||||
gcdata *byte
|
||||
str nameOff
|
||||
ptrToThis typeOff
|
||||
}
|
||||
201
common/utils/concurrent-swiss-map/swiss/LICENSE
Normal file
201
common/utils/concurrent-swiss-map/swiss/LICENSE
Normal file
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
2
common/utils/concurrent-swiss-map/swiss/README.md
Normal file
2
common/utils/concurrent-swiss-map/swiss/README.md
Normal file
@@ -0,0 +1,2 @@
|
||||
# swiss
|
||||
Golang port of Abseil's flat_hash_map
|
||||
59
common/utils/concurrent-swiss-map/swiss/bits.go
Normal file
59
common/utils/concurrent-swiss-map/swiss/bits.go
Normal file
@@ -0,0 +1,59 @@
|
||||
// Copyright 2023 Dolthub, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//go:build !amd64 || nosimd
|
||||
|
||||
//nolint:all
|
||||
|
||||
package swiss
|
||||
|
||||
import (
|
||||
"math/bits"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
groupSize = 8
|
||||
maxAvgGroupLoad = 7
|
||||
|
||||
loBits uint64 = 0x0101010101010101
|
||||
hiBits uint64 = 0x8080808080808080
|
||||
)
|
||||
|
||||
type bitset uint64
|
||||
|
||||
func metaMatchH2(m *metadata, h h2) bitset {
|
||||
// https://graphics.stanford.edu/~seander/bithacks.html##ValueInWord
|
||||
return hasZeroByte(castUint64(m) ^ (loBits * uint64(h)))
|
||||
}
|
||||
|
||||
func metaMatchEmpty(m *metadata) bitset {
|
||||
return hasZeroByte(castUint64(m) ^ hiBits)
|
||||
}
|
||||
|
||||
func nextMatch(b *bitset) uint32 {
|
||||
s := uint32(bits.TrailingZeros64(uint64(*b)))
|
||||
*b &= ^(1 << s) // clear bit |s|
|
||||
return s >> 3 // div by 8
|
||||
}
|
||||
|
||||
func hasZeroByte(x uint64) bitset {
|
||||
return bitset(((x - loBits) & ^(x)) & hiBits)
|
||||
}
|
||||
|
||||
func castUint64(m *metadata) uint64 {
|
||||
return *(*uint64)((unsafe.Pointer)(m))
|
||||
}
|
||||
|
||||
//go:linkname fastrand runtime.fastrand
|
||||
func fastrand() uint32
|
||||
52
common/utils/concurrent-swiss-map/swiss/bits_amd64.go
Normal file
52
common/utils/concurrent-swiss-map/swiss/bits_amd64.go
Normal file
@@ -0,0 +1,52 @@
|
||||
// Copyright 2023 Dolthub, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//nolint:all
|
||||
//go:build amd64 && !nosimd
|
||||
|
||||
package swiss
|
||||
|
||||
import (
|
||||
"github.com/mhmtszr/concurrent-swiss-map/swiss/simd"
|
||||
"math/bits"
|
||||
_ "unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
groupSize = 16
|
||||
maxAvgGroupLoad = 14
|
||||
)
|
||||
|
||||
type bitset uint16
|
||||
|
||||
//nolint:all
|
||||
func metaMatchH2(m *metadata, h h2) bitset {
|
||||
b := simd.MatchMetadata((*[16]int8)(m), int8(h))
|
||||
return bitset(b)
|
||||
}
|
||||
|
||||
//nolint:all
|
||||
func metaMatchEmpty(m *metadata) bitset {
|
||||
b := simd.MatchMetadata((*[16]int8)(m), empty)
|
||||
return bitset(b)
|
||||
}
|
||||
|
||||
//nolint:all
|
||||
func nextMatch(b *bitset) (s uint32) {
|
||||
s = uint32(bits.TrailingZeros16(uint16(*b)))
|
||||
*b &= ^(1 << s) // clear bit |s|
|
||||
return
|
||||
}
|
||||
|
||||
//go:linkname fastrand runtime.fastrand
|
||||
func fastrand() uint32
|
||||
357
common/utils/concurrent-swiss-map/swiss/map.go
Normal file
357
common/utils/concurrent-swiss-map/swiss/map.go
Normal file
@@ -0,0 +1,357 @@
|
||||
// Copyright 2023 Dolthub, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package swiss
|
||||
|
||||
import (
|
||||
"github.com/mhmtszr/concurrent-swiss-map/maphash"
|
||||
)
|
||||
|
||||
const (
|
||||
maxLoadFactor = float32(maxAvgGroupLoad) / float32(groupSize)
|
||||
)
|
||||
|
||||
// Map is an open-addressing hash map
|
||||
// based on Abseil's flat_hash_map.
|
||||
type Map[K comparable, V any] struct {
|
||||
ctrl []metadata
|
||||
groups []group[K, V]
|
||||
hash maphash.Hasher[K]
|
||||
resident uint32
|
||||
dead uint32
|
||||
limit uint32
|
||||
}
|
||||
|
||||
// metadata is the h2 metadata array for a group.
|
||||
// find operations first probe the controls bytes
|
||||
// to filter candidates before matching keys
|
||||
type metadata [groupSize]int8
|
||||
|
||||
// group is a group of 16 key-value pairs
|
||||
type group[K comparable, V any] struct {
|
||||
keys [groupSize]K
|
||||
values [groupSize]V
|
||||
}
|
||||
|
||||
const (
|
||||
h1Mask uint64 = 0xffff_ffff_ffff_ff80
|
||||
h2Mask uint64 = 0x0000_0000_0000_007f
|
||||
empty int8 = -128 // 0b1000_0000
|
||||
tombstone int8 = -2 // 0b1111_1110
|
||||
)
|
||||
|
||||
// h1 is a 57 bit hash prefix
|
||||
type h1 uint64
|
||||
|
||||
// h2 is a 7 bit hash suffix
|
||||
type h2 int8
|
||||
|
||||
// NewMap constructs a Map.
|
||||
func NewMap[K comparable, V any](sz uint32) (m *Map[K, V]) {
|
||||
groups := numGroups(sz)
|
||||
m = &Map[K, V]{
|
||||
ctrl: make([]metadata, groups),
|
||||
groups: make([]group[K, V], groups),
|
||||
hash: maphash.NewHasher[K](),
|
||||
limit: groups * maxAvgGroupLoad,
|
||||
}
|
||||
for i := range m.ctrl {
|
||||
m.ctrl[i] = newEmptyMetadata()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (m *Map[K, V]) HasWithHash(key K, hash uint64) (ok bool) {
|
||||
hi, lo := splitHash(hash)
|
||||
g := probeStart(hi, len(m.groups))
|
||||
for { // inlined find loop
|
||||
matches := metaMatchH2(&m.ctrl[g], lo)
|
||||
for matches != 0 {
|
||||
s := nextMatch(&matches)
|
||||
if key == m.groups[g].keys[s] {
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
}
|
||||
// |key| is not in group |g|,
|
||||
// stop probing if we see an empty slot
|
||||
matches = metaMatchEmpty(&m.ctrl[g])
|
||||
if matches != 0 {
|
||||
ok = false
|
||||
return
|
||||
}
|
||||
g++ // linear probing
|
||||
if g >= uint32(len(m.groups)) {
|
||||
g = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Map[K, V]) GetWithHash(key K, hash uint64) (value V, ok bool) {
|
||||
hi, lo := splitHash(hash)
|
||||
g := probeStart(hi, len(m.groups))
|
||||
for { // inlined find loop
|
||||
matches := metaMatchH2(&m.ctrl[g], lo)
|
||||
for matches != 0 {
|
||||
s := nextMatch(&matches)
|
||||
if key == m.groups[g].keys[s] {
|
||||
value, ok = m.groups[g].values[s], true
|
||||
return
|
||||
}
|
||||
}
|
||||
// |key| is not in group |g|,
|
||||
// stop probing if we see an empty slot
|
||||
matches = metaMatchEmpty(&m.ctrl[g])
|
||||
if matches != 0 {
|
||||
ok = false
|
||||
return
|
||||
}
|
||||
g++ // linear probing
|
||||
if g >= uint32(len(m.groups)) {
|
||||
g = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Put attempts to insert |key| and |value|
|
||||
func (m *Map[K, V]) Put(key K, value V) {
|
||||
if m.resident >= m.limit {
|
||||
m.rehash(m.nextSize())
|
||||
}
|
||||
hi, lo := splitHash(m.hash.Hash(key))
|
||||
g := probeStart(hi, len(m.groups))
|
||||
for { // inlined find loop
|
||||
matches := metaMatchH2(&m.ctrl[g], lo)
|
||||
for matches != 0 {
|
||||
s := nextMatch(&matches)
|
||||
if key == m.groups[g].keys[s] { // update
|
||||
m.groups[g].keys[s] = key
|
||||
m.groups[g].values[s] = value
|
||||
return
|
||||
}
|
||||
}
|
||||
// |key| is not in group |g|,
|
||||
// stop probing if we see an empty slot
|
||||
matches = metaMatchEmpty(&m.ctrl[g])
|
||||
if matches != 0 { // insert
|
||||
s := nextMatch(&matches)
|
||||
m.groups[g].keys[s] = key
|
||||
m.groups[g].values[s] = value
|
||||
m.ctrl[g][s] = int8(lo)
|
||||
m.resident++
|
||||
return
|
||||
}
|
||||
g++ // linear probing
|
||||
if g >= uint32(len(m.groups)) {
|
||||
g = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Put attempts to insert |key| and |value|
|
||||
func (m *Map[K, V]) PutWithHash(key K, value V, hash uint64) {
|
||||
if m.resident >= m.limit {
|
||||
m.rehash(m.nextSize())
|
||||
}
|
||||
hi, lo := splitHash(hash)
|
||||
g := probeStart(hi, len(m.groups))
|
||||
for { // inlined find loop
|
||||
matches := metaMatchH2(&m.ctrl[g], lo)
|
||||
for matches != 0 {
|
||||
s := nextMatch(&matches)
|
||||
if key == m.groups[g].keys[s] { // update
|
||||
m.groups[g].keys[s] = key
|
||||
m.groups[g].values[s] = value
|
||||
return
|
||||
}
|
||||
}
|
||||
// |key| is not in group |g|,
|
||||
// stop probing if we see an empty slot
|
||||
matches = metaMatchEmpty(&m.ctrl[g])
|
||||
if matches != 0 { // insert
|
||||
s := nextMatch(&matches)
|
||||
m.groups[g].keys[s] = key
|
||||
m.groups[g].values[s] = value
|
||||
m.ctrl[g][s] = int8(lo)
|
||||
m.resident++
|
||||
return
|
||||
}
|
||||
g++ // linear probing
|
||||
if g >= uint32(len(m.groups)) {
|
||||
g = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Map[K, V]) DeleteWithHash(key K, hash uint64) (ok bool) {
|
||||
hi, lo := splitHash(hash)
|
||||
g := probeStart(hi, len(m.groups))
|
||||
for {
|
||||
matches := metaMatchH2(&m.ctrl[g], lo)
|
||||
for matches != 0 {
|
||||
s := nextMatch(&matches)
|
||||
if key == m.groups[g].keys[s] {
|
||||
ok = true
|
||||
// optimization: if |m.ctrl[g]| contains any empty
|
||||
// metadata bytes, we can physically delete |key|
|
||||
// rather than placing a tombstone.
|
||||
// The observation is that any probes into group |g|
|
||||
// would already be terminated by the existing empty
|
||||
// slot, and therefore reclaiming slot |s| will not
|
||||
// cause premature termination of probes into |g|.
|
||||
if metaMatchEmpty(&m.ctrl[g]) != 0 {
|
||||
m.ctrl[g][s] = empty
|
||||
m.resident--
|
||||
} else {
|
||||
m.ctrl[g][s] = tombstone
|
||||
m.dead++
|
||||
}
|
||||
var k K
|
||||
var v V
|
||||
m.groups[g].keys[s] = k
|
||||
m.groups[g].values[s] = v
|
||||
return
|
||||
}
|
||||
}
|
||||
// |key| is not in group |g|,
|
||||
// stop probing if we see an empty slot
|
||||
matches = metaMatchEmpty(&m.ctrl[g])
|
||||
if matches != 0 { // |key| absent
|
||||
ok = false
|
||||
return
|
||||
}
|
||||
g++ // linear probing
|
||||
if g >= uint32(len(m.groups)) {
|
||||
g = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clear removes all elements from the Map.
|
||||
func (m *Map[K, V]) Clear() {
|
||||
for i, c := range m.ctrl {
|
||||
for j := range c {
|
||||
m.ctrl[i][j] = empty
|
||||
}
|
||||
}
|
||||
var k K
|
||||
var v V
|
||||
for i := range m.groups {
|
||||
g := &m.groups[i]
|
||||
for i := range g.keys {
|
||||
g.keys[i] = k
|
||||
g.values[i] = v
|
||||
}
|
||||
}
|
||||
m.resident, m.dead = 0, 0
|
||||
}
|
||||
|
||||
// Iter iterates the elements of the Map, passing them to the callback.
|
||||
// It guarantees that any key in the Map will be visited only once, and
|
||||
// for un-mutated Maps, every key will be visited once. If the Map is
|
||||
// Mutated during iteration, mutations will be reflected on return from
|
||||
// Iter, but the set of keys visited by Iter is non-deterministic.
|
||||
//
|
||||
//nolint:gosec
|
||||
func (m *Map[K, V]) Iter(cb func(k K, v V) (stop bool)) bool {
|
||||
// take a consistent view of the table in case
|
||||
// we rehash during iteration
|
||||
ctrl, groups := m.ctrl, m.groups
|
||||
// pick a random starting group
|
||||
g := randIntN(len(groups))
|
||||
for n := 0; n < len(groups); n++ {
|
||||
for s, c := range ctrl[g] {
|
||||
if c == empty || c == tombstone {
|
||||
continue
|
||||
}
|
||||
k, v := groups[g].keys[s], groups[g].values[s]
|
||||
if stop := cb(k, v); stop {
|
||||
return stop
|
||||
}
|
||||
}
|
||||
g++
|
||||
if g >= uint32(len(groups)) {
|
||||
g = 0
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Count returns the number of elements in the Map.
|
||||
func (m *Map[K, V]) Count() int {
|
||||
return int(m.resident - m.dead)
|
||||
}
|
||||
|
||||
func (m *Map[K, V]) nextSize() (n uint32) {
|
||||
n = uint32(len(m.groups)) * 2
|
||||
if m.dead >= (m.resident / 2) {
|
||||
n = uint32(len(m.groups))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (m *Map[K, V]) rehash(n uint32) {
|
||||
groups, ctrl := m.groups, m.ctrl
|
||||
m.groups = make([]group[K, V], n)
|
||||
m.ctrl = make([]metadata, n)
|
||||
for i := range m.ctrl {
|
||||
m.ctrl[i] = newEmptyMetadata()
|
||||
}
|
||||
m.hash = maphash.NewSeed(m.hash)
|
||||
m.limit = n * maxAvgGroupLoad
|
||||
m.resident, m.dead = 0, 0
|
||||
for g := range ctrl {
|
||||
for s := range ctrl[g] {
|
||||
c := ctrl[g][s]
|
||||
if c == empty || c == tombstone {
|
||||
continue
|
||||
}
|
||||
m.Put(groups[g].keys[s], groups[g].values[s])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// numGroups returns the minimum number of groups needed to store |n| elems.
|
||||
func numGroups(n uint32) (groups uint32) {
|
||||
groups = (n + maxAvgGroupLoad - 1) / maxAvgGroupLoad
|
||||
if groups == 0 {
|
||||
groups = 1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func newEmptyMetadata() (meta metadata) {
|
||||
for i := range meta {
|
||||
meta[i] = empty
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func splitHash(h uint64) (h1, h2) {
|
||||
return h1((h & h1Mask) >> 7), h2(h & h2Mask)
|
||||
}
|
||||
|
||||
func probeStart(hi h1, groups int) uint32 {
|
||||
return fastModN(uint32(hi), uint32(groups))
|
||||
}
|
||||
|
||||
// lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/
|
||||
func fastModN(x, n uint32) uint32 {
|
||||
return uint32((uint64(x) * uint64(n)) >> 32)
|
||||
}
|
||||
|
||||
// randIntN returns a random number in the interval [0, n).
|
||||
func randIntN(n int) uint32 {
|
||||
return fastModN(fastrand(), uint32(n))
|
||||
}
|
||||
19
common/utils/concurrent-swiss-map/swiss/simd/match.s
Normal file
19
common/utils/concurrent-swiss-map/swiss/simd/match.s
Normal file
@@ -0,0 +1,19 @@
|
||||
// Code generated by command: go run asm.go -out match.s -stubs match_amd64.go. DO NOT EDIT.
|
||||
//nolint
|
||||
//go:build amd64
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
// func MatchMetadata(metadata *[16]int8, hash int8) uint16
|
||||
// Requires: SSE2, SSSE3
|
||||
TEXT ·MatchMetadata(SB), NOSPLIT, $0-18
|
||||
MOVQ metadata+0(FP), AX
|
||||
MOVBLSX hash+8(FP), CX
|
||||
MOVD CX, X0
|
||||
PXOR X1, X1
|
||||
PSHUFB X1, X0
|
||||
MOVOU (AX), X1
|
||||
PCMPEQB X1, X0
|
||||
PMOVMSKB X0, AX
|
||||
MOVW AX, ret+16(FP)
|
||||
RET
|
||||
@@ -0,0 +1,9 @@
|
||||
// Code generated by command: go run asm.go -out match.s -stubs match_amd64.go. DO NOT EDIT.
|
||||
//nolint:all
|
||||
//go:build amd64
|
||||
|
||||
package simd
|
||||
|
||||
// MatchMetadata performs a 16-way probe of |metadata| using SSE instructions
|
||||
// nb: |metadata| must be an aligned pointer
|
||||
func MatchMetadata(metadata *[16]int8, hash int8) uint16
|
||||
Reference in New Issue
Block a user