From e90d44f10487f1ca50bc1c7b507cc55037ef65f5 Mon Sep 17 00:00:00 2001 From: cornelk Date: Sun, 28 Aug 2022 16:06:27 -0600 Subject: [PATCH] update benchmarks --- README.md | 26 ++++++++++++++------------ benchmarks/benchmark_test.go | 24 +++++++++++++++++------- benchmarks/go.mod | 6 ++++-- benchmarks/go.sum | 6 ++---- 4 files changed, 37 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 536c663..5b15fce 100644 --- a/README.md +++ b/README.md @@ -45,26 +45,26 @@ Reading from the hash map in a thread-safe way is nearly as fast as reading from in an unsafe way and twice as fast as Go's `sync.Map`: ``` -BenchmarkReadHashMapUint-8 2047108 547.2 ns/op -BenchmarkReadHaxMapUint-8 2295067 532.3 ns/op (not safe to use, can lose writes during concurrent deletes) -BenchmarkReadGoMapUintUnsafe-8 3303577 360.6 ns/op -BenchmarkReadGoMapUintMutex-8 83017 14266 ns/op -BenchmarkReadGoSyncMapUint-8 943773 1208 ns/op +BenchmarkReadHashMapUint-8 1314156 955.6 ns/op +BenchmarkReadHaxMapUint-8 872134 1316 ns/op (can not handle hash 0 collisions) +BenchmarkReadGoMapUintUnsafe-8 1560886 762.8 ns/op +BenchmarkReadGoMapUintMutex-8 42284 28232 ns/op +BenchmarkReadGoSyncMapUint-8 468338 2672 ns/op ``` Reading from the map while writes are happening: ``` -BenchmarkReadHashMapWithWritesUint-8 1669750 718.1 ns/op -BenchmarkReadGoMapWithWritesUintMutex-8 24919 54270 ns/op -BenchmarkReadGoSyncMapWithWritesUint-8 823063 1439 ns/op +BenchmarkReadHashMapWithWritesUint-8 890938 1288 ns/op +BenchmarkReadGoMapWithWritesUintMutex-8 14290 86758 ns/op +BenchmarkReadGoSyncMapWithWritesUint-8 374464 3149 ns/op ``` Write performance without any concurrent reads: ``` -BenchmarkWriteHashMapUint-8 30104 49176 ns/op -BenchmarkWriteGoMapMutexUint-8 177536 6571 ns/op -BenchmarkWriteGoSyncMapUint-8 19857 61428 ns/op +BenchmarkWriteHashMapUint-8 15384 79032 ns/op +BenchmarkWriteGoMapMutexUint-8 74569 14874 ns/op +BenchmarkWriteGoSyncMapUint-8 10000 107094 ns/op ``` The benchmarks were run with Golang 1.19.0 on Linux using `make benchmark`. @@ -73,7 +73,7 @@ The benchmarks were run with Golang 1.19.0 on Linux using `make benchmark`. * Faster -* thread-safe access without need of a(n extra) mutex +* thread-safe access without need of a mutex ### Benefits over [Golang's sync.Map](https://golang.org/pkg/sync/#Map) @@ -92,3 +92,5 @@ The benchmarks were run with Golang 1.19.0 on Linux using `make benchmark`. Once a slice is allocated, the size of it does not change. The library limits the index into the slice, therefore the Golang size check is obsolete. When the slice reaches a defined fill rate, a bigger slice is allocated and all keys are recalculated and transferred into the new slice. + +* For hashing, specialized xxhash implementations are used that match the size of the key type where available diff --git a/benchmarks/benchmark_test.go b/benchmarks/benchmark_test.go index 0fdc4c6..4017e0e 100644 --- a/benchmarks/benchmark_test.go +++ b/benchmarks/benchmark_test.go @@ -10,7 +10,7 @@ import ( "github.com/cornelk/hashmap" ) -const benchmarkItemCount = 512 +const benchmarkItemCount = 1024 func setupHashMap(b *testing.B) *hashmap.HashMap[uintptr, uintptr] { b.Helper() @@ -19,8 +19,6 @@ func setupHashMap(b *testing.B) *hashmap.HashMap[uintptr, uintptr] { for i := uintptr(0); i < benchmarkItemCount; i++ { m.Set(i, i) } - - b.ResetTimer() return m } @@ -45,7 +43,6 @@ func setupHashMapString(b *testing.B) (*hashmap.HashMap[string, string], []strin keys[i] = s } - b.ResetTimer() return m, keys } @@ -57,7 +54,6 @@ func setupGoMap(b *testing.B) map[uintptr]uintptr { m[i] = i } - b.ResetTimer() return m } @@ -69,7 +65,6 @@ func setupGoSyncMap(b *testing.B) *sync.Map { m.Store(i, i) } - b.ResetTimer() return m } @@ -83,12 +78,12 @@ func setupGoMapString(b *testing.B) (map[string]string, []string) { m[s] = s keys[i] = s } - b.ResetTimer() return m, keys } func BenchmarkReadHashMapUint(b *testing.B) { m := setupHashMap(b) + b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { @@ -105,6 +100,7 @@ func BenchmarkReadHashMapUint(b *testing.B) { func BenchmarkReadHashMapWithWritesUint(b *testing.B) { m := setupHashMap(b) var writer uintptr + b.ResetTimer() b.RunParallel(func(pb *testing.PB) { // use 1 thread as writer @@ -129,6 +125,7 @@ func BenchmarkReadHashMapWithWritesUint(b *testing.B) { func BenchmarkReadHashMapString(b *testing.B) { m, keys := setupHashMapString(b) + b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { @@ -145,6 +142,7 @@ func BenchmarkReadHashMapString(b *testing.B) { func BenchmarkReadHashMapInterface(b *testing.B) { m := setupHashMap(b) + b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { @@ -160,6 +158,7 @@ func BenchmarkReadHashMapInterface(b *testing.B) { func BenchmarkReadHaxMapUint(b *testing.B) { m := setupHaxMap(b) + b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { @@ -176,6 +175,7 @@ func BenchmarkReadHaxMapUint(b *testing.B) { func BenchmarkReadHaxMapWithWritesUint(b *testing.B) { m := setupHaxMap(b) var writer uintptr + b.ResetTimer() b.RunParallel(func(pb *testing.PB) { // use 1 thread as writer @@ -200,6 +200,7 @@ func BenchmarkReadHaxMapWithWritesUint(b *testing.B) { func BenchmarkReadGoMapUintUnsafe(b *testing.B) { m := setupGoMap(b) + b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { for i := uintptr(0); i < benchmarkItemCount; i++ { @@ -215,6 +216,7 @@ func BenchmarkReadGoMapUintUnsafe(b *testing.B) { func BenchmarkReadGoMapUintMutex(b *testing.B) { m := setupGoMap(b) l := &sync.RWMutex{} + b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { for i := uintptr(0); i < benchmarkItemCount; i++ { @@ -233,6 +235,7 @@ func BenchmarkReadGoMapWithWritesUintMutex(b *testing.B) { m := setupGoMap(b) l := &sync.RWMutex{} var writer uintptr + b.ResetTimer() b.RunParallel(func(pb *testing.PB) { // use 1 thread as writer @@ -261,6 +264,7 @@ func BenchmarkReadGoMapWithWritesUintMutex(b *testing.B) { func BenchmarkReadGoSyncMapUint(b *testing.B) { m := setupGoSyncMap(b) + b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { for i := uintptr(0); i < benchmarkItemCount; i++ { @@ -276,6 +280,7 @@ func BenchmarkReadGoSyncMapUint(b *testing.B) { func BenchmarkReadGoSyncMapWithWritesUint(b *testing.B) { m := setupGoSyncMap(b) var writer uintptr + b.ResetTimer() b.RunParallel(func(pb *testing.PB) { // use 1 thread as writer @@ -300,6 +305,7 @@ func BenchmarkReadGoSyncMapWithWritesUint(b *testing.B) { func BenchmarkReadGoMapStringUnsafe(b *testing.B) { m, keys := setupGoMapString(b) + b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { for i := 0; i < benchmarkItemCount; i++ { @@ -316,6 +322,7 @@ func BenchmarkReadGoMapStringUnsafe(b *testing.B) { func BenchmarkReadGoMapStringMutex(b *testing.B) { m, keys := setupGoMapString(b) l := &sync.RWMutex{} + b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { for i := 0; i < benchmarkItemCount; i++ { @@ -333,6 +340,7 @@ func BenchmarkReadGoMapStringMutex(b *testing.B) { func BenchmarkWriteHashMapUint(b *testing.B) { m := hashmap.New[uintptr, uintptr]() + b.ResetTimer() for n := 0; n < b.N; n++ { for i := uintptr(0); i < benchmarkItemCount; i++ { @@ -344,6 +352,7 @@ func BenchmarkWriteHashMapUint(b *testing.B) { func BenchmarkWriteGoMapMutexUint(b *testing.B) { m := make(map[uintptr]uintptr) l := &sync.RWMutex{} + b.ResetTimer() for n := 0; n < b.N; n++ { for i := uintptr(0); i < benchmarkItemCount; i++ { @@ -356,6 +365,7 @@ func BenchmarkWriteGoMapMutexUint(b *testing.B) { func BenchmarkWriteGoSyncMapUint(b *testing.B) { m := &sync.Map{} + b.ResetTimer() for n := 0; n < b.N; n++ { for i := uintptr(0); i < benchmarkItemCount; i++ { diff --git a/benchmarks/go.mod b/benchmarks/go.mod index 73f3245..4da199a 100644 --- a/benchmarks/go.mod +++ b/benchmarks/go.mod @@ -2,9 +2,11 @@ module github.com/cornelk/hashmap/benchmarks go 1.19 +replace github.com/cornelk/hashmap => ../ + require ( - github.com/alphadose/haxmap v0.1.1-0.20220808155550-bc3b9a6adfc4 - github.com/cornelk/hashmap v1.0.4-0.20220810053739-f969cadbb3f0 + github.com/alphadose/haxmap v0.2.1-0.20220828165710-add810974d4f + github.com/cornelk/hashmap v1.0.5-0.20220828215932-152772b42884 ) require github.com/cespare/xxhash v1.1.0 // indirect diff --git a/benchmarks/go.sum b/benchmarks/go.sum index 14c71ae..7f27fde 100644 --- a/benchmarks/go.sum +++ b/benchmarks/go.sum @@ -1,11 +1,9 @@ github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/alphadose/haxmap v0.1.1-0.20220808155550-bc3b9a6adfc4 h1:E78W2LuJFjDKpr0a0xFK9w0rawO4wtmTdcbRX5H11mg= -github.com/alphadose/haxmap v0.1.1-0.20220808155550-bc3b9a6adfc4/go.mod h1:68nFwlFwh/HEilKSdlNOKnd1PwOy8EUnKUtZH2TdVb8= +github.com/alphadose/haxmap v0.2.1-0.20220828165710-add810974d4f h1:up6qKu3lIXQ4H3AyJovBy0Tp6Ug++aveneLrUv7TyNo= +github.com/alphadose/haxmap v0.2.1-0.20220828165710-add810974d4f/go.mod h1:Fu37Wlmj7cR++vSLgRTu3fGy8wpjHGmMypM2aclkc1A= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cornelk/hashmap v1.0.4-0.20220810053739-f969cadbb3f0 h1:jed6FFAMwRgg57mgmqH/kfSsjtXL8ocT3f2zE/8vumg= -github.com/cornelk/hashmap v1.0.4-0.20220810053739-f969cadbb3f0/go.mod h1:T9KPzj/SjvCwiF51NdfFMmqHhRm6cl2slzzgmmgDnUw= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ=