Skip to content

Commit

Permalink
Add benchmark test & reset hand pointer on Purge function
Browse files Browse the repository at this point in the history
  • Loading branch information
scalalang2 committed Mar 13, 2024
1 parent db38576 commit 75f0941
Show file tree
Hide file tree
Showing 6 changed files with 186 additions and 14 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
golang-fifo.test
out

# IDE
.idea
.vscode
Expand Down
83 changes: 70 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,17 +70,17 @@ The benchmark result were obtained using [go-cache-benchmark](https://github.com
```
itemSize=500000, workloads=7500000, cacheSize=0.10%, zipf's alpha=0.99, concurrency=16
CACHE | HITRATE | MEMORY | QPS | HITS | MISSES
-----------------+---------+---------+---------+---------+----------
sieve | 47.66% | 0.09MiB | 2508361 | 3574212 | 3925788
tinylfu | 47.37% | 0.11MiB | 2269542 | 3552921 | 3947079
s3-fifo | 47.17% | 0.18MiB | 1651619 | 3538121 | 3961879
slru | 46.49% | 0.11MiB | 2201350 | 3486476 | 4013524
s4lru | 46.09% | 0.12MiB | 2484266 | 3456682 | 4043318
two-queue | 45.49% | 0.17MiB | 1713502 | 3411800 | 4088200
clock | 37.34% | 0.10MiB | 2370417 | 2800750 | 4699250
lru-groupcache | 36.59% | 0.11MiB | 2206841 | 2743894 | 4756106
lru-hashicorp | 36.57% | 0.08MiB | 2055358 | 2743000 | 4757000
CACHE | HITRATE | QPS | HITS | MISSES
-----------------+---------+---------+---------+----------
sieve | 47.66% | 2508361 | 3574212 | 3925788
tinylfu | 47.37% | 2269542 | 3552921 | 3947079
s3-fifo | 47.17% | 1651619 | 3538121 | 3961879
slru | 46.49% | 2201350 | 3486476 | 4013524
s4lru | 46.09% | 2484266 | 3456682 | 4043318
two-queue | 45.49% | 1713502 | 3411800 | 4088200
clock | 37.34% | 2370417 | 2800750 | 4699250
lru-groupcache | 36.59% | 2206841 | 2743894 | 4756106
lru-hashicorp | 36.57% | 2055358 | 2743000 | 4757000
```

**SIEVE** delivers both high hit rates and the highest QPS(queries per seconds) compared to other LRU-based caches.
Expand All @@ -94,10 +94,67 @@ requiring a potentially slow lock acquisition,
SIEVE only needs to update a single bit upon a cache hit.
This update can be done with a significantly faster reader lock, leading to increased performance.

The real-world traces are also evaluted at [here](https://observablehq.com/@1a1a11a/sieve-miss-ratio-plots)
The real-world traces are also evaluated at [here](https://observablehq.com/@1a1a11a/sieve-miss-ratio-plots)

## Appendix

<details>
<summary>Performance : golang-fifo</summary>

```shell
goos: linux
goarch: amd64
pkg: github.com/scalalang2/golang-fifo
cpu: Intel(R) Core(TM) i5-10600KF CPU @ 4.10GHz
BenchmarkCache
BenchmarkCache/cache=sieve
BenchmarkCache/cache=sieve/t=int32
BenchmarkCache/cache=sieve/t=int32-12 2765682 393.8 ns/op 148 B/op 4 allocs/op
BenchmarkCache/cache=sieve/t=int32-12 3037669 388.1 ns/op 149 B/op 4 allocs/op
BenchmarkCache/cache=sieve/t=int32-12 3075998 395.0 ns/op 149 B/op 4 allocs/op
BenchmarkCache/cache=sieve/t=int32-12 2924646 392.0 ns/op 148 B/op 4 allocs/op
BenchmarkCache/cache=sieve/t=int32-12 2632326 409.3 ns/op 148 B/op 4 allocs/op
BenchmarkCache/cache=sieve/t=int32-12 2746551 463.5 ns/op 148 B/op 4 allocs/op
BenchmarkCache/cache=sieve/t=int32-12 3004071 401.0 ns/op 148 B/op 4 allocs/op
BenchmarkCache/cache=sieve/t=int32-12 2398981 456.0 ns/op 149 B/op 4 allocs/op
BenchmarkCache/cache=sieve/t=int32-12 2698939 422.9 ns/op 148 B/op 4 allocs/op
BenchmarkCache/cache=sieve/t=int32-12 2647030 392.1 ns/op 148 B/op 4 allocs/op
BenchmarkCache/cache=sieve/t=int64
BenchmarkCache/cache=sieve/t=int64-12 2532614 414.1 ns/op 158 B/op 4 allocs/op
BenchmarkCache/cache=sieve/t=int64-12 2825973 419.3 ns/op 158 B/op 4 allocs/op
BenchmarkCache/cache=sieve/t=int64-12 2693790 407.1 ns/op 158 B/op 4 allocs/op
BenchmarkCache/cache=sieve/t=int64-12 2882792 414.7 ns/op 157 B/op 4 allocs/op
BenchmarkCache/cache=sieve/t=int64-12 2903197 421.7 ns/op 157 B/op 4 allocs/op
BenchmarkCache/cache=sieve/t=int64-12 2876046 435.7 ns/op 157 B/op 4 allocs/op
BenchmarkCache/cache=sieve/t=int64-12 2846494 410.4 ns/op 157 B/op 4 allocs/op
BenchmarkCache/cache=sieve/t=int64-12 2455807 440.1 ns/op 158 B/op 4 allocs/op
BenchmarkCache/cache=sieve/t=int64-12 2774462 435.1 ns/op 158 B/op 4 allocs/op
BenchmarkCache/cache=sieve/t=int64-12 2833150 433.9 ns/op 157 B/op 4 allocs/op
BenchmarkCache/cache=sieve/t=string
BenchmarkCache/cache=sieve/t=string-12 2117859 546.9 ns/op 186 B/op 4 allocs/op
BenchmarkCache/cache=sieve/t=string-12 2079752 527.1 ns/op 186 B/op 4 allocs/op
BenchmarkCache/cache=sieve/t=string-12 2210930 530.8 ns/op 186 B/op 4 allocs/op
BenchmarkCache/cache=sieve/t=string-12 2122942 514.4 ns/op 186 B/op 4 allocs/op
BenchmarkCache/cache=sieve/t=string-12 2222488 553.6 ns/op 186 B/op 4 allocs/op
BenchmarkCache/cache=sieve/t=string-12 2260266 558.6 ns/op 186 B/op 4 allocs/op
BenchmarkCache/cache=sieve/t=string-12 2239196 567.1 ns/op 186 B/op 4 allocs/op
BenchmarkCache/cache=sieve/t=string-12 2064308 576.8 ns/op 186 B/op 4 allocs/op
BenchmarkCache/cache=sieve/t=string-12 1882754 569.9 ns/op 185 B/op 4 allocs/op
BenchmarkCache/cache=sieve/t=string-12 1917342 574.6 ns/op 185 B/op 4 allocs/op
BenchmarkCache/cache=sieve/t=composite
BenchmarkCache/cache=sieve/t=composite-12 1825063 707.0 ns/op 223 B/op 4 allocs/op
BenchmarkCache/cache=sieve/t=composite-12 1745775 660.1 ns/op 224 B/op 4 allocs/op
BenchmarkCache/cache=sieve/t=composite-12 1680552 678.1 ns/op 225 B/op 4 allocs/op
BenchmarkCache/cache=sieve/t=composite-12 1774438 690.1 ns/op 224 B/op 4 allocs/op
BenchmarkCache/cache=sieve/t=composite-12 1530580 731.1 ns/op 226 B/op 4 allocs/op
BenchmarkCache/cache=sieve/t=composite-12 1663950 761.7 ns/op 225 B/op 4 allocs/op
BenchmarkCache/cache=sieve/t=composite-12 1607760 678.4 ns/op 225 B/op 4 allocs/op
BenchmarkCache/cache=sieve/t=composite-12 1703283 784.4 ns/op 225 B/op 4 allocs/op
BenchmarkCache/cache=sieve/t=composite-12 1295089 864.6 ns/op 229 B/op 4 allocs/op
BenchmarkCache/cache=sieve/t=composite-12 1552182 769.9 ns/op 226 B/op 4 allocs/op
```
</details>

<details>
<summary>Why LRU Cache is not good enough?</summary>

Expand Down Expand Up @@ -144,5 +201,5 @@ $ go test -v ./...

How to run benchmark test
```bash
$ go test -bench=. -benchtime=10s
$ ./bench.sh
```
4 changes: 4 additions & 0 deletions bench.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/bash

go test -c
./golang-fifo.test -test.v -test.run - -test.bench . -test.count 10 -test.benchmem -test.timeout 10h | tee out
85 changes: 85 additions & 0 deletions bench_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package golang_fifo

import (
"strconv"
"testing"

"github.com/scalalang2/golang-fifo/sieve"
)

type value struct {
bytes []byte
}

type compositeKey struct {
key1 string
key2 string
}

type benchTypes interface {
int32 | int64 | string | compositeKey
}

func BenchmarkCache(b *testing.B) {
b.Run("cache=sieve", func(b *testing.B) {
b.Run("t=int32", bench[int32](genKeysInt32))
b.Run("t=int64", bench[int64](genKeysInt64))
b.Run("t=string", bench[string](genKeysString))
b.Run("t=composite", bench[compositeKey](genKeysComposite))
})
}

func bench[T benchTypes](gen func(workload int) []T) func(b *testing.B) {
cacheSize := 100000

return func(b *testing.B) {
benchmarkSieveCache[T](b, cacheSize, gen)
}
}

func genKeysInt32(workload int) []int32 {
keys := make([]int32, workload)
for i := range keys {
keys[i] = int32(i)
}
return keys
}

func genKeysInt64(workload int) []int64 {
keys := make([]int64, workload)
for i := range keys {
keys[i] = int64(i)
}
return keys
}

func genKeysString(workload int) []string {
keys := make([]string, workload)
for i := range keys {
keys[i] = strconv.Itoa(i)
}
return keys
}

func genKeysComposite(workload int) []compositeKey {
keys := make([]compositeKey, workload)
for i := range keys {
keys[i].key1 = strconv.Itoa(i)
keys[i].key2 = strconv.Itoa(i)
}
return keys
}

func benchmarkSieveCache[T benchTypes](b *testing.B, cacheSize int, genKey func(size int) []T) {
cache := sieve.New[T, value](cacheSize, 0)
keys := genKey(b.N)
b.ResetTimer()
for i := 0; i < b.N; i++ {
key := keys[i]
cache.Set(key, value{
bytes: make([]byte, 10),
})
cache.Get(key)
}
cache.Purge()
}
6 changes: 5 additions & 1 deletion sieve/sieve.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ package sieve
import (
"container/list"
"context"
"github.com/scalalang2/golang-fifo/types"
"sync"
"time"

"github.com/scalalang2/golang-fifo/types"
)

const numberOfBuckets = 100
Expand Down Expand Up @@ -192,6 +193,9 @@ func (s *Sieve[K, V]) Purge() {
}
}

// hand pointer must also be reset
s.hand = nil
s.nextCleanupBucket = 0
s.ll.Init()
}

Expand Down
19 changes: 19 additions & 0 deletions sieve/sieve_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,3 +199,22 @@ func TestEvictionCallbackWithTTL(t *testing.T) {
}
}
}

func TestLargerWorkloadsThanCacheSize(t *testing.T) {
type value struct {
bytes []byte
}

cache := New[int32, value](128, 0)
workload := int32(256)
for i := int32(0); i < workload; i++ {
val := value{
bytes: make([]byte, 10),
}
cache.Set(i, val)

v, ok := cache.Get(i)
require.True(t, ok)
require.Equal(t, v, val)
}
}

0 comments on commit 75f0941

Please sign in to comment.