Skip to content

Commit

Permalink
add gc pause tests
Browse files Browse the repository at this point in the history
  • Loading branch information
phuslu committed Mar 24, 2024
1 parent b34ef88 commit a8f255a
Show file tree
Hide file tree
Showing 2 changed files with 205 additions and 0 deletions.
43 changes: 43 additions & 0 deletions .github/workflows/gcpause.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: memory

on:
schedule:
- cron: '0 0 * * *'
push:
branches:
- master

jobs:
memory:
runs-on: ubuntu-latest
steps:
- name: Install packages
run: sudo apt update -y && sudo apt-get install -y csvkit datamash
- uses: actions/setup-go@v4
with:
go-version: '1.22'
check-latest: true
- uses: actions/checkout@v4
- name: go mod tidy
run: |
mkdir bench
cd bench
awk '{if($0 ~ "// gcpause.go"){a=1;b=1};if($0 ~ "```" && b=1){b=0};if (a&&b) {print}}' ../README.md > gcpause.go
go mod init bench
go mod tidy
go mod edit -replace github.com/phuslu/lru=../
cat go.mod
- name: GC Tests
working-directory: ./bench
run: |
echo -e 'MemStats\tCacheSize\tAlloc\tTotalAlloc\tSys' > gcpause.txt
for name in nottl phuslu lxzan ristretto freelru ecache otter theine cloudflare ccache hashicorp; do
for cachesize in 100000 200000 400000 1000000; do
go run gcpause.go $name $cachesize >> gcpause.txt
done
done
cat gcpause.txt
- name: GC Results
working-directory: ./bench
run: |
cat gcpause.txt | datamash --header-in crosstab 1,2 unique 3 | tee >(head -1) | tail -n +2 | sort -k12 -n | datamash transpose | tee >(head -1) | tail -n +2 | sort -n | datamash transpose | csvlook
162 changes: 162 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -667,6 +667,168 @@ func SetupHashicorp(cachesize int) {
- ristretto employs a questionable usage pattern due to its rejection of items via a bloom filter, resulting in a lower hit ratio.
- freelru overcommits the cache size to the next power of 2, leading to higher memory usage particularly at larger cache sizes.

### GC pause

The GC pause result as below. Check github [gcpause][gcpause] action for more results and details.
<details>
<summary>GC pause on keysize=16(string), valuesize=8(int), cachesize in (100000,200000,400000,1000000)</summary>

```go
// gcpause.go
package main

import (
"fmt"
"os"
"runtime"
"runtime/debug"
"strconv"
"time"

theine "github.com/Yiling-J/theine-go"
"github.com/cespare/xxhash/v2"
cloudflare "github.com/cloudflare/golibs/lrucache"
ristretto "github.com/dgraph-io/ristretto"
freelru "github.com/elastic/go-freelru"
hashicorp "github.com/hashicorp/golang-lru/v2/expirable"
ccache "github.com/karlseguin/ccache/v3"
lxzan "github.com/lxzan/memorycache"
otter "github.com/maypok86/otter"
ecache "github.com/orca-zhang/ecache"
phuslu "github.com/phuslu/lru"
)

const keysize = 16
const repeat = 100

var keys []string

func main() {
name := os.Args[1]
cachesize, _ := strconv.Atoi(os.Args[2])

keys = make([]string, cachesize)
for i := 0; i < cachesize; i++ {
keys[i] = fmt.Sprintf(fmt.Sprintf("%%0%dd", keysize), i)
}

setup := map[string]func(int){
"nottl": SetupNottl,
"phuslu": SetupPhuslu,
"freelru": SetupFreelru,
"ristretto": SetupRistretto,
"otter": SetupOtter,
"lxzan": SetupLxzan,
"ecache": SetupEcache,
"cloudflare": SetupCloudflare,
"ccache": SetupCcache,
"hashicorp": SetupHashicorp,
"theine": SetupTheine,
}[name]

runtime.GC()

var o debug.GCStats
debug.ReadGCStats(&o)

for i := 0; i < repeat; i++ {
setup(cachesize)
}

var s debug.GCStats
debug.ReadGCStats(&s)

fmt.Printf("%s %s\n", name, s.PauseTotal-o.PauseTotal)
}

func SetupNottl(cachesize int) {
cache := phuslu.NewLRUCache[string, int](cachesize)
for i := 0; i < cachesize; i++ {
cache.Set(keys[i], i)
}
}

func SetupPhuslu(cachesize int) {
cache := phuslu.NewTTLCache[string, int](cachesize)
for i := 0; i < cachesize; i++ {
cache.Set(keys[i], i, time.Hour)
}
}

func SetupFreelru(cachesize int) {
cache, _ := freelru.NewSharded[string, int](uint32(cachesize), func(s string) uint32 { return uint32(xxhash.Sum64String(s)) })
for i := 0; i < cachesize; i++ {
cache.AddWithLifetime(keys[i], i, time.Hour)
}
}

func SetupOtter(cachesize int) {
cache, _ := otter.MustBuilder[string, int](cachesize).WithVariableTTL().Build()
for i := 0; i < cachesize; i++ {
cache.Set(keys[i], i, time.Hour)
}
}

func SetupEcache(cachesize int) {
cache := ecache.NewLRUCache(1024, uint16(cachesize/1024), time.Hour)
for i := 0; i < cachesize; i++ {
cache.Put(keys[i], i)
}
}

func SetupRistretto(cachesize int) {
cache, _ := ristretto.NewCache(&ristretto.Config{
NumCounters: int64(10 * cachesize), // number of keys to track frequency of (10M).
MaxCost: int64(cachesize), // maximum cost of cache (1M).
BufferItems: 64, // number of keys per Get buffer.
})
for i := 0; i < cachesize; i++ {
cache.SetWithTTL(keys[i], i, 1, time.Hour)
}
}

func SetupLxzan(cachesize int) {
cache := lxzan.New[string, int](
lxzan.WithBucketNum(128),
lxzan.WithBucketSize(cachesize/128, cachesize/128),
lxzan.WithInterval(time.Hour, time.Hour),
)
for i := 0; i < cachesize; i++ {
cache.Set(keys[i], i, time.Hour)
}
}

func SetupTheine(cachesize int) {
cache, _ := theine.NewBuilder[string, int](int64(cachesize)).Build()
for i := 0; i < cachesize; i++ {
cache.SetWithTTL(keys[i], i, 1, time.Hour)
}
}

func SetupCloudflare(cachesize int) {
cache := cloudflare.NewMultiLRUCache(1024, uint(cachesize/1024))
for i := 0; i < cachesize; i++ {
cache.Set(keys[i], i, time.Now().Add(time.Hour))
}
}

func SetupCcache(cachesize int) {
cache := ccache.New(ccache.Configure[int]().MaxSize(int64(cachesize)).ItemsToPrune(100))
for i := 0; i < cachesize; i++ {
cache.Set(keys[i], i, time.Hour)
}
}

func SetupHashicorp(cachesize int) {
cache := hashicorp.NewLRU[string, int](cachesize, nil, time.Hour)
for i := 0; i < cachesize; i++ {
cache.Add(keys[i], i)
}
}
```
</details>


### Hit ratio
It is a classic sharded LRU implementation, so the hit ratio is comparable to or slightly lower than a regular LRU.

Expand Down

0 comments on commit a8f255a

Please sign in to comment.