From 02c79c9e8849802d4917cd642ed9179c3e0b7526 Mon Sep 17 00:00:00 2001 From: Esteban Del Boca Date: Fri, 6 Sep 2024 14:12:03 -0300 Subject: [PATCH] Move towards Github Actions + Deprecating old code/tooling (#35) * Enable Github actions * [Deprecation] Deprecating code and utilities * [deprecate] Earthly builds * Final draft of the github actions * Go Releaser on GitHub actions --- .buildkite/pipeline.yaml | 80 --- .buildkite/steps/common | 12 - .buildkite/steps/release | 9 - .earthignore | 5 - .github/workflows/go.yml | 33 ++ .github/workflows/release.yml | 29 + .gitignore | 1 + .golangci.yml | 5 +- .goreleaser.yml | 8 +- Earthfile | 269 ---------- bench/.golangci.yml | 36 -- bench/README.md | 50 -- bench/go.mod | 24 - bench/go.sum | 24 - bench/hashicorp_wrapper.go | 126 ----- bench/lazylru_benchmark_test.go | 101 ---- bench/main.go | 252 --------- bench/main_test.go | 83 --- bench/map_cache.go | 73 --- bench/null_cache.go | 17 - bench/results.csv.gz | Bin 33911 -> 0 bytes bench/revive.toml | 27 - bench/test_data.go | 83 --- generic/.golangci.yml | 37 -- generic/Earthfile | 64 --- generic/benchmark_results.txt | 95 ---- generic/containers/heap/README.md | 5 - generic/containers/heap/heap.go | 118 ---- generic/containers/heap/heap_test.go | 214 -------- generic/expected_stats_test.go | 93 ---- generic/fuzz_test.go | 40 -- generic/go.mod | 17 - generic/go.sum | 26 - generic/lazylru.go | 507 ------------------ generic/lazylru_benchmark_test.go | 237 -------- generic/lazylru_test.go | 448 ---------------- generic/pq.go | 59 -- generic/pq_test.go | 77 --- generic/revive.toml | 27 - generic/sharded/README.md | 144 ----- .../benchmark_results_macbook_pro_m1_8.txt | 336 ------------ .../benchmark_results_n2-highcpu-64_64.txt | 337 ------------ generic/sharded/expected_stats_test.go | 93 ---- generic/sharded/hasher.go | 111 ---- generic/sharded/hasher_test.go | 131 ----- generic/sharded/lazylru_benchmark_test.go | 115 ---- generic/sharded/shard_helper_key.go | 77 --- generic/sharded/shard_helper_kv.go | 85 --- generic/sharded/shard_helper_test.go | 85 --- generic/sharded/sharded.go | 244 --------- generic/sharded/sharded_test.go | 417 -------------- generic/sharded/sharder.go | 64 --- generic/sharded/sharder_test.go | 203 ------- generic/stats.go | 17 - go.mod | 10 +- go.sum | 11 +- go.work.sum | 8 - revive.toml | 27 - 58 files changed, 74 insertions(+), 5852 deletions(-) delete mode 100755 .buildkite/pipeline.yaml delete mode 100644 .buildkite/steps/common delete mode 100755 .buildkite/steps/release delete mode 100644 .earthignore create mode 100644 .github/workflows/go.yml create mode 100644 .github/workflows/release.yml delete mode 100644 Earthfile delete mode 100644 bench/.golangci.yml delete mode 100644 bench/README.md delete mode 100644 bench/go.mod delete mode 100644 bench/go.sum delete mode 100644 bench/hashicorp_wrapper.go delete mode 100644 bench/lazylru_benchmark_test.go delete mode 100644 bench/main.go delete mode 100644 bench/main_test.go delete mode 100644 bench/map_cache.go delete mode 100644 bench/null_cache.go delete mode 100644 bench/results.csv.gz delete mode 100644 bench/revive.toml delete mode 100644 bench/test_data.go delete mode 100644 generic/.golangci.yml delete mode 100644 generic/Earthfile delete mode 100644 generic/benchmark_results.txt delete mode 100644 generic/containers/heap/README.md delete mode 100644 generic/containers/heap/heap.go delete mode 100644 generic/containers/heap/heap_test.go delete mode 100644 generic/expected_stats_test.go delete mode 100644 generic/fuzz_test.go delete mode 100644 generic/go.mod delete mode 100644 generic/go.sum delete mode 100644 generic/lazylru.go delete mode 100644 generic/lazylru_benchmark_test.go delete mode 100644 generic/lazylru_test.go delete mode 100644 generic/pq.go delete mode 100644 generic/pq_test.go delete mode 100644 generic/revive.toml delete mode 100644 generic/sharded/README.md delete mode 100644 generic/sharded/benchmark_results_macbook_pro_m1_8.txt delete mode 100644 generic/sharded/benchmark_results_n2-highcpu-64_64.txt delete mode 100644 generic/sharded/expected_stats_test.go delete mode 100644 generic/sharded/hasher.go delete mode 100644 generic/sharded/hasher_test.go delete mode 100644 generic/sharded/lazylru_benchmark_test.go delete mode 100644 generic/sharded/shard_helper_key.go delete mode 100644 generic/sharded/shard_helper_kv.go delete mode 100644 generic/sharded/shard_helper_test.go delete mode 100644 generic/sharded/sharded.go delete mode 100644 generic/sharded/sharded_test.go delete mode 100644 generic/sharded/sharder.go delete mode 100644 generic/sharded/sharder_test.go delete mode 100644 generic/stats.go delete mode 100644 go.work.sum delete mode 100644 revive.toml diff --git a/.buildkite/pipeline.yaml b/.buildkite/pipeline.yaml deleted file mode 100755 index 32b1f8c..0000000 --- a/.buildkite/pipeline.yaml +++ /dev/null @@ -1,80 +0,0 @@ -name: lazylru -description: lazylru build pipeline -env: - BUILDKITE_PLUGIN_GCR_JSON_KEY: ${BUILDKITE_PLUGIN_GCR_JSON_KEY} - EARTHLY_SSH_AUTH_SOCK: "/root/.ssh/ssh-agent.sock" - -retry: &retry_spot_instance - automatic: - - exit_status: -1 - limit: 3 - - exit_status: '*' - limit: 3 -steps: -- label: "interface-based" - key: "interface" - plugins: - - ssh://git@github.com/TriggerMail/coveralls-buildkite-plugin#v1.0.4: - login: true - agents: - queue: vmserver - env: - COVERALLS_TOKEN: ${COVERALLS_TOKEN} - command: "earthly --secret COVERALLS_TOKEN +ci-interface --BUILD_NUMBER=$BUILDKITE_BUILD_NUMBER" - artifact_paths: test-results/interface/*.xml - retry: *retry_spot_instance -- label: "bench" - key: "bench" - plugins: - - ssh://git@github.com/TriggerMail/coveralls-buildkite-plugin#v1.0.4: - login: true - agents: - queue: vmserver - env: - COVERALLS_TOKEN: ${COVERALLS_TOKEN} - command: "earthly --secret COVERALLS_TOKEN +ci-bench --BUILD_NUMBER=$BUILDKITE_BUILD_NUMBER" - artifact_paths: test-results/bench/*.xml - retry: *retry_spot_instance -- label: "generic" - key: "generic" - plugins: - - ssh://git@github.com/TriggerMail/coveralls-buildkite-plugin#v1.0.4: - login: true - agents: - queue: vmserver - env: - COVERALLS_TOKEN: ${COVERALLS_TOKEN} - command: "earthly --secret COVERALLS_TOKEN +ci-generic --BUILD_NUMBER=$BUILDKITE_BUILD_NUMBER" - artifact_paths: test-results/generic/*.xml - retry: *retry_spot_instance -- label: ":golang: release" - key: "release" - agents: - queue: vmserver - command: ".buildkite/steps/release" - plugins: - - ssh://git@github.com/TriggerMail/coveralls-buildkite-plugin#v1.0.4: - login: true - if: build.tag =~ /v[0-9]+(\.[0-9]+)*(-.*)*/ - depends_on: - - "generic" - - "interface" - retry: *retry_spot_instance -- wait: - continue_on_failure: true -- label: "collect test results" - key: "collect" - commands: - - "buildkite-agent artifact download test-results/interface/*.xml . --step interface" - - "buildkite-agent artifact download test-results/bench/*.xml . --step bench" - - "buildkite-agent artifact download test-results/generic/*.xml . --step generic" - - "find ./test-results -name 'go-test-*-report.xml' -exec mv {} test-results/ \\;" - artifact_paths: test-results/*.xml - retry: *retry_spot_instance -- label: "report test results" - depends_on: "collect" - key: "test_results" - plugins: - - junit-annotate#v1.9.0: - artifacts: test-results/go-test-*.xml - retry: *retry_spot_instance diff --git a/.buildkite/steps/common b/.buildkite/steps/common deleted file mode 100644 index 742817c..0000000 --- a/.buildkite/steps/common +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env sh - -config_git() { - echo "configuring git..." - git config --global url."git@github.com:".insteadOf "https://github.com/" -} - -shout() { echo "$0: $*" >&2; } -barf() { shout "$*"; exit 111; } -try() { "$@" || barf "cannot $*"; } - -# vi: ft=sh diff --git a/.buildkite/steps/release b/.buildkite/steps/release deleted file mode 100755 index 341a09b..0000000 --- a/.buildkite/steps/release +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env bash - -. .buildkite/steps/common - -git fetch --tags - -try curl -sL https://git.io/goreleaser | bash - -# vi: ft=sh diff --git a/.earthignore b/.earthignore deleted file mode 100644 index fcff19a..0000000 --- a/.earthignore +++ /dev/null @@ -1,5 +0,0 @@ -.buildkite/ -.vscode/ -vendor/ -generic/vendor/ -test-results/ diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml new file mode 100644 index 0000000..d608127 --- /dev/null +++ b/.github/workflows/go.yml @@ -0,0 +1,33 @@ +name: go + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +jobs: + + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: 1.23 + + - name: Go Tidy + run: go mod tidy && git diff --exit-code + + - name: Build + run: go build -v ./... + + - name: Test + run: go test --count=1 --timeout=30s -v ./... + + - name: Lint code + uses: golangci/golangci-lint-action@v6 + with: + only-new-issues: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..5de7500 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,29 @@ +name: release + +on: + push: + tags: + - '*' + +permissions: + contents: write + +jobs: + goreleaser: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v5 + + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v6 + with: + distribution: goreleaser + version: '~> v2' + args: release --clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 6701ae9..b91996b 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ .DS_Store test-results/ dist/ +vendor/ diff --git a/.golangci.yml b/.golangci.yml index 8b5729b..739c217 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,10 +1,10 @@ run: - go: "1.20" + go: "1.23" timeout: 5m issues-exit-code: 1 tests: true skip-dirs-use-default: true - modules-download-mode: vendor + modules-download-mode: readonly linters: enable: @@ -32,3 +32,4 @@ issues: exclude: - EXC0002 # Annoying issue about not having a comment - EXC0012 # func should have comment or be unexported + - EXG115 # integer overflow conversion uint64 -> int diff --git a/.goreleaser.yml b/.goreleaser.yml index c482639..80ad9b4 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -1,14 +1,8 @@ -# yaml-language-server: $schema=https://goreleaser.com/static/schema.json -# vim: set ts=2 sw=2 tw=0 fo=cnqoj -version: 2 - project_name: lazylru - +version: 2 builds: - skip: true - release: prerelease: auto - changelog: sort: asc diff --git a/Earthfile b/Earthfile deleted file mode 100644 index 4d19ea2..0000000 --- a/Earthfile +++ /dev/null @@ -1,269 +0,0 @@ -VERSION 0.6 - -FROM golang:1.23 - -all-bench: - BUILD +fmt-bench - BUILD +lint-bench - BUILD +vet-bench - BUILD +test-bench - -all-interface: - BUILD +fmt-interface - BUILD +lint-interface - BUILD +vet-interface - BUILD +test-interface - -all-generic: - BUILD +fmt-generic - BUILD +lint-generic - BUILD +vet-generic - BUILD +test-generic - -all: - BUILD +all-bench - BUILD +all-interface - BUILD +all-generic - -ci-bench: - ARG --required BUILD_NUMBER - BUILD +fmt-bench - BUILD +lint-bench - BUILD +vet-bench - COPY --dir +test-bench/files ./test-results/bench - SAVE ARTIFACT ./test-results/bench AS LOCAL test-results/bench - - BUILD +publish-coverage-bench --BUILD_NUMBER=$BUILD_NUMBER - -ci-interface: - ARG --required BUILD_NUMBER - BUILD +fmt-interface - BUILD +lint-interface - BUILD +vet-interface - COPY --dir +test-interface/files ./test-results/interface - SAVE ARTIFACT ./test-results/interface AS LOCAL test-results/interface - BUILD +publish-coverage-interface --BUILD_NUMBER=$BUILD_NUMBER - -ci-generic: - ARG --required BUILD_NUMBER - BUILD +fmt-generic - BUILD +lint-generic - BUILD +vet-generic - COPY --dir +test-generic/files ./test-results/generic - SAVE ARTIFACT ./test-results/generic AS LOCAL test-results/generic - BUILD +publish-coverage-generic --BUILD_NUMBER=$BUILD_NUMBER - -ci: - ARG --required BUILD_NUMBER - BUILD +ci-bench --BUILD_NUMBER=$BUILD_NUMBER - BUILD +ci-interface --BUILD_NUMBER=$BUILD_NUMBER - BUILD +ci-generic --BUILD_NUMBER=$BUILD_NUMBER - -go-mod-bench: - WORKDIR /bench - RUN git config --global url."git@github.com:".insteadOf "https://github.com/" - RUN mkdir -p -m 0600 ~/.ssh && ssh-keyscan github.com >> ~/.ssh/known_hosts - COPY go.mod go.sum . - RUN --ssh go mod download - -go-mod-interface: - WORKDIR /app - RUN git config --global url."git@github.com:".insteadOf "https://github.com/" - RUN mkdir -p -m 0600 ~/.ssh && ssh-keyscan github.com >> ~/.ssh/known_hosts - COPY go.mod go.sum . - RUN --ssh go mod download - -go-mod-generic: - WORKDIR /generic - RUN git config --global url."git@github.com:".insteadOf "https://github.com/" - RUN mkdir -p -m 0600 ~/.ssh && ssh-keyscan github.com >> ~/.ssh/known_hosts - COPY go.mod go.sum . - RUN --ssh go mod download - -go-mod: - BUILD +go-mod-bench - BUILD +go-mod-interface - BUILD +go-mod-generic - -fmt-bench: - COPY --dir ./bench /bench - WORKDIR /bench - RUN find . -type d -path "./vendor" -prune -o -name "*.go" -exec gofmt -d -e {} \; | tee /tmp/gofmt.out - RUN bash -c 'if [[ -s /tmp/gofmt.out ]]; then exit 1; fi' - -fmt-interface: - COPY --dir . /app - RUN rm -rf generic - RUN rm -rf bench - RUN find . -type d -path "./vendor" -prune -o -name "*.go" -exec gofmt -d -e {} \; | tee /tmp/gofmt.out - RUN bash -c 'if [[ -s /tmp/gofmt.out ]]; then exit 1; fi' - -fmt-generic: - COPY --dir ./generic /generic - WORKDIR /generic - RUN find . -type d -path "./vendor" -prune -o -name "*.go" -exec gofmt -d -e {} \; | tee /tmp/gofmt.out - RUN bash -c 'if [[ -s /tmp/gofmt.out ]]; then exit 1; fi' - -fmt: - BUILD +fmt-bench - BUILD +fmt-interface - BUILD +fmt-generic - -vendor-bench: - FROM +go-mod-bench - WORKDIR /app - COPY --dir . . - WORKDIR /app/bench - RUN --ssh go mod vendor - SAVE ARTIFACT . files - -vendor-interface: - FROM +go-mod-interface - WORKDIR /app - COPY --dir . . - RUN rm -rf generic - RUN rm -rf bench - RUN --ssh go mod vendor - SAVE ARTIFACT . files - -vendor-generic: - FROM +go-mod-generic - WORKDIR /app - COPY --dir .git . - COPY ./generic ./generic - WORKDIR /app/generic - RUN --ssh go mod vendor - SAVE ARTIFACT . files - -vendor: - BUILD +vendor-bench - BUILD +vendor-interface - BUILD +vendor-generic - -lint-bench: - FROM +vendor-bench - COPY +golangci-lint/go/bin/golangci-lint /go/bin/golangci-lint - RUN golangci-lint run - -lint-interface: - FROM +vendor-interface - COPY +golangci-lint/go/bin/golangci-lint /go/bin/golangci-lint - RUN golangci-lint run - -lint-generic: - FROM +vendor-generic - COPY +golangci-lint/go/bin/golangci-lint /go/bin/golangci-lint - RUN golangci-lint run - -lint: - BUILD +lint-bench - BUILD +lint-interface - BUILD +lint-generic - -vet-bench: - FROM +vendor-bench - RUN go vet ./... - -vet-interface: - FROM +vendor-interface - RUN go vet ./... - -vet-generic: - FROM +vendor-generic - RUN go vet ./... - -vet: - BUILD +vet-bench - BUILD +vet-interface - BUILD +vet-generic - -test-bench: - FROM +vendor-bench - COPY +junit-report/go/bin/go-junit-report /go/bin/go-junit-report - RUN go version - RUN mkdir -p test-results - # To both see the output in the console AND convert into junit-style results - # to send to the plug-in, we need to run the tests, writing to a file, then - # send that file to go-junit-report - RUN 2>&1 go test -race -v ./... -cover -coverprofile=test-results/cover.out | tee test-results/go-test-bench.out - RUN cat test-results/go-test-bench.out | $GOPATH/bin/go-junit-report > test-results/go-test-bench-report.xml - SAVE ARTIFACT test-results files - -test-interface: - FROM +vendor-interface - COPY +junit-report/go/bin/go-junit-report /go/bin/go-junit-report - RUN go version - RUN mkdir -p test-results - # To both see the output in the console AND convert into junit-style results - # to send to the plug-in, we need to run the tests, writing to a file, then - # send that file to go-junit-report - RUN 2>&1 go test -race -v ./... -cover -coverprofile=test-results/cover.out | tee test-results/go-test-interface.out - RUN cat test-results/go-test-interface.out | $GOPATH/bin/go-junit-report > test-results/go-test-interface-report.xml - SAVE ARTIFACT test-results files - -test-generic: - FROM +vendor-generic - COPY +junit-report/go/bin/go-junit-report /go/bin/go-junit-report - RUN go version - RUN mkdir -p test-results - # To both see the output in the console AND convert into junit-style results - # to send to the plug-in, we need to run the tests, writing to a file, then - # send that file to go-junit-report - RUN 2>&1 go test -race -v ./... -cover -coverprofile=test-results/cover.out | tee test-results/go-test-generic.out - RUN cat test-results/go-test-generic.out | $GOPATH/bin/go-junit-report > test-results/go-test-generic-report.xml - SAVE ARTIFACT test-results files - -test: - COPY --dir +test-bench/files ./test-results/bench - COPY --dir +test-interface/files ./test-results/interface - COPY --dir +test-generic/files ./test-results/generic - SAVE ARTIFACT ./test-results AS LOCAL test-results - -publish-coverage-interface: - ARG --required BUILD_NUMBER - FROM +test-interface - COPY +goveralls/go/bin/goveralls /go/bin/goveralls - RUN --no-cache --secret COVERALLS_TOKEN=+secrets/COVERALLS_TOKEN \ - goveralls \ - -jobnumber="$BUILD_NUMBER" \ - -flagname=interface \ - -service=buildkite \ - -coverprofile=test-results/cover.out - -publish-coverage-bench: - ARG --required BUILD_NUMBER - FROM +test-bench - COPY +goveralls/go/bin/goveralls /go/bin/goveralls - RUN --no-cache --secret COVERALLS_TOKEN=+secrets/COVERALLS_TOKEN \ - goveralls \ - -jobnumber="$BUILD_NUMBER" \ - -flagname=bench \ - -service=buildkite \ - -coverprofile=test-results/cover.out - -publish-coverage-generic: - ARG --required BUILD_NUMBER - FROM +test-generic - COPY +goveralls/go/bin/goveralls /go/bin/goveralls - RUN --no-cache --secret COVERALLS_TOKEN=+secrets/COVERALLS_TOKEN \ - goveralls \ - -jobnumber="$BUILD_NUMBER" \ - -flagname=generic \ - -service=buildkite \ - -coverprofile=test-results/cover.out - -# These are tools that are used in the targets above -goveralls: - RUN echo Installing goveralls - RUN go install github.com/mattn/goveralls@latest - SAVE ARTIFACT /go/bin/goveralls /go/bin/goveralls - -golangci-lint: - RUN echo Installing golangci-lint... - # see https://golangci-lint.run/usage/install/#other-ci - RUN curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b /go/bin v1.60.3 - SAVE ARTIFACT /go/bin/golangci-lint /go/bin/golangci-lint - -junit-report: - RUN go install github.com/jstemmer/go-junit-report@latest - SAVE ARTIFACT /go/bin/go-junit-report /go/bin/go-junit-report diff --git a/bench/.golangci.yml b/bench/.golangci.yml deleted file mode 100644 index a2a3f0c..0000000 --- a/bench/.golangci.yml +++ /dev/null @@ -1,36 +0,0 @@ -run: - go: "1.18" - timeout: 5m - issues-exit-code: 1 - tests: true - skip-dirs-use-default: true - modules-download-mode: vendor - -linters: - disable: - - structcheck # this is disabled because v1.45 is showing a lot of false positives - enable: - - revive - - gofmt - - govet - - gosec - - unconvert - - goconst - - gocyclo - - goimports - -linters-settings: - govet: - enable: - - fieldalignment - fieldalignment: - fix: true - revive: - rules: - - name: unused-parameter - disabled: true - -issues: - exclude: - - EXC0002 # Annoying issue about not having a comment - - EXC0012 # func should have comment or be unexported diff --git a/bench/README.md b/bench/README.md deleted file mode 100644 index d78f7b2..0000000 --- a/bench/README.md +++ /dev/null @@ -1,50 +0,0 @@ -# LazyLRU Benchmarking - -Because this implementation is designed for groups of keys that come in waves, a simple [testing benchmark](https://golang.org/pkg/testing/#hdr-Benchmarks) that reads and writes random keys would not be an accurate representation of this library. For those kinds of general loads, [hashicorp/golang-lru](https://github.com/hashicorp/golang-lru) is just as good as LazyLRU when the cache is >25% full. So these benchmarks try to fill that gap. - -Benchmarking independently is interesting, but not as instructive. The candidates for all the tests were: - -* **null**: Do nothing. Don't save anything. All `get` operations are misses. -* **mapcache.{hour|50ms}**: A map of `key => {value, expiration}`. If the map is full, items are dropped at random. The time indicates the expiration -- _50ms_ for expiring frequently relative to read/write operations, _hour_ for exprining infrequently relative to read/write operations. -* **lazylru.{hour|50ms}**: The thing in this repo. The one we're here to test. -* **hashicorp.lru**: This is the default implementation in the [hashicorp/golang-lru](https://pkg.go.dev/github.com/hashicorp/golang-lru?utm_source=godoc) package. This is the implementation based on [groupcache](https://github.com/golang/groupcache/blob/master/lru/lru.go). _This implementation does not support expiration._ -* **hashicorp.exp_{hour|50ms}**: This is the hashicorp.lru, but instead of storing raw values, we store `key => {value, expiration}` like we did in the mapcache above. Expiry is checked on read and stale values are discarded. -* **hashicorp.arc**: hashicorp's implementation of the [Adaptive Relay Cache](https://www.usenix.org/legacy/event/fast03/tech/full_papers/megiddo/megiddo.pdf). _This implementation does not support expiration._ -* **hashicorp.2Q**: hashicorp's implementation of the [multi-queue replacement algorithm](https://static.usenix.org/event/usenix01/full_papers/zhou/zhou.pdf). - -These tests define sets of keys, then rotate through those sets. This is meant to simulate the waves of requests for a set of keys that would come as marketing sends run through a day. Tests have the following parameters: - -* **algorithm**: What we are testing -* **ranges**: How many ranges of keys are in the test -* **keys/range**: How big each range is -* **cycles/range**: How many times each range is read -* **threads**: The number of concurrent reader/writer workers -* **size**: Capacity of the cache under test -* **work_time_µs**: On each operation, spin-wait to alleviate lock contention while not releasing the CPU -* **sleep_time_µs**: On each operation, sleep to allevaite lock contention while yielding -* **cycles**: How may read or write operations are in the test -* **duration_ms**: How long the test took -* **rate_kHz**: Cycles/duration -* **hit_rate_%**: How efficient the cache was - -I ran 253 variations of these tests on a Google Cloud [n1-standard-8](https://cloud.google.com/compute/docs/machine-types#n1_machine_types) (8-core) VM running Go 1.16.4. I've included the [raw results](results.csv.gz) in this repo. - -* **Test A**: 5 ranges of 1000 keys, 1000000 cycles/range, size 10000, 1 thread, 0 work, 0 sleep. -* **Test B**: 5 ranges of 1000 keys, 1000000 cycles/range, size 10000, 64 thread, 0 work, 0 sleep -* **Test C**: 1 ranges of 20000 keys, 1000000 cycles/range, size 10000, 64 thread, 0 work, 0 sleep - -| Algorithm | rate (kHz) | hit rate (%) | rate (kHz) | hit rate (%) | rate (kHz) | hit rate (%) | -| ------------------ | ---------: | -----------: | ---------: | -----------: | ---------: | -----------: | -| | **Test A** | **Test A** | **Test B** | **Test B** | **Test C** | **Test C** | -| null | 18532.44 | 0.00 | 3755.14 | 0.00 | 3131.50 | 0.00 | -| mapcache.hour | 3177.00 | 99.90 | 1854.16 | 99.90 | 881.83 | 10.00 | -| mapcache.50ms | 3108.52 | 96.83 | 1654.61 | 94.20 | 865.27 | 8.20 | -| lazylru.hour | 4811.95 | 99.90 | 2719.89 | 99.90 | 462.65 | 9.96 | -| lazylru.50ms | 3977.11 | 97.50 | 1466.69 | 93.29 | 458.97 | 9.94 | -| hashicorp.lru | 3796.49 | 99.90 | 1457.54 | 99.90 | 696.47 | 10.00 | -| hashicorp.exp_hour | 2627.22 | 99.90 | 1343.52 | 99.90 | 591.02 | 9.97 | -| hashicorp.exp_50ms | 2616.85 | 99.90 | 1342.45 | 99.90 | 587.46 | 10.00 | -| hashicorp.arc | 3496.26 | 99.90 | 1455.67 | 99.90 | 338.93 | 9.95 | -| hashicorp.2Q | 3804.71 | 99.90 | 1476.98 | 99.90 | 357.44 | 9.97 | - -The reason that HashiCorp's implementations were used as the reference is that they are well done. For general purpose caching needs, they are hard to beat. However, the Tests A and B are a reasonable facsimilie of what we see in real life. And in that environment, it performs very well. In Test C, where the cache is undersized, LazyLRU worse than the regular LRU algorithm. The two "smart" algorithms, ARC and 2Q, shouldn't be expected to perform well in this test because of the random request pattern. diff --git a/bench/go.mod b/bench/go.mod deleted file mode 100644 index 2fd4f52..0000000 --- a/bench/go.mod +++ /dev/null @@ -1,24 +0,0 @@ -module github.com/TriggerMail/lazylru/bench - -go 1.22 - -toolchain go1.22.2 - -replace github.com/TriggerMail/lazylru => ../ - -replace github.com/TriggerMail/lazylru/generic => ../generic - -require ( - github.com/TriggerMail/lazylru v0.3.3 - github.com/hashicorp/golang-lru v0.5.4 - github.com/stretchr/testify v1.9.0 - go.uber.org/zap v1.24.0 -) - -require ( - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - go.uber.org/atomic v1.11.0 // indirect - go.uber.org/multierr v1.11.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect -) diff --git a/bench/go.sum b/bench/go.sum deleted file mode 100644 index a3b532b..0000000 --- a/bench/go.sum +++ /dev/null @@ -1,24 +0,0 @@ -github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= -github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= -github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= -go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= -go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= -go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= -go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= -go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/bench/hashicorp_wrapper.go b/bench/hashicorp_wrapper.go deleted file mode 100644 index eb8d000..0000000 --- a/bench/hashicorp_wrapper.go +++ /dev/null @@ -1,126 +0,0 @@ -package main - -import ( - "time" - - hlru "github.com/hashicorp/golang-lru" -) - -type hashicorpCache interface { - Get(key interface{}) (value interface{}, ok bool) - Add(key, value interface{}) - Purge() -} - -// ignoreEvict is a type alias to make the Add method of Cache like the others -type ignoreEvict struct { - *hlru.Cache -} - -func (c *ignoreEvict) Get(key interface{}) (value interface{}, ok bool) { - return c.Cache.Get(key) -} - -func (c *ignoreEvict) Add(key, value interface{}) { - c.Cache.Add(key, value) -} - -func (c *ignoreEvict) Purge() { - c.Cache.Purge() -} - -// HashicorpWrapper wraps hashicorp/golang-lru.Cache in an interface for testing -type HashicorpWrapper[K comparable, V any] struct { - cache hashicorpCache -} - -// Get looks an item up in the cache. The boolean indicates if the key was found -func (c *HashicorpWrapper[K, V]) Get(key K) (V, bool) { - var retval V - v, ok := c.cache.Get(key) - if ok { - retval, ok = v.(V) - } - return retval, ok -} - -// Set adds or updates a value in the cache -func (c *HashicorpWrapper[K, V]) Set(key string, value string) { - c.cache.Add(key, value) -} - -// Close removes everything from the cache -func (c *HashicorpWrapper[K, V]) Close() { - c.cache.Purge() -} - -// NewHashicorpWrapper initializes a new cache with capacity of `size` -func NewHashicorpWrapper[K comparable, V any](size int) *HashicorpWrapper[K, V] { - retval, err := hlru.New(size) - if err != nil { - panic(err) - } - return &HashicorpWrapper[K, V]{&ignoreEvict{retval}} -} - -// NewHashicorpARCWrapper initializes a new cache with capacity of `size` -func NewHashicorpARCWrapper[K comparable, V any](size int) *HashicorpWrapper[K, V] { - retval, err := hlru.NewARC(size) - if err != nil { - panic(err) - } - return &HashicorpWrapper[K, V]{retval} -} - -// NewHashicorp2QWrapper initializes a new cache with capacity of `size` -func NewHashicorp2QWrapper[K comparable, V any](size int) *HashicorpWrapper[K, V] { - retval, err := hlru.New2Q(size) - if err != nil { - panic(err) - } - return &HashicorpWrapper[K, V]{retval} -} - -// HashicorpWrapperExp is a wrapper around the hashicorp.cache that stores items -// in an envelope that enforces a time-to-live constraint -type HashicorpWrapperExp[K comparable, V any] struct { - cache *hlru.Cache - ttl time.Duration -} - -// NewHashicorpWrapperExp initializes a new cache with capacity of `size` -func NewHashicorpWrapperExp[K comparable, V any](size int, ttl time.Duration) *HashicorpWrapperExp[K, V] { - retval, err := hlru.New(size) - if err != nil { - panic(err) - } - return &HashicorpWrapperExp[K, V]{retval, ttl} -} - -// Get looks an item up in the cache. The boolean indicates if the key was found -func (c *HashicorpWrapperExp[K, V]) Get(key K) (V, bool) { - var zeroval V - ret, ok := c.cache.Get(key) - if !ok { - return zeroval, false - } - item, ok := ret.(mapCacheElement[V]) - if !ok { - return zeroval, false - } - if item.expiration.Before(time.Now()) { - return zeroval, ok - } - - return item.value, ok -} - -// Set adds or updates a value in the cache -func (c *HashicorpWrapperExp[K, V]) Set(key K, value V) { - c.cache.Add(key, mapCacheElement[V]{value: value, expiration: time.Now().Add(c.ttl)}) -} - -// Close removes everything from the cache -func (c *HashicorpWrapperExp[K, V]) Close() { - c.cache.Purge() -} diff --git a/bench/lazylru_benchmark_test.go b/bench/lazylru_benchmark_test.go deleted file mode 100644 index a9bc174..0000000 --- a/bench/lazylru_benchmark_test.go +++ /dev/null @@ -1,101 +0,0 @@ -package main_test - -import ( - "fmt" - "math/rand/v2" - "runtime" - "strconv" - "testing" - "time" - - "github.com/TriggerMail/lazylru" -) - -const keycnt = 100000 - -var keys = func() []string { - k := make([]string, keycnt) - - for i := 0; i < keycnt; i++ { - k[i] = strconv.Itoa(i) - } - return k -}() - -type benchconfig struct { - capacity int - keyCount int - readRate float64 -} - -func (bc benchconfig) Name() string { - comment := "eqcap" - if bc.capacity > bc.keyCount { - comment = "overcap" - } else if bc.capacity < bc.keyCount { - comment = "undercap" - } - return fmt.Sprintf("%dW/%dR_%s", 100-int(100*bc.readRate), int(100*bc.readRate), comment) -} - -func (bc benchconfig) Generic(b *testing.B) { - lru := lazylru.NewT[string, int](bc.capacity, time.Minute) - defer lru.Close() - for i := 0; i < bc.keyCount; i++ { - lru.Set(keys[i], i) - } - - runtime.GC() - b.ResetTimer() - for i := 0; i < b.N; i++ { - ix := rand.IntN(bc.keyCount) //nolint:gosec - if rand.Float64() < bc.readRate { //nolint:gosec - lru.Get(keys[ix]) - } else { - lru.Set(keys[ix], ix) - } - } -} - -func (bc benchconfig) GenInterface(b *testing.B) { - lru := lazylru.New(bc.capacity, time.Minute) //nolint:staticcheck - defer lru.Close() - for i := 0; i < bc.keyCount; i++ { - lru.Set(keys[i], i) - } - - runtime.GC() - b.ResetTimer() - for i := 0; i < b.N; i++ { - ix := rand.IntN(bc.keyCount) //nolint:gosec - if rand.Float64() < bc.readRate { //nolint:gosec - lru.Get(keys[ix]) - } else { - lru.Set(keys[ix], ix) - } - } -} - -func Benchmark(b *testing.B) { - for _, bc := range []benchconfig{ - {1, 1, 0.5}, // this is meant as a warm-up - {1000, 100, 0.0}, - {1000, 100, 1.0}, - {1000, 100, 0.25}, - {1000, 100, 0.75}, - {1000, 100, 0.99}, - {100, 1000, 0.0}, - {100, 1000, 1.0}, - {100, 1000, 0.25}, - {100, 1000, 0.75}, - {100, 1000, 0.99}, - {100, 100, 0.0}, - {100, 100, 1.0}, - {100, 100, 0.25}, - {100, 100, 0.75}, - {100, 100, 0.99}, - } { - b.Run(bc.Name()+"/gen[string,iface]", bc.GenInterface) - b.Run(bc.Name()+"/gen[string,int]", bc.Generic) - } -} diff --git a/bench/main.go b/bench/main.go deleted file mode 100644 index 70ab69c..0000000 --- a/bench/main.go +++ /dev/null @@ -1,252 +0,0 @@ -package main - -import ( - "fmt" - "math" - "sort" - "strings" - "sync" - "sync/atomic" - "time" - - "github.com/TriggerMail/lazylru" - "go.uber.org/zap" -) - -// SpinsPerMicro is a rough, empirical measure of the number of cycles the -// spinWait function must run per microsecond -var SpinsPerMicro = func() uint64 { - testSpins := uint64(1000000) - testIterations := 1001 - results := make([]time.Duration, testIterations) - - for i := 0; i < testIterations; i++ { - start := time.Now() - spinWait(testSpins) - results[i] = time.Since(start) - } - - sort.Slice(results, func(i, j int) bool { - return results[i] < results[j] - }) - - return uint64(float64(testSpins) / (results[testIterations/2].Seconds() * 1000000)) -}() - -func spinWait(n uint64) { - for i := uint64(0); i < n; i++ { - _ = i - } -} - -// Cache is the interface that all implementations under test must implement -type Cache interface { - Get(key string) (string, bool) - Set(key string, value string) - Close() -} - -// TestParams holds the parameters of a test -type TestParams struct { - Cache Cache - Name string - MaxCycles int - Threads int - Size int - Duration time.Duration - WorkTime time.Duration - SleepTime time.Duration - TestDataSpec TestDataSpec -} - -var logger *zap.Logger - -func main() { - logger, _ = zap.NewProduction(zap.WithCaller(false)) - - logger.Info("Begin") - logger.Info("Data loaded") - testDuration := 5 * time.Second - - caches := []struct { - factory func(int) Cache - name string - }{ - {func(size int) Cache { return NullCache }, "null"}, - {func(size int) Cache { return NewMapCache[string, string](size, time.Hour) }, "mapcache.hour"}, - {func(size int) Cache { return NewMapCache[string, string](size, time.Millisecond*50) }, "mapcache.50ms"}, - {func(size int) Cache { return lazylru.NewT[string, string](size, time.Hour) }, "lazylru.hour"}, - {func(size int) Cache { return lazylru.NewT[string, string](size, time.Millisecond*50) }, "lazylru.50ms"}, - {func(size int) Cache { return NewHashicorpWrapper[string, string](size) }, "hashicorp.lru"}, - {func(size int) Cache { return NewHashicorpWrapperExp[string, string](size, time.Hour) }, "hashicorp.exp_hour"}, - {func(size int) Cache { return NewHashicorpWrapperExp[string, string](size, time.Millisecond*50) }, "hashicorp.exp_50ms"}, - {func(size int) Cache { return NewHashicorpARCWrapper[string, string](size) }, "hashicorp.arc"}, - {func(size int) Cache { return NewHashicorp2QWrapper[string, string](size) }, "hashicorp.2Q"}, - } - - printHeaders() - - for _, tds := range []TestDataSpec{ - {1, 5000, 1000000}, - {5, 1000, 1000000}, - {5, 20000, 1000000}, - } { - testData := tds.ToRanges() - for _, testSleepTime := range []time.Duration{0, 100 * time.Microsecond, time.Millisecond} { - for _, testWorkTime := range []time.Duration{0, 1 * time.Microsecond, 10 * time.Microsecond} { - for _, testThreads := range []int{1, 8, 64, 256} { - for _, testSize := range []int{100, 10000} { - for _, cache := range caches { - testLru( - TestParams{ - Duration: testDuration, - MaxCycles: tds.Ranges * tds.CyclesPerRange, - Threads: testThreads, - Size: testSize, - Name: cache.name, - Cache: cache.factory(testSize), - WorkTime: testWorkTime, - SleepTime: testSleepTime, - TestDataSpec: tds, - }, - testData, - ) - _ = logger.Sync() - } - } - } - } - } - } -} - -func testLru(testParams TestParams, testData TestData) { - runtime := testParams.Duration - threads := testParams.Threads - cache := testParams.Cache - log := logger.With(zap.String("name", testParams.Name), zap.Int("size", testParams.Size), zap.Int("threads", threads)) - - var wg sync.WaitGroup - globalHits := int64(0) - globalCycles := int64(0) - - N := int64(testParams.MaxCycles) / int64(threads) - if N <= 0 { - N = int64(1<<63 - 1) - } - - endtimes := time.NewTimer(runtime) - go func() { - <-endtimes.C - log.Debug("Signalling the end times") - atomic.StoreInt64(&N, -1) - }() - - workCycles := uint64(testParams.WorkTime/time.Microsecond) * SpinsPerMicro - - log.Debug("Starting threads.", zap.Int("count", threads)) - start := time.Now() - for i := 0; i < threads; i++ { - wg.Add(1) - go func(i int) { - defer wg.Done() - log.Debug("Starting thread", zap.Int("thread", i)) - hits := int64(0) - cycles := int64(0) - for ; cycles < atomic.LoadInt64(&N); cycles++ { - if cycles%10000000 == 0 { - log.Debug( - "Progress", - zap.Int("thread", i), - zap.Int64("count", cycles), - zap.Int64("N", atomic.LoadInt64(&N)), - ) - } - key, value := testData.RandomKV() - if _, ok := cache.Get(key); ok { - hits++ - } else { - cache.Set(key, value) - } - if workCycles > 0 { - spinWait(workCycles) - } - time.Sleep(testParams.SleepTime) - } - atomic.AddInt64(&globalHits, hits) - atomic.AddInt64(&globalCycles, cycles) - log.Debug("Stopping thread", zap.Int("thread", i)) - }(i) - } - log.Debug("Waiting for threads to finish") - wg.Wait() - duration := time.Since(start) - log.Debug("All threads finished. Closing lru") - cache.Close() - - // disabled since it only works on lazylru and pollutes the output - // stats := lru.Stats() - // log.Info( - // "stats", - // zap.Uint32("keys_written", stats.KeysWritten), - // zap.Uint32("read_ok", stats.KeysReadOK), - // zap.Uint32("read_not_found", stats.KeysReadNotFound), - // zap.Uint32("read_expired", stats.KeysReadExpired), - // zap.Uint32("shuffles", stats.Shuffles), - // zap.Uint32("evictions", stats.Evictions), - // zap.Uint32("reaped", stats.KeysReaped), - // zap.Uint32("reaper_cycles", stats.ReaperCycles), - // ) - printResult(globalCycles, globalHits, duration, testParams) -} - -func printHeaders() { - fmt.Println(strings.Join([]string{ - "algorithm", - "ranges", - "keys/range", - "cycles/range", - "threads", - "size", - "work_time_µs", - "sleep_time_µs", - "cycles", - "duration_ms", - "rate_kHz", - "hit_rate_%", - }, "\t")) -} - -func printResult(cycles int64, hits int64, duration time.Duration, testParams TestParams) { - fmt.Print(testParams.Name) - fmt.Print("\t") - fmt.Print(testParams.TestDataSpec.Ranges) - fmt.Print("\t") - fmt.Print(testParams.TestDataSpec.KeysPerRange) - fmt.Print("\t") - fmt.Print(testParams.TestDataSpec.CyclesPerRange) - fmt.Print("\t") - fmt.Print(testParams.Threads) - fmt.Print("\t") - fmt.Print(testParams.Size) - fmt.Print("\t") - fmt.Print(testParams.WorkTime / time.Microsecond) - fmt.Print("\t") - fmt.Print(testParams.SleepTime / time.Microsecond) - fmt.Print("\t") - fmt.Print(cycles) - fmt.Print("\t") - fmt.Print(int(math.Round(duration.Seconds() * 1000))) - fmt.Print("\t") - fmt.Print(RoundDigits(float64(cycles)/(duration.Seconds()*1000), 2)) - fmt.Print("\t") - fmt.Print(RoundDigits(float64(hits)*100.0/float64(cycles), 2)) - - fmt.Println() -} - -// RoundDigits rounds a floating point value to a given number of digits -func RoundDigits(val float64, digits int) float64 { - scale := math.Pow10(digits) - return math.Round(val*scale) / scale -} diff --git a/bench/main_test.go b/bench/main_test.go deleted file mode 100644 index c080181..0000000 --- a/bench/main_test.go +++ /dev/null @@ -1,83 +0,0 @@ -package main_test - -import ( - "fmt" - "testing" - "time" - - "github.com/TriggerMail/lazylru" - bench "github.com/TriggerMail/lazylru/bench" - "github.com/stretchr/testify/require" -) - -func TestWrapperFunctions(t *testing.T) { - for _, test := range []struct { - cache bench.Cache - name string - }{ - {bench.NewMapCache[string, string](10, time.Hour), "mapcache"}, - {lazylru.NewT[string, string](10, time.Hour), "lazylru"}, - {bench.NewHashicorpWrapper[string, string](10), "hashicorp.lru"}, - {bench.NewHashicorpWrapperExp[string, string](10, time.Hour), "hashicorp.exp"}, - {bench.NewHashicorpARCWrapper[string, string](10), "hashicorp.arc"}, - {bench.NewHashicorp2QWrapper[string, string](10), "hashicorp.2Q"}, - } { - t.Run(test.name, func(t *testing.T) { - _, ok := test.cache.Get("medeco") - require.False(t, ok) - test.cache.Set("medeco", "abloy") - v, ok := test.cache.Get("medeco") - require.True(t, ok) - require.Equal(t, "abloy", v) - _, ok = test.cache.Get("schlage") - require.False(t, ok) - test.cache.Close() - }) - } - // the NullCache doesn't actually hold anything, so we need a different test - t.Run("NullCache", func(t *testing.T) { - cache := bench.NullCache - _, ok := cache.Get("medeco") - require.False(t, ok) - cache.Set("medeco", "abloy") - _, ok = cache.Get("medeco") - require.False(t, ok) - _, ok = cache.Get("schlage") - require.False(t, ok) - cache.Close() - }) -} - -func TestRoundDigits(t *testing.T) { - for _, test := range []struct { - in float64 - prec int - exp float64 - }{ - {1, 0, 1}, - {1, 1, 1}, - {1, 10, 1}, - {1.4, 0, 1.0}, - {1.6, 0, 2.0}, - {1.449, 1, 1.4}, - {1.44449, 3, 1.444}, - {1.45, 1, 1.5}, - } { - t.Run(fmt.Sprintf("%f@%d", test.in, test.prec), func(t *testing.T) { - act := bench.RoundDigits(test.in, test.prec) - require.Equal(t, test.exp, act) - }) - } -} - -func TestSpinsPerMicro(t *testing.T) { - require.Less(t, uint64(10), bench.SpinsPerMicro) -} - -func TestSourceData(t *testing.T) { - r := bench.NewTestData(1000) - require.Equal(t, 1000, len(r)) - for i := 1; i < len(r); i++ { - require.NotEqual(t, r[i-1], r[i]) - } -} diff --git a/bench/map_cache.go b/bench/map_cache.go deleted file mode 100644 index c02ec2e..0000000 --- a/bench/map_cache.go +++ /dev/null @@ -1,73 +0,0 @@ -package main - -import ( - "sync" - "time" -) - -type mapCacheElement[V any] struct { - expiration time.Time - value V -} - -// MapCache is a simple, map-based implementation of a cache. The replacement -// "strategy" is random. All operations take an exclusive lock. It is meant to -// be the control in our experiment. -type MapCache[K comparable, V any] struct { - data map[K]mapCacheElement[V] - ttl time.Duration - mut sync.Mutex - maxSize int -} - -// NewMapCache creates a new cache with a given size and item time-to-live -func NewMapCache[K comparable, V any](maxSize int, ttl time.Duration) *MapCache[K, V] { - return &MapCache[K, V]{ - data: map[K]mapCacheElement[V]{}, - ttl: ttl, - maxSize: maxSize, - } -} - -// Get retrieves an item from the cache -func (mc *MapCache[K, V]) Get(key K) (V, bool) { - mc.mut.Lock() - ret, ok := mc.data[key] - if ok && ret.expiration.Before(time.Now()) { - delete(mc.data, key) - ok = false - } - mc.mut.Unlock() - if ok { - return ret.value, ok - } - var zeroval V - return zeroval, false -} - -// Set adds an item to the cache -func (mc *MapCache[K, V]) Set(key K, value V) { - mc.mut.Lock() - // are we about to run out of space? - if len(mc.data) == mc.maxSize { - _, ok := mc.data[key] - // if we are not replacing an existing element, delete an element arbitrarily - if !ok { - // is it worth the trouble to try to find expired elements? - // probably not - var k K - for k = range mc.data { - break - } - delete(mc.data, k) - } - } - mc.data[key] = mapCacheElement[V]{ - value: value, - expiration: time.Now().Add(mc.ttl), - } - mc.mut.Unlock() -} - -// Close doesn't do anything in this implementation -func (mc *MapCache[K, V]) Close() {} diff --git a/bench/null_cache.go b/bench/null_cache.go deleted file mode 100644 index 9a6b288..0000000 --- a/bench/null_cache.go +++ /dev/null @@ -1,17 +0,0 @@ -package main - -type nullCache struct{} - -// NullCache is a cache that holds nothing and returns nothing -var NullCache = nullCache{} - -// Get always returns `nil, false“ -func (nc nullCache) Get(key string) (string, bool) { - return "", false -} - -// Set does nothing -func (nc nullCache) Set(key string, value string) {} - -// Close does nothing -func (nc nullCache) Close() {} diff --git a/bench/results.csv.gz b/bench/results.csv.gz deleted file mode 100644 index 6b62a870067a8d67be58f33f66fd282a2f699d59..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 33911 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

l21i?BI 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 boMMO3%!^C|`p{inkoW%sqJ>-e`r!fqnKAaA diff --git a/bench/revive.toml b/bench/revive.toml deleted file mode 100644 index 2d4274f..0000000 --- a/bench/revive.toml +++ /dev/null @@ -1,27 +0,0 @@ -# this configuration is the same as the default, but warnings and errors return -# a non-zero value -ignoreGeneratedHeader = false -severity = "warning" -confidence = 0.8 -warningCode = 1 -errorCode = 1 - -[rule.blank-imports] -[rule.context-as-argument] -[rule.context-keys-type] -[rule.dot-imports] -[rule.error-return] -[rule.error-strings] -[rule.error-naming] -[rule.exported] -[rule.if-return] -[rule.increment-decrement] -[rule.var-naming] -[rule.var-declaration] -[rule.package-comments] -[rule.range] -[rule.receiver-naming] -[rule.time-naming] -[rule.unexported-return] -[rule.indent-error-flow] -[rule.errorf] \ No newline at end of file diff --git a/bench/test_data.go b/bench/test_data.go deleted file mode 100644 index 7aa6dea..0000000 --- a/bench/test_data.go +++ /dev/null @@ -1,83 +0,0 @@ -package main - -import ( - "math/rand/v2" -) - -const validChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" - -func randomString(size int) string { - retval := make([]byte, size) - for i := 0; i < len(retval); i++ { - retval[i] = validChars[rand.IntN(len(validChars))] //nolint:gosec - } - return string(retval) -} - -// TestData represents a source of keys and values -type TestData interface { - RandomKV() (string, string) -} - -// KV is a key/value pair for use in the test -type KV struct { - Key string - Value string -} - -// TestDataSimple is a slice of KV -type TestDataSimple []KV - -// NewTestData creates `count` KV instances -func NewTestData(count int) TestDataSimple { - retval := make(TestDataSimple, count) - - for i := 0; i < count; i++ { - retval[i] = KV{randomString(12), randomString(24)} - } - return retval -} - -// RandomKV retrieves a random key/value pair -func (td TestDataSimple) RandomKV() (string, string) { - kv := td[rand.IntN(len(td))] //nolint:gosec - return kv.Key, kv.Value -} - -// TestDataRanges is a series of test data sets that will be cycled through -// during a benchmark run -type TestDataRanges struct { - ranges []TestDataSimple - cyclesPerRange int - currentCycle int -} - -// TestDataSpec defined the shape of a benchmark run -type TestDataSpec struct { - Ranges int - KeysPerRange int - CyclesPerRange int -} - -// ToRanges creates TestDataRanges from a spec -func (tds TestDataSpec) ToRanges() *TestDataRanges { - return NewTestDataRanges(tds.Ranges, tds.KeysPerRange, tds.CyclesPerRange) -} - -// NewTestDataRanges creates test data ranges -func NewTestDataRanges(ranges int, keysPerRange int, cyclesPerRange int) *TestDataRanges { - td := make([]TestDataSimple, ranges) - for i := 0; i < ranges; i++ { - td[i] = NewTestData(keysPerRange) - } - return &TestDataRanges{ - cyclesPerRange: cyclesPerRange, - ranges: td, - } -} - -// RandomKV pulls a random item from the current range in a benchmark run -func (tdr *TestDataRanges) RandomKV() (string, string) { - tdr.currentCycle++ - return tdr.ranges[tdr.currentCycle%len(tdr.ranges)].RandomKV() -} diff --git a/generic/.golangci.yml b/generic/.golangci.yml deleted file mode 100644 index a75fc05..0000000 --- a/generic/.golangci.yml +++ /dev/null @@ -1,37 +0,0 @@ -run: - go: "1.20" - timeout: 5m - issues-exit-code: 1 - tests: true - skip-dirs-use-default: true - modules-download-mode: vendor - -linters: - disable: - - structcheck # this is disabled because v1.45 is showing a lot of false positives - enable: - - revive - - gofmt - - govet - - gosec - - unconvert - - goconst - - gocyclo - - goimports - -linters-settings: - govet: - enable: - - fieldalignment - fieldalignment: - fix: true - revive: - rules: - - name: unused-parameter - disabled: true - -issues: - exclude: - - EXC0002 # Annoying issue about not having a comment - - EXC0012 # func should have comment or be unexported - - SA1019 # deprecation (this whole package is deprecated) diff --git a/generic/Earthfile b/generic/Earthfile deleted file mode 100644 index 836f0ad..0000000 --- a/generic/Earthfile +++ /dev/null @@ -1,64 +0,0 @@ -VERSION 0.6 -FROM golang:rc-buster - -WORKDIR /app - -all: - BUILD +fmt - BUILD +lint - BUILD +vet - BUILD +test - -ci: - BUILD +fmt - BUILD +lint - BUILD +vet - COPY --dir +test/files ./test-results - SAVE ARTIFACT ./test-results AS LOCAL test-results - -go-mod: - RUN git config --global url."git@github.com:".insteadOf "https://github.com/" - RUN mkdir -p -m 0600 ~/.ssh && ssh-keyscan github.com >> ~/.ssh/known_hosts - COPY go.mod go.sum . - RUN --ssh go mod download - -fmt: - COPY . . - RUN find . -type d -path "./vendor" -prune -o -name "*.go" -exec gofmt -d -e {} \; | tee /tmp/gofmt.out - RUN bash -c 'if [[ -s /tmp/gofmt.out ]]; then exit 1; fi' - -vendor: - FROM +go-mod - COPY . . - RUN --ssh go mod vendor - SAVE ARTIFACT . files - -lint: - FROM +vendor - COPY +golangci-lint/go/bin/golangci-lint /go/bin/golangci-lint - RUN golangci-lint run - -vet: - FROM +vendor - RUN go vet ./... - -test: - FROM +vendor - COPY +junit-report/go/bin/go-junit-report /go/bin/go-junit-report - RUN mkdir -p test-results - # To both see the output in the console AND convert into junit-style results - # to send to the plug-in, we need to run the tests, writing to a file, then - # send that file to go-junit-report - RUN 2>&1 go test -v ./... -cover | tee test-results/go-test-generic.out - RUN cat test-results/go-test-generic.out | $GOPATH/bin/go-junit-report > test-results/go-test-generic-report.xml - SAVE ARTIFACT test-results files - -golangci-lint: - RUN echo Installing golangci-lint... - # see https://golangci-lint.run/usage/install/#other-ci - RUN curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b /go/bin v1.51.2 - SAVE ARTIFACT /go/bin/golangci-lint /go/bin/golangci-lint - -junit-report: - RUN go install github.com/jstemmer/go-junit-report@latest - SAVE ARTIFACT /go/bin/go-junit-report /go/bin/go-junit-report diff --git a/generic/benchmark_results.txt b/generic/benchmark_results.txt deleted file mode 100644 index 3725137..0000000 --- a/generic/benchmark_results.txt +++ /dev/null @@ -1,95 +0,0 @@ -goos: darwin -goarch: arm64 -pkg: github.com/TriggerMail/lazylru/generic -Benchmark/100W/0R_overcap/interface/array-8 6574646 178.1 ns/op 32 B/op 2 allocs/op -Benchmark/100W/0R_overcap/generic/array-8 8215860 147.0 ns/op 8 B/op 1 allocs/op -Benchmark/100W/0R_overcap/interface/struct-8 7208605 163.1 ns/op 48 B/op 1 allocs/op -Benchmark/100W/0R_overcap/generic/struct-8 7125775 169.1 ns/op 48 B/op 1 allocs/op -Benchmark/100W/0R_overcap/interface/value-8 8620632 136.1 ns/op 0 B/op 0 allocs/op -Benchmark/100W/0R_overcap/generic/value-8 8263629 144.8 ns/op 0 B/op 0 allocs/op -Benchmark/0W/100R_overcap/interface/array-8 16991059 71.24 ns/op 0 B/op 0 allocs/op -Benchmark/0W/100R_overcap/generic/array-8 16842764 71.27 ns/op 0 B/op 0 allocs/op -Benchmark/0W/100R_overcap/interface/struct-8 16439744 71.01 ns/op 0 B/op 0 allocs/op -Benchmark/0W/100R_overcap/generic/struct-8 16181029 74.18 ns/op 0 B/op 0 allocs/op -Benchmark/0W/100R_overcap/interface/value-8 16014564 72.92 ns/op 0 B/op 0 allocs/op -Benchmark/0W/100R_overcap/generic/value-8 16612014 70.63 ns/op 0 B/op 0 allocs/op -Benchmark/75W/25R_overcap/interface/array-8 9313356 125.8 ns/op 23 B/op 1 allocs/op -Benchmark/75W/25R_overcap/generic/array-8 11290264 107.8 ns/op 5 B/op 0 allocs/op -Benchmark/75W/25R_overcap/interface/struct-8 10212685 118.8 ns/op 35 B/op 0 allocs/op -Benchmark/75W/25R_overcap/generic/struct-8 8987116 121.6 ns/op 35 B/op 0 allocs/op -Benchmark/75W/25R_overcap/interface/value-8 11766843 101.7 ns/op 0 B/op 0 allocs/op -Benchmark/75W/25R_overcap/generic/value-8 11219032 108.0 ns/op 0 B/op 0 allocs/op -Benchmark/25W/75R_overcap/interface/array-8 13314169 88.90 ns/op 7 B/op 0 allocs/op -Benchmark/25W/75R_overcap/generic/array-8 14428806 81.52 ns/op 2 B/op 0 allocs/op -Benchmark/25W/75R_overcap/interface/struct-8 13784242 86.32 ns/op 11 B/op 0 allocs/op -Benchmark/25W/75R_overcap/generic/struct-8 13285591 90.09 ns/op 11 B/op 0 allocs/op -Benchmark/25W/75R_overcap/interface/value-8 15024955 80.18 ns/op 0 B/op 0 allocs/op -Benchmark/25W/75R_overcap/generic/value-8 14286096 83.58 ns/op 0 B/op 0 allocs/op -Benchmark/1W/99R_overcap/interface/array-8 16585476 72.41 ns/op 0 B/op 0 allocs/op -Benchmark/1W/99R_overcap/generic/array-8 17094625 74.04 ns/op 0 B/op 0 allocs/op -Benchmark/1W/99R_overcap/interface/struct-8 16787716 72.22 ns/op 0 B/op 0 allocs/op -Benchmark/1W/99R_overcap/generic/struct-8 16363254 72.59 ns/op 0 B/op 0 allocs/op -Benchmark/1W/99R_overcap/interface/value-8 16585618 70.85 ns/op 0 B/op 0 allocs/op -Benchmark/1W/99R_overcap/generic/value-8 16645830 71.77 ns/op 0 B/op 0 allocs/op -Benchmark/100W/0R_undercap/interface/array-8 3998246 293.5 ns/op 112 B/op 3 allocs/op -Benchmark/100W/0R_undercap/generic/array-8 4382415 275.0 ns/op 101 B/op 2 allocs/op -Benchmark/100W/0R_undercap/interface/struct-8 4136809 289.6 ns/op 138 B/op 2 allocs/op -Benchmark/100W/0R_undercap/generic/struct-8 4273404 280.9 ns/op 125 B/op 2 allocs/op -Benchmark/100W/0R_undercap/interface/value-8 4440426 270.9 ns/op 99 B/op 1 allocs/op -Benchmark/100W/0R_undercap/generic/value-8 4634301 253.3 ns/op 64 B/op 1 allocs/op -Benchmark/0W/100R_undercap/interface/array-8 26218166 44.54 ns/op 0 B/op 0 allocs/op -Benchmark/0W/100R_undercap/generic/array-8 25893668 46.03 ns/op 0 B/op 0 allocs/op -Benchmark/0W/100R_undercap/interface/struct-8 27219987 46.45 ns/op 0 B/op 0 allocs/op -Benchmark/0W/100R_undercap/generic/struct-8 27875130 46.76 ns/op 0 B/op 0 allocs/op -Benchmark/0W/100R_undercap/interface/value-8 25505030 45.39 ns/op 0 B/op 0 allocs/op -Benchmark/0W/100R_undercap/generic/value-8 25884103 48.57 ns/op 0 B/op 0 allocs/op -Benchmark/75W/25R_undercap/interface/array-8 4903078 245.6 ns/op 91 B/op 2 allocs/op -Benchmark/75W/25R_undercap/generic/array-8 5373604 224.2 ns/op 73 B/op 1 allocs/op -Benchmark/75W/25R_undercap/interface/struct-8 5045581 236.8 ns/op 101 B/op 1 allocs/op -Benchmark/75W/25R_undercap/generic/struct-8 5258272 228.7 ns/op 91 B/op 1 allocs/op -Benchmark/75W/25R_undercap/interface/value-8 5151207 225.4 ns/op 71 B/op 1 allocs/op -Benchmark/75W/25R_undercap/generic/value-8 5667578 212.1 ns/op 55 B/op 0 allocs/op -Benchmark/25W/75R_undercap/interface/array-8 10693899 112.6 ns/op 30 B/op 0 allocs/op -Benchmark/25W/75R_undercap/generic/array-8 11583872 103.9 ns/op 24 B/op 0 allocs/op -Benchmark/25W/75R_undercap/interface/struct-8 10949071 110.0 ns/op 33 B/op 0 allocs/op -Benchmark/25W/75R_undercap/generic/struct-8 11261811 106.8 ns/op 30 B/op 0 allocs/op -Benchmark/25W/75R_undercap/interface/value-8 11426806 104.9 ns/op 23 B/op 0 allocs/op -Benchmark/25W/75R_undercap/generic/value-8 12066440 98.86 ns/op 17 B/op 0 allocs/op -Benchmark/1W/99R_undercap/interface/array-8 22972735 52.10 ns/op 1 B/op 0 allocs/op -Benchmark/1W/99R_undercap/generic/array-8 22962386 51.95 ns/op 0 B/op 0 allocs/op -Benchmark/1W/99R_undercap/interface/struct-8 22903312 52.09 ns/op 1 B/op 0 allocs/op -Benchmark/1W/99R_undercap/generic/struct-8 22371103 52.93 ns/op 1 B/op 0 allocs/op -Benchmark/1W/99R_undercap/interface/value-8 22640299 52.29 ns/op 0 B/op 0 allocs/op -Benchmark/1W/99R_undercap/generic/value-8 22364502 52.87 ns/op 0 B/op 0 allocs/op -Benchmark/100W/0R_eqcap/interface/array-8 6762968 176.9 ns/op 32 B/op 2 allocs/op -Benchmark/100W/0R_eqcap/generic/array-8 8356812 146.3 ns/op 8 B/op 1 allocs/op -Benchmark/100W/0R_eqcap/interface/struct-8 7349427 161.8 ns/op 48 B/op 1 allocs/op -Benchmark/100W/0R_eqcap/generic/struct-8 7245291 165.2 ns/op 48 B/op 1 allocs/op -Benchmark/100W/0R_eqcap/interface/value-8 8755994 135.7 ns/op 0 B/op 0 allocs/op -Benchmark/100W/0R_eqcap/generic/value-8 8278689 144.0 ns/op 0 B/op 0 allocs/op -Benchmark/0W/100R_eqcap/interface/array-8 8285852 141.0 ns/op 0 B/op 0 allocs/op -Benchmark/0W/100R_eqcap/generic/array-8 8363359 140.4 ns/op 0 B/op 0 allocs/op -Benchmark/0W/100R_eqcap/interface/struct-8 8519584 140.2 ns/op 0 B/op 0 allocs/op -Benchmark/0W/100R_eqcap/generic/struct-8 8460104 141.3 ns/op 0 B/op 0 allocs/op -Benchmark/0W/100R_eqcap/interface/value-8 8539122 144.6 ns/op 0 B/op 0 allocs/op -Benchmark/0W/100R_eqcap/generic/value-8 8443440 142.6 ns/op 0 B/op 0 allocs/op -Benchmark/75W/25R_eqcap/interface/array-8 7173831 165.3 ns/op 24 B/op 1 allocs/op -Benchmark/75W/25R_eqcap/generic/array-8 8038418 147.2 ns/op 6 B/op 0 allocs/op -Benchmark/75W/25R_eqcap/interface/struct-8 7464213 158.7 ns/op 36 B/op 0 allocs/op -Benchmark/75W/25R_eqcap/generic/struct-8 7510382 161.8 ns/op 36 B/op 0 allocs/op -Benchmark/75W/25R_eqcap/interface/value-8 8576936 140.5 ns/op 0 B/op 0 allocs/op -Benchmark/75W/25R_eqcap/generic/value-8 8062348 148.4 ns/op 0 B/op 0 allocs/op -Benchmark/25W/75R_eqcap/interface/array-8 7919556 149.8 ns/op 8 B/op 0 allocs/op -Benchmark/25W/75R_eqcap/generic/array-8 8340012 144.9 ns/op 1 B/op 0 allocs/op -Benchmark/25W/75R_eqcap/interface/struct-8 7983458 148.2 ns/op 11 B/op 0 allocs/op -Benchmark/25W/75R_eqcap/generic/struct-8 7907188 150.9 ns/op 12 B/op 0 allocs/op -Benchmark/25W/75R_eqcap/interface/value-8 8448810 141.7 ns/op 0 B/op 0 allocs/op -Benchmark/25W/75R_eqcap/generic/value-8 8175607 142.9 ns/op 0 B/op 0 allocs/op -Benchmark/1W/99R_eqcap/interface/array-8 8496406 141.1 ns/op 0 B/op 0 allocs/op -Benchmark/1W/99R_eqcap/generic/array-8 8665159 140.6 ns/op 0 B/op 0 allocs/op -Benchmark/1W/99R_eqcap/interface/struct-8 8554677 141.1 ns/op 0 B/op 0 allocs/op -Benchmark/1W/99R_eqcap/generic/struct-8 8468197 141.9 ns/op 0 B/op 0 allocs/op -Benchmark/1W/99R_eqcap/interface/value-8 8484927 141.2 ns/op 0 B/op 0 allocs/op -Benchmark/1W/99R_eqcap/generic/value-8 8438985 141.7 ns/op 0 B/op 0 allocs/op -PASS -ok github.com/TriggerMail/lazylru/generic 120.482s diff --git a/generic/containers/heap/README.md b/generic/containers/heap/README.md deleted file mode 100644 index 35dd611..0000000 --- a/generic/containers/heap/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Generic Heap - -This is a LITERAL COPY from the Go standard library because there is not yet a genric version of the `containers/heap` module in the Go 1.18 beta 1 release. I am sure this will come out in Go 1.18 or in a subsequent release shortly thereafter. This library is meant to be a bridge until that is done and nothing more. - -Please do not use this for anything outside the lazylru module because it _will_ be deleted as soon as a generic heap is available in the stardard library. diff --git a/generic/containers/heap/heap.go b/generic/containers/heap/heap.go deleted file mode 100644 index 2c69ac9..0000000 --- a/generic/containers/heap/heap.go +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright 2009 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. - -// Package heap provides heap operations for any type that implements -// heap.Interface. A heap is a tree with the property that each node is the -// minimum-valued node in its subtree. -// -// The minimum element in the tree is the root, at index 0. -// -// A heap is a common way to implement a priority queue. To build a priority -// queue, implement the Heap interface with the (negative) priority as the -// ordering for the Less method, so Push adds items while Pop removes the -// highest-priority item from the queue. The Examples include such an -// implementation; the file example_pq_test.go has the complete source. -package heap - -import "sort" - -// The Interface type describes the requirements -// for a type using the routines in this package. -// Any type that implements it may be used as a -// min-heap with the following invariants (established after -// Init has been called or if the data is empty or sorted): -// -// !h.Less(j, i) for 0 <= i < h.Len() and 2*i+1 <= j <= 2*i+2 and j < h.Len() -// -// Note that Push and Pop in this interface are for package heap's -// implementation to call. To add and remove things from the heap, -// use heap.Push and heap.Pop. -type Interface[T any] interface { - sort.Interface - Push(x T) // add x as element Len() - Pop() T // remove and return element Len() - 1. -} - -// Init establishes the heap invariants required by the other routines in this package. -// Init is idempotent with respect to the heap invariants -// and may be called whenever the heap invariants may have been invalidated. -// The complexity is O(n) where n = h.Len(). -func Init[T any](h Interface[T]) { - // heapify - n := h.Len() - for i := n/2 - 1; i >= 0; i-- { - down(h, i, n) - } -} - -// Push pushes the element x onto the heap. -// The complexity is O(log n) where n = h.Len(). -func Push[T any](h Interface[T], x T) { - h.Push(x) - up(h, h.Len()-1) -} - -// Pop removes and returns the minimum element (according to Less) from the heap. -// The complexity is O(log n) where n = h.Len(). -// Pop is equivalent to Remove(h, 0). -func Pop[T any](h Interface[T]) T { - n := h.Len() - 1 - h.Swap(0, n) - down(h, 0, n) - return h.Pop() -} - -// Remove removes and returns the element at index i from the heap. -// The complexity is O(log n) where n = h.Len(). -func Remove[T any](h Interface[T], i int) T { - n := h.Len() - 1 - if n != i { - h.Swap(i, n) - if !down(h, i, n) { - up(h, i) - } - } - return h.Pop() -} - -// Fix re-establishes the heap ordering after the element at index i has changed its value. -// Changing the value of the element at index i and then calling Fix is equivalent to, -// but less expensive than, calling Remove(h, i) followed by a Push of the new value. -// The complexity is O(log n) where n = h.Len(). -func Fix[T any](h Interface[T], i int) { - if !down(h, i, h.Len()) { - up(h, i) - } -} - -func up[T any](h Interface[T], j int) { - for { - i := (j - 1) / 2 // parent - if i == j || !h.Less(j, i) { - break - } - h.Swap(i, j) - j = i - } -} - -func down[T any](h Interface[T], i0, n int) bool { - i := i0 - for { - j1 := 2*i + 1 - if j1 >= n || j1 < 0 { // j1 < 0 after int overflow - break - } - j := j1 // left child - if j2 := j1 + 1; j2 < n && h.Less(j2, j1) { - j = j2 // = 2*i + 2 // right child - } - if !h.Less(j, i) { - break - } - h.Swap(i, j) - i = j - } - return i > i0 -} diff --git a/generic/containers/heap/heap_test.go b/generic/containers/heap/heap_test.go deleted file mode 100644 index 668db7e..0000000 --- a/generic/containers/heap/heap_test.go +++ /dev/null @@ -1,214 +0,0 @@ -// Copyright 2009 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. - -package heap - -import ( - "math/rand/v2" - "testing" -) - -type myHeap []int - -func (h *myHeap) Less(i, j int) bool { - return (*h)[i] < (*h)[j] -} - -func (h *myHeap) Swap(i, j int) { - (*h)[i], (*h)[j] = (*h)[j], (*h)[i] -} - -func (h *myHeap) Len() int { - return len(*h) -} - -func (h *myHeap) Pop() (v int) { - *h, v = (*h)[:h.Len()-1], (*h)[h.Len()-1] - return -} - -func (h *myHeap) Push(v int) { - *h = append(*h, v) -} - -func (h myHeap) verify(t *testing.T, i int) { - t.Helper() - n := h.Len() - j1 := 2*i + 1 - j2 := 2*i + 2 - if j1 < n { - if h.Less(j1, i) { - t.Errorf("heap invariant invalidated [%d] = %d > [%d] = %d", i, h[i], j1, h[j1]) - return - } - h.verify(t, j1) - } - if j2 < n { - if h.Less(j2, i) { - t.Errorf("heap invariant invalidated [%d] = %d > [%d] = %d", i, h[i], j1, h[j2]) - return - } - h.verify(t, j2) - } -} - -func TestInit0(t *testing.T) { - h := new(myHeap) - for i := 20; i > 0; i-- { - h.Push(0) // all elements are the same - } - Init[int](h) - h.verify(t, 0) - - for i := 1; h.Len() > 0; i++ { - x := Pop[int](h) - h.verify(t, 0) - if x != 0 { - t.Errorf("%d.th pop got %d; want %d", i, x, 0) - } - } -} - -func TestInit1(t *testing.T) { - h := new(myHeap) - for i := 20; i > 0; i-- { - h.Push(i) // all elements are different - } - Init[int](h) - h.verify(t, 0) - - for i := 1; h.Len() > 0; i++ { - x := Pop[int](h) - h.verify(t, 0) - if x != i { - t.Errorf("%d.th pop got %d; want %d", i, x, i) - } - } -} - -func Test(t *testing.T) { - h := new(myHeap) - h.verify(t, 0) - - for i := 20; i > 10; i-- { - h.Push(i) - } - Init[int](h) - h.verify(t, 0) - - for i := 10; i > 0; i-- { - Push[int](h, i) - h.verify(t, 0) - } - - for i := 1; h.Len() > 0; i++ { - x := Pop[int](h) - if i < 20 { - Push[int](h, 20+i) - } - h.verify(t, 0) - if x != i { - t.Errorf("%d.th pop got %d; want %d", i, x, i) - } - } -} - -func TestRemove0(t *testing.T) { - h := new(myHeap) - for i := 0; i < 10; i++ { - h.Push(i) - } - h.verify(t, 0) - - for h.Len() > 0 { - i := h.Len() - 1 - x := Remove[int](h, i) - if x != i { - t.Errorf("Remove(%d) got %d; want %d", i, x, i) - } - h.verify(t, 0) - } -} - -func TestRemove1(t *testing.T) { - h := new(myHeap) - for i := 0; i < 10; i++ { - h.Push(i) - } - h.verify(t, 0) - - for i := 0; h.Len() > 0; i++ { - x := Remove[int](h, 0) - if x != i { - t.Errorf("Remove(0) got %d; want %d", x, i) - } - h.verify(t, 0) - } -} - -func TestRemove2(t *testing.T) { - N := 10 - - h := new(myHeap) - for i := 0; i < N; i++ { - h.Push(i) - } - h.verify(t, 0) - - m := make(map[int]bool) - for h.Len() > 0 { - m[Remove[int](h, (h.Len()-1)/2)] = true - h.verify(t, 0) - } - - if len(m) != N { - t.Errorf("len(m) = %d; want %d", len(m), N) - } - for i := 0; i < len(m); i++ { - if !m[i] { - t.Errorf("m[%d] doesn't exist", i) - } - } -} - -func BenchmarkDup(b *testing.B) { - const n = 10000 - h := make(myHeap, 0, n) - for i := 0; i < b.N; i++ { - for j := 0; j < n; j++ { - Push[int](&h, 0) // all elements are the same - } - for h.Len() > 0 { - Pop[int](&h) - } - } -} - -func TestFix(t *testing.T) { - h := new(myHeap) - h.verify(t, 0) - - for i := 200; i > 0; i -= 10 { - Push[int](h, i) - } - h.verify(t, 0) - - if (*h)[0] != 10 { - t.Fatalf("Expected head to be 10, was %d", (*h)[0]) - } - (*h)[0] = 210 - Fix[int](h, 0) - h.verify(t, 0) - - for i := 100; i > 0; i-- { - elem := rand.IntN(h.Len()) //nolint:gosec - if i&1 == 0 { - (*h)[elem] *= 2 - } else { - (*h)[elem] /= 2 - } - Fix[int](h, elem) - h.verify(t, 0) - } -} diff --git a/generic/expected_stats_test.go b/generic/expected_stats_test.go deleted file mode 100644 index a5948fa..0000000 --- a/generic/expected_stats_test.go +++ /dev/null @@ -1,93 +0,0 @@ -package lazylru_test - -import ( - "testing" - - lazylru "github.com/TriggerMail/lazylru/generic" - "github.com/stretchr/testify/require" -) - -type ExpectedStats struct { - KeysWritten *uint32 - KeysReadOK *uint32 - KeysReadNotFound *uint32 - KeysReadExpired *uint32 - Shuffles *uint32 - Evictions *uint32 - KeysReaped *uint32 - ReaperCycles *uint32 -} - -func (es ExpectedStats) WithKeysWritten(v uint32) ExpectedStats { - es.KeysWritten = &v - return es -} - -func (es ExpectedStats) WithKeysReadOK(v uint32) ExpectedStats { - es.KeysReadOK = &v - return es -} - -func (es ExpectedStats) WithKeysReadNotFound(v uint32) ExpectedStats { - es.KeysReadNotFound = &v - return es -} - -func (es ExpectedStats) WithKeysReadExpired(v uint32) ExpectedStats { - es.KeysReadExpired = &v - return es -} - -func (es ExpectedStats) WithShuffles(v uint32) ExpectedStats { - es.Shuffles = &v - return es -} - -func (es ExpectedStats) WithEvictions(v uint32) ExpectedStats { - es.Evictions = &v - return es -} - -func (es ExpectedStats) WithKeysReaped(v uint32) ExpectedStats { - es.KeysReaped = &v - return es -} - -func (es ExpectedStats) WithReaperCycles(v uint32) ExpectedStats { - es.ReaperCycles = &v - return es -} - -func (es ExpectedStats) Test(t *testing.T, stats lazylru.Stats) { - if es.KeysWritten != nil { - require.Equal(t, int(*es.KeysWritten), int(stats.KeysWritten), "keys written") - } - - if es.KeysReadOK != nil { - require.Equal(t, int(*es.KeysReadOK), int(stats.KeysReadOK), "keys read ok") - } - - if es.KeysReadNotFound != nil { - require.Equal(t, int(*es.KeysReadNotFound), int(stats.KeysReadNotFound), "keys read not found") - } - - if es.KeysReadExpired != nil { - require.Equal(t, int(*es.KeysReadExpired), int(stats.KeysReadExpired), "keys read expired") - } - - if es.Shuffles != nil { - require.Equal(t, int(*es.Shuffles), int(stats.Shuffles), "shuffles") - } - - if es.Evictions != nil { - require.Equal(t, int(*es.Evictions), int(stats.Evictions), "evictions") - } - - if es.KeysReaped != nil { - require.Equal(t, int(*es.KeysReaped), int(stats.KeysReaped), "keys reaped") - } - - if es.ReaperCycles != nil { - require.Equal(t, int(*es.ReaperCycles), int(stats.ReaperCycles), "reaper cycles") - } -} diff --git a/generic/fuzz_test.go b/generic/fuzz_test.go deleted file mode 100644 index c92e176..0000000 --- a/generic/fuzz_test.go +++ /dev/null @@ -1,40 +0,0 @@ -package lazylru_test - -import ( - "testing" - "time" - - lazylru "github.com/TriggerMail/lazylru/generic" - "github.com/stretchr/testify/require" -) - -func FuzzIntInt(f *testing.F) { - lru := lazylru.NewT[int, int](1000, 10*time.Second) - f.Cleanup(lru.Close) - f.Add(0, 0) - f.Fuzz(func(t *testing.T, k int, v int) { - lru.Set(k, v) - - a, ok := lru.Get(k) - require.True(t, ok, "failed to find %d", k) - require.Equal(t, v, a, "unexpected value for prev %d", k) - require.GreaterOrEqual(t, 1000, lru.Len()) - }) -} - -func FuzzStringStruct(f *testing.F) { - type data struct { - val int - } - - lru := lazylru.NewT[string, *data](1000, 10*time.Second) - f.Cleanup(lru.Close) - f.Add("foo", 0) - f.Fuzz(func(t *testing.T, k string, v int) { - lru.Set(k, &data{v}) - a, ok := lru.Get(k) - require.True(t, ok) - require.Equal(t, v, a.val) - require.GreaterOrEqual(t, 1000, lru.Len()) - }) -} diff --git a/generic/go.mod b/generic/go.mod deleted file mode 100644 index 7c587c3..0000000 --- a/generic/go.mod +++ /dev/null @@ -1,17 +0,0 @@ -module github.com/TriggerMail/lazylru/generic - -go 1.20 - -require ( - github.com/stretchr/testify v1.8.1 - github.com/zeebo/xxh3 v1.0.2 -) - -require ( - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/klauspost/cpuid/v2 v2.2.5 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/zeebo/assert v1.3.1 // indirect - golang.org/x/sys v0.9.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect -) diff --git a/generic/go.sum b/generic/go.sum deleted file mode 100644 index adb32b3..0000000 --- a/generic/go.sum +++ /dev/null @@ -1,26 +0,0 @@ -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= -github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/zeebo/assert v1.3.1 h1:vukIABvugfNMZMQO1ABsyQDJDTVQbn+LWSMy1ol1h6A= -github.com/zeebo/assert v1.3.1/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= -github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= -github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= -golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/generic/lazylru.go b/generic/lazylru.go deleted file mode 100644 index 4b10122..0000000 --- a/generic/lazylru.go +++ /dev/null @@ -1,507 +0,0 @@ -package lazylru - -import ( - "errors" - "math/rand/v2" - "sync" - "sync/atomic" - "time" - - heap "github.com/TriggerMail/lazylru/generic/containers/heap" -) - -// LazyLRU is an LRU cache that only reshuffles values if it is somewhat full. -// This is a cache implementation that uses a hash table for lookups and a -// priority queue to approximate LRU. Approximate because the usage is not -// updated on every get. Rather, items close to the head of the queue, those -// most likely to be read again and least likely to age out, are not updated. -// This assumption does not hold under every condition -- if the cache is -// undersized and churning a lot, this implementation will perform worse than an -// LRU that updates on every read. -// -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -type LazyLRU[K comparable, V any] struct { - doneCh chan int - index map[K]*item[K, V] - items itemPQ[K, V] - maxItems int - itemIx uint64 - ttl time.Duration - stats Stats - lock sync.RWMutex - isRunning bool - isClosing bool -} - -// New creates a LazyLRU[string, interface{} with the given capacity and default -// expiration. This is compatible with the pre-generic interface. The generic -// version is available as `NewT`. If maxItems is zero or fewer, the cache will -// not hold anything, but does still incur some runtime penalties. If ttl is -// greater than zero, a background ticker will be engaged to proactively remove -// expired items. -// -// Deprecated: To avoid the casting, use the generic NewT interface instead -// -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -func New(maxItems int, ttl time.Duration) *LazyLRU[string, interface{}] { - return NewT[string, interface{}](maxItems, ttl) -} - -// NewT creates a LazyLRU with the given capacity and default expiration. If -// maxItems is zero or fewer, the cache will not hold anything, but does still -// incur some runtime penalties. If ttl is greater than zero, a background -// ticker will be engaged to proactively remove expired items. -// -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -func NewT[K comparable, V any](maxItems int, ttl time.Duration) *LazyLRU[K, V] { - if maxItems < 0 { - maxItems = 0 - } - - doneCh := make(chan int) - lru := &LazyLRU[K, V]{ - items: itemPQ[K, V]{}, - index: map[K]*item[K, V]{}, - maxItems: maxItems, - itemIx: 1, // starting at 1 means that 0 can always be popped - ttl: ttl, - doneCh: doneCh, - isRunning: false, - stats: Stats{}, - } - - if ttl > 0 { - lru.reaper() - } else { - lru.isClosing = true - close(doneCh) - } - - return lru -} - -// IsRunning indicates whether the background reaper is active -// -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -func (lru *LazyLRU[K, V]) IsRunning() bool { - lru.lock.RLock() - defer lru.lock.RUnlock() - return lru.isRunning -} - -// reaper engages a background goroutine to randomly select items from the list -// on a regular basis and check them for expiry. This does not check the whole -// list, but starts at a random point, looking for expired items. -// -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -func (lru *LazyLRU[K, V]) reaper() { - if lru.ttl > 0 { - watchTime := lru.ttl / 10 - if watchTime < time.Millisecond { - watchTime = time.Millisecond - } - if watchTime > time.Second { - watchTime = time.Second - } - ticker := time.NewTicker(watchTime) - lru.lock.Lock() - lru.isRunning = true - lru.lock.Unlock() - go func() { - deathList := make([]*item[K, V], 0, 100) - keepGoing := true - for keepGoing { - select { - case <-lru.doneCh: - lru.lock.Lock() - // These triggered a race with the shouldBubble method. It - // shouldn't really matter, but there isn't much reason to - // worry about these things when the whole thing is going - // away. Putting a read lock around that first shouldBubble - // call had an 8.5% penalty on the read path, so leaving the - // data behind seemed like the better choice. - // Interestingly, the non-generic version of this code did - // not trigger the race condition. - // lru.items = nil - // lru.index = nil - // lru.maxItems = 0 - lru.isRunning = false - lru.lock.Unlock() - keepGoing = false - break - case <-ticker.C: - lru.reap(-1, deathList) - } - } - ticker.Stop() - }() - } -} - -// Reap removes all expired items from the cache -// -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -func (lru *LazyLRU[K, V]) Reap() { - lru.reap(0, make([]*item[K, V], 0, 100)) -} - -func (lru *LazyLRU[K, V]) reap(start int, deathList []*item[K, V]) { - timestamp := time.Now() - if lru.Len() == 0 { - return - } - - cycles := uint32(0) - for { - cycles++ - // grab a read lock while we are looking for items to kill - lru.lock.RLock() - - // make sure there is nothing left from the last cycle - deathList = deathList[:0] - if (!lru.isRunning) || len(lru.items) == 0 { - lru.lock.RUnlock() - break - } - if start < 0 { - start = rand.IntN(len(lru.items)) //nolint:gosec - } - end := start + 100 // why 100? no idea - if end > len(lru.items) { - end = len(lru.items) - } - for i := start; i < end; i++ { - if lru.items[i].expiration.Before(timestamp) { - deathList = append(deathList, lru.items[i]) - } - } - lru.lock.RUnlock() - - // if there are no candidates to kill, we're done - // break is safe here because we are between locks - if len(deathList) == 0 { - break - } - - lru.lock.Lock() - // mark the expired candidates as dead, remove from index - for ix, pqi := range deathList { - // it may have been touched between the locks - if pqi.insertNumber > 0 && pqi.expiration.Before(timestamp) { - lru.items.update(pqi, 0) - delete(lru.index, pqi.key) - deathList[ix] = nil - lru.stats.KeysReaped++ - } - } - // cut off all the expired items - for 0 < lru.items.Len() && lru.items[0].insertNumber == 0 { - _ = heap.Pop[*item[K, V]](&lru.items) - } - lru.lock.Unlock() - } - atomic.AddUint32(&lru.stats.ReaperCycles, cycles) -} - -// shouldBubble determines if a particular item should be updated on read and -// moved to the end of the queue. This is NOT thread safe and should only be -// called with a lock in place. -func (lru *LazyLRU[K, V]) shouldBubble(index int) bool { - return (index + (lru.maxItems - lru.items.Len())) < (lru.maxItems >> 2) -} - -// Get retrieves a value from the cache. The returned bool indicates whether the -// key was found in the cache. -// -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -func (lru *LazyLRU[K, V]) Get(key K) (V, bool) { - lru.lock.RLock() - pqi, ok := lru.index[key] - lru.lock.RUnlock() - if !ok { - atomic.AddUint32(&lru.stats.KeysReadNotFound, 1) - var zero V - return zero, false - } - - // there is a dangerous case if the read/lock/read pattern returns an - // unexpired key on the second read -- if we are not careful, we may end up - // trying to take the lock twice. Because "defer" can't help us here, I'm - // being really explicit about whether or not we have the lock already. - locked := false - // if the item is expired, remove it - if pqi.expiration.Before(time.Now()) && pqi.index >= 0 { - lru.lock.Lock() - locked = true - - // double check in case this has already been removed - if pqi.expiration.Before(time.Now()) && pqi.index >= 0 { - // this will push the item to the end - lru.items.update(pqi, 0) - delete(lru.index, pqi.key) - // cut off all the expired items. should only be one - for lru.items.Len() > 0 && lru.items[0].insertNumber == 0 { - _ = heap.Pop[*item[K, V]](&lru.items) - } - lru.stats.KeysReadExpired++ - lru.lock.Unlock() - var zero V - return zero, false - } - } - - // We only want to shuffle this item if it is far enough from the front that - // it is at risk of being evicted. This will save us from exclusive locking - // 75% of the time. - if lru.shouldBubble(pqi.index) { - if !locked { - lru.lock.Lock() - locked = true - } - // double check because someone else may have shuffled - if lru.shouldBubble(pqi.index) { - lru.items.update(pqi, atomic.AddUint64(&(lru.itemIx), 1)) - lru.stats.Shuffles++ - } - } - if locked { - lru.lock.Unlock() - } - atomic.AddUint32(&lru.stats.KeysReadOK, 1) - return pqi.value, ok -} - -// MGet retrieves values from the cache. Missing values will not be returned. -// -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -func (lru *LazyLRU[K, V]) MGet(keys ...K) map[K]V { - retval := make(map[K]V, len(keys)) - maybeExpired := make([]K, 0, len(keys)) - needsShuffle := make([]K, 0, len(keys)) - - lru.lock.RLock() - notfound := uint32(0) - for _, key := range keys { - if pqi, found := lru.index[key]; found { - retval[key] = pqi.value - if pqi.expiration.Before(time.Now()) && pqi.index >= 0 { - maybeExpired = append(maybeExpired, key) - } else if lru.shouldBubble(pqi.index) { - needsShuffle = append(needsShuffle, key) - } - } else { - notfound++ - } - } - lru.lock.RUnlock() - if notfound > 0 { - atomic.AddUint32(&lru.stats.KeysReadNotFound, notfound) - } - - // if we are done, let's be done - if len(retval) == 0 || (len(maybeExpired) == 0 && len(needsShuffle) == 0) { - atomic.AddUint32(&lru.stats.KeysReadOK, uint32(len(retval))) - return retval - } - - // we're going to have to change _something_ - lru.lock.Lock() - defer lru.lock.Unlock() - for _, key := range maybeExpired { - pqi, ok := lru.index[key] - if !ok { - continue - } - // if the item is expired, remove it - if pqi.expiration.Before(time.Now()) && pqi.index >= 0 { - // this will push the item to the end - lru.items.update(pqi, 0) - delete(lru.index, key) - delete(retval, key) - lru.stats.KeysReadExpired++ - } - } - - // cut off all the expired items - for lru.items.Len() > 0 && lru.items[0].insertNumber == 0 { - _ = heap.Pop[*item[K, V]](&lru.items) - } - - for _, key := range needsShuffle { - // we only want to shuffle this item if it is far - // enough from the front that it is at risk of being - // evicted. This will save us from locking 75% of - // the time - pqi, ok := lru.index[key] - if ok && lru.shouldBubble(pqi.index) { - lru.stats.Shuffles++ - // double check because someone else may have shuffled - lru.items.update(pqi, atomic.AddUint64(&(lru.itemIx), 1)) - } - } - - atomic.AddUint32(&lru.stats.KeysReadOK, uint32(len(retval))) - return retval -} - -// Set writes to the cache -// -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -func (lru *LazyLRU[K, V]) Set(key K, value V) { - lru.SetTTL(key, value, lru.ttl) -} - -// SetTTL writes to the cache, expiring with the given time-to-live value -// -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -func (lru *LazyLRU[K, V]) SetTTL(key K, value V, ttl time.Duration) { - lru.lock.Lock() - lru.setInternal(key, value, time.Now().Add(ttl)) - lru.lock.Unlock() -} - -// setInternal writes elements. This is NOT thread safe and should always be -// called with a write lock -func (lru *LazyLRU[K, V]) setInternal(key K, value V, expiration time.Time) { - if lru.maxItems <= 0 { - return - } - lru.stats.KeysWritten++ - if pqi, ok := lru.index[key]; ok { - pqi.expiration = expiration - pqi.value = value - lru.items.update(pqi, atomic.AddUint64(&(lru.itemIx), 1)) - } else { - pqi := &item[K, V]{ - value: value, - insertNumber: atomic.AddUint64(&(lru.itemIx), 1), - key: key, - expiration: expiration, - } - - // remove excess - for lru.items.Len() >= lru.maxItems { - deadGuy := heap.Pop[*item[K, V]](&lru.items) - delete(lru.index, deadGuy.key) - lru.stats.Evictions++ - } - heap.Push[*item[K, V]](&lru.items, pqi) - lru.index[key] = pqi - } -} - -// MSet writes multiple keys and values to the cache. If the "key" and "value" -// parameters are of different lengths, this method will return an error. -// -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -func (lru *LazyLRU[K, V]) MSet(keys []K, values []V) error { - return lru.MSetTTL(keys, values, lru.ttl) -} - -// MSetTTL writes multiple keys and values to the cache, expiring with the given -// time-to-live value. If the "key" and "value" parameters are of different -// lengths, this method will return an error. -// -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -func (lru *LazyLRU[K, V]) MSetTTL(keys []K, values []V, ttl time.Duration) error { - // we don't need to store stuff that is already expired - if ttl < 0 { - return nil - } - if len(keys) != len(values) { - return errors.New("Mismatch between number of keys and number of values") - } - - lru.lock.Lock() - expiration := time.Now().Add(ttl) - for i := 0; i < len(keys); i++ { - lru.setInternal(keys[i], values[i], expiration) - } - lru.lock.Unlock() - return nil -} - -// Delete elimitates a key from the cache. Removing a key that is not in the index is safe. -// -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -func (lru *LazyLRU[K, V]) Delete(key K) { - // if the key isn't here, don't bother taking the exclusive lock - lru.lock.RLock() - _, ok := lru.index[key] - lru.lock.RUnlock() - if !ok { - return - } - lru.lock.Lock() - pqi, ok := lru.index[key] - if !ok { - lru.lock.Unlock() - return - } - delete(lru.index, pqi.key) // remove from search index - lru.items.update(pqi, 0) // move this item to the top of the heap - heap.Pop[*item[K, V]](&lru.items) // pop item from the top of the heap - lru.lock.Unlock() -} - -// Len returns the number of items in the cache -// -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -func (lru *LazyLRU[K, V]) Len() int { - lru.lock.RLock() - defer lru.lock.RUnlock() - return len(lru.items) -} - -// Close stops the reaper process. This is safe to call multiple times. -// -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -func (lru *LazyLRU[K, V]) Close() { - lru.lock.Lock() - if !lru.isClosing { - close(lru.doneCh) - lru.isClosing = true - } - lru.lock.Unlock() -} - -// Stats gets a copy of the stats held by the cache. Note that this is a copy, -// so returned objects will not update as the service continues to execute. -// -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -func (lru *LazyLRU[K, V]) Stats() Stats { - // note that this returns a copy of stats, not a reference - return lru.stats -} diff --git a/generic/lazylru_benchmark_test.go b/generic/lazylru_benchmark_test.go deleted file mode 100644 index 86b9428..0000000 --- a/generic/lazylru_benchmark_test.go +++ /dev/null @@ -1,237 +0,0 @@ -package lazylru_test - -import ( - "fmt" - "math/rand/v2" - "runtime" - "strconv" - "testing" - "time" - - lazylru "github.com/TriggerMail/lazylru/generic" -) - -const keycnt = 100000 - -type value struct { - x *int - s string - v int - b byte -} - -var _ = value{ - x: nil, - s: "xyzzy", - v: 123, - b: 123, -} - -var keys = func() []string { - k := make([]string, keycnt) - - for i := 0; i < keycnt; i++ { - k[i] = strconv.Itoa(i) - } - return k -}() - -type benchconfig struct { - capacity int - keyCount int - readRate float64 -} - -func (bc benchconfig) Name() string { - comment := "eqcap" - if bc.capacity > bc.keyCount { - comment = "overcap" - } else if bc.capacity < bc.keyCount { - comment = "undercap" - } - return fmt.Sprintf("%dW/%dR_%s", 100-int(100*bc.readRate), int(100*bc.readRate), comment) -} - -func (bc benchconfig) InterfaceArray(b *testing.B) { - lru := lazylru.New(bc.capacity, time.Minute) - defer lru.Close() - for i := 0; i < bc.keyCount; i++ { - lru.Set(keys[i], []int{i}) - } - runtime.GC() - b.ResetTimer() - for i := 0; i < b.N; i++ { - ix := i % bc.keyCount - if rand.Float64() < bc.readRate { //nolint:gosec - // if true { - if iv, ok := lru.Get(keys[ix]); !ok { - continue - } else { - v, ok := iv.([]int) - if !ok { - b.Fatalf("expected integer value, got %v", iv) - } - if v[0] != ix { - b.Fatalf("expected %d, got %d", ix, v[0]) - } - } - } else { - lru.Set(keys[ix], []int{ix}) - } - } -} - -func (bc benchconfig) GenericArray(b *testing.B) { - lru := lazylru.NewT[string, []int](bc.capacity, time.Minute) - defer lru.Close() - for i := 0; i < bc.keyCount; i++ { - lru.Set(keys[i], []int{i}) - } - runtime.GC() - b.ResetTimer() - for i := 0; i < b.N; i++ { - ix := i % bc.keyCount - if rand.Float64() < bc.readRate { //nolint:gosec - // if true { - if v, ok := lru.Get(keys[ix]); !ok { - continue - } else if v[0] != ix { - b.Fatalf("expected %d, got %d", ix, v[0]) - } - } else { - lru.Set(keys[ix], []int{ix}) - } - } -} - -func (bc benchconfig) InterfaceStructPtr(b *testing.B) { - lru := lazylru.New(bc.capacity, time.Minute) - defer lru.Close() - for i := 0; i < bc.keyCount; i++ { - lru.Set(keys[i], &value{v: i}) - } - runtime.GC() - b.ResetTimer() - for i := 0; i < b.N; i++ { - ix := i % bc.keyCount - if rand.Float64() < bc.readRate { //nolint:gosec - // if true { - if iv, ok := lru.Get(keys[ix]); !ok { - continue - } else { - v, ok := iv.(*value) - if !ok { - b.Fatalf("expected integer value, got %v", iv) - } - if v.v != ix { - b.Fatalf("expected %d, got %d", ix, v.v) - } - } - } else { - lru.Set(keys[ix], &value{v: ix}) - } - } -} - -func (bc benchconfig) GenericStructPtr(b *testing.B) { - lru := lazylru.NewT[string, *value](bc.capacity, time.Minute) - defer lru.Close() - for i := 0; i < bc.keyCount; i++ { - lru.Set(keys[i], &value{v: i}) - } - runtime.GC() - b.ResetTimer() - for i := 0; i < b.N; i++ { - ix := i % bc.keyCount - if rand.Float64() < bc.readRate { //nolint:gosec - // if true { - if v, ok := lru.Get(keys[ix]); !ok { - continue - } else if v.v != ix { - b.Fatalf("expected %d, got %d", ix, v.v) - } - } else { - lru.Set(keys[ix], &value{v: ix}) - } - } -} - -func (bc benchconfig) InterfaceValue(b *testing.B) { - lru := lazylru.New(bc.capacity, time.Minute) - defer lru.Close() - for i := 0; i < bc.keyCount; i++ { - lru.Set(keys[i], i) - } - runtime.GC() - b.ResetTimer() - for i := 0; i < b.N; i++ { - ix := i % bc.keyCount - if rand.Float64() < bc.readRate { //nolint:gosec - // if true { - if iv, ok := lru.Get(keys[ix]); !ok { - continue - } else { - v, ok := iv.(int) - if !ok { - b.Fatalf("expected integer value, got %v", iv) - } - if v != ix { - b.Fatalf("expected %d, got %d", ix, v) - } - } - } else { - lru.Set(keys[ix], ix) - } - } -} - -func (bc benchconfig) GenericValue(b *testing.B) { - lru := lazylru.NewT[string, int](bc.capacity, time.Minute) - defer lru.Close() - for i := 0; i < bc.keyCount; i++ { - lru.Set(keys[i], i) - } - runtime.GC() - b.ResetTimer() - for i := 0; i < b.N; i++ { - ix := i % bc.keyCount - if rand.Float64() < bc.readRate { //nolint:gosec - // if true { - if v, ok := lru.Get(keys[ix]); !ok { - continue - } else if v != ix { - b.Fatalf("expected %d, got %d", ix, v) - } - } else { - lru.Set(keys[ix], ix) - } - } -} - -func Benchmark(b *testing.B) { - for _, bc := range []benchconfig{ - // {1, 1, 0.5}, // this is meant as a warm-up - {1000, 100, 0.0}, - {1000, 100, 1.0}, - {1000, 100, 0.25}, - {1000, 100, 0.75}, - {1000, 100, 0.99}, - {100, 1000, 0.0}, - {100, 1000, 1.0}, - {100, 1000, 0.25}, - {100, 1000, 0.75}, - {100, 1000, 0.99}, - {100, 100, 0.0}, - {100, 100, 1.0}, - {100, 100, 0.25}, - {100, 100, 0.75}, - {100, 100, 0.99}, - } { - b.Run(bc.Name()+"/interface/array", bc.InterfaceArray) - b.Run(bc.Name()+"/generic/array", bc.GenericArray) - b.Run(bc.Name()+"/interface/struct", bc.InterfaceStructPtr) - b.Run(bc.Name()+"/generic/struct", bc.GenericStructPtr) - b.Run(bc.Name()+"/interface/value", bc.InterfaceValue) - b.Run(bc.Name()+"/generic/value", bc.GenericValue) - } -} diff --git a/generic/lazylru_test.go b/generic/lazylru_test.go deleted file mode 100644 index ff046e8..0000000 --- a/generic/lazylru_test.go +++ /dev/null @@ -1,448 +0,0 @@ -package lazylru_test - -import ( - "strconv" - "testing" - "time" - - lazylru "github.com/TriggerMail/lazylru/generic" - "github.com/stretchr/testify/require" -) - -func doTest[K comparable, V any](t *testing.T, maxItems int, ttl time.Duration, test func(t *testing.T, lru *lazylru.LazyLRU[K, V]), expected ExpectedStats) { - lru := lazylru.NewT[K, V](maxItems, ttl) - test(t, lru) - lru.Close() - expected.Test(t, lru.Stats()) -} - -func TestMakeNew(t *testing.T) { - doTest(t, 10, time.Hour, func(t *testing.T, lru *lazylru.LazyLRU[string, int]) { - require.NotNil(t, lru) - }, - ExpectedStats{}, - ) -} - -func TestGetUnknown(t *testing.T) { - doTest(t, 10, time.Hour, func(t *testing.T, lru *lazylru.LazyLRU[string, int]) { - v, ok := lru.Get("something new") - require.Equal(t, 0, v) - require.False(t, ok) - }, - ExpectedStats{}.WithKeysReadNotFound(1), - ) -} - -func TestGetKnown(t *testing.T) { - doTest(t, 10, time.Hour, func(t *testing.T, lru *lazylru.LazyLRU[string, string]) { - lru.Set("abloy", "medeco") - v, ok := lru.Get("abloy") - require.True(t, ok) - require.Equal(t, "medeco", v) - }, - ExpectedStats{}.WithKeysWritten(1).WithKeysReadOK(1), - ) -} - -func testGetKnownShuffleMitigationHelper(t *testing.T, getter func(lru *lazylru.LazyLRU[string, int], key string) (int, bool)) { - doTest(t, 100, time.Hour, func(t *testing.T, lru *lazylru.LazyLRU[string, int]) { - keys := make([]string, 100) - values := make([]int, len(keys)) - for i := 0; i < len(keys); i++ { - keys[i] = strconv.FormatInt(int64(i), 10) - values[i] = i - } - require.NoError(t, lru.MSet(keys, values)) - - // This should affect 100 reads, but only 1 shuffle - for i := 0; i < 100; i++ { - v, ok := getter(lru, "0") - require.True(t, ok) - require.Equal(t, 0, v) - } - - // This should affect 100 reads, but only no shuffles - for i := 0; i < 100; i++ { - v, ok := getter(lru, "99") - require.True(t, ok) - require.Equal(t, 99, v) - } - }, - ExpectedStats{}.WithKeysReadOK(200).WithShuffles(1), - ) -} - -func TestGetKnownShuffleMitigationGet(t *testing.T) { - testGetKnownShuffleMitigationHelper(t, - func(lru *lazylru.LazyLRU[string, int], key string) (int, bool) { - v, ok := lru.Get(key) - return v, ok - }) -} - -func TestGetKnownShuffleMitigationMGet(t *testing.T) { - testGetKnownShuffleMitigationHelper(t, - func(lru *lazylru.LazyLRU[string, int], key string) (int, bool) { - d := lru.MGet(key) - v, ok := d[key] - return v, ok - }, - ) -} - -func TestMGetUnknown(t *testing.T) { - doTest(t, 10, time.Hour, func(t *testing.T, lru *lazylru.LazyLRU[string, string]) { - found := lru.MGet("a", "b", "c") - require.Equal(t, 0, len(found)) - }, - ExpectedStats{}.WithKeysReadNotFound(3), - ) -} - -func TestMGetKnown(t *testing.T) { - doTest(t, 10, time.Hour, func(t *testing.T, lru *lazylru.LazyLRU[string, string]) { - err := lru.MSet( - []string{"abloy", "schlage"}, - []string{"medeco", "kwikset"}, - ) - require.NoError(t, err) - found := lru.MGet("abloy", "schlage") - require.Equal(t, 2, len(found)) - v, ok := found["abloy"] - require.True(t, ok) - require.Equal(t, "medeco", v) - }, - ExpectedStats{}.WithKeysWritten(2).WithKeysReadOK(2), - ) -} - -func TestSetNTimes(t *testing.T) { - doTest(t, 10, time.Hour, func(t *testing.T, lru *lazylru.LazyLRU[string, string]) { - require.Equal(t, 0, lru.Len()) - lru.Set("abloy", "schlage") - require.Equal(t, 1, lru.Len()) - for i := 0; i < 1000; i++ { - lru.Set("abloy", "schlage") - } - require.Equal(t, 1, lru.Len()) - }, - ExpectedStats{}.WithKeysWritten(1001), - ) -} - -func TestMGetOneKnown(t *testing.T) { - doTest(t, 10, time.Hour, func(t *testing.T, lru *lazylru.LazyLRU[string, string]) { - lru.Set("abloy", "medeco") - - found := lru.MGet("abloy") - require.Equal(t, 1, len(found)) - - v, ok := found["abloy"] - require.True(t, ok) - require.Equal(t, "medeco", v) - }, - ExpectedStats{}.WithKeysWritten(1).WithKeysReadOK(1), - ) -} - -func TestMSetBad(t *testing.T) { - doTest(t, 10, time.Hour, func(t *testing.T, lru *lazylru.LazyLRU[string, string]) { - err := lru.MSet( - []string{"abloy"}, - []string{"medeco", "kwikset"}, - ) - require.Error(t, err) - }, - ExpectedStats{}.WithKeysWritten(0), - ) -} - -func TestMSetTooMany(t *testing.T) { - doTest(t, 5, time.Hour, func(t *testing.T, lru *lazylru.LazyLRU[string, string]) { - err := lru.MSet( - []string{"a", "b", "c", "d", "e", "f", "g"}, - []string{"a", "b", "c", "d", "e", "f", "g"}, - ) - require.NoError(t, err) - require.Equal(t, 5, lru.Len()) - }, - ExpectedStats{}.WithKeysWritten(7).WithEvictions(2), - ) -} - -func TestMSetTooManyTwice(t *testing.T) { - doTest(t, 5, time.Hour, func(t *testing.T, lru *lazylru.LazyLRU[string, string]) { - err := lru.MSet( - []string{"a", "b", "c", "d", "e", "f", "g"}, - []string{"a", "b", "c", "d", "e", "f", "g"}, - ) - require.NoError(t, err) - require.Equal(t, 5, lru.Len()) - found := lru.MGet("a", "b", "c", "d", "e", "f", "g") - require.Equal(t, 5, len(found)) - - // "g" will still be in the set, but "a" will evict something - err = lru.MSet( - []string{"a", "g"}, - []string{"a", "g"}, - ) - - require.NoError(t, err) - require.Equal(t, 5, lru.Len()) - _, ok := lru.Get("f") - require.True(t, ok) - _, ok = lru.Get("g") - require.True(t, ok) - }, - ExpectedStats{}. - WithKeysWritten(9). - WithEvictions(3). - WithKeysReadOK(7). - WithKeysReadNotFound(2), - ) -} - -func TestMGetExpired(t *testing.T) { - doTest(t, 5, time.Millisecond, func(t *testing.T, lru *lazylru.LazyLRU[string, string]) { - lru.Set("abloy", "medeco") - time.Sleep(time.Millisecond * 10) - - found := lru.MGet("abloy") - require.Equal(t, 0, len(found)) - }, - ExpectedStats{}. - WithKeysWritten(1). - WithKeysReadExpired(0). - WithKeysReadNotFound(1). - WithKeysReaped(1), - ) -} - -func TestClose(t *testing.T) { - lru := lazylru.NewT[string, string](10, time.Hour) - require.True(t, lru.IsRunning()) - lru.Close() - time.Sleep(time.Millisecond * 10) - require.False(t, lru.IsRunning()) - lru.Close() // ensure double-close is safe -} - -func TestCloseWithReap(t *testing.T) { - doTest(t, 10, 10*time.Millisecond, func(t *testing.T, lru *lazylru.LazyLRU[string, int]) { - require.True(t, lru.IsRunning()) - - lru.SetTTL("abloy", 0, time.Hour) - err := lru.MSetTTL( - []string{"a", "b", "c", "d", "e"}, - []int{1, 2, 3, 4, 5}, - 0, - ) - require.NoError(t, err) - require.Equal(t, 6, lru.Len()) - time.Sleep(time.Millisecond * 20) - require.True(t, lru.IsRunning()) - require.Equal(t, 1, lru.Len()) - lru.Close() - time.Sleep(time.Millisecond * 10) - require.False(t, lru.IsRunning()) - }, - ExpectedStats{}. - WithKeysWritten(6). - WithKeysReaped(5), - ) -} - -func TestReap(t *testing.T) { - doTest(t, 10, time.Hour, func(t *testing.T, lru *lazylru.LazyLRU[string, string]) { - lru.Reap() - - lru.SetTTL("abloy", "medeco", time.Millisecond*10) - - found := lru.MGet("abloy") - require.Equal(t, 1, len(found)) - - time.Sleep(time.Millisecond * 10) - lru.Reap() - found = lru.MGet("abloy") - require.Equal(t, 0, len(found)) - require.Equal(t, 0, lru.Len()) - }, - ExpectedStats{}. - WithKeysWritten(1). - WithKeysReadOK(1). - WithKeysReadNotFound(1). - WithKeysReaped(1). - // make sure that we actually reaped the key, not that the read of an - // expired key did it - WithKeysReadExpired(0), - ) -} - -func TestPushBeyondCapacity(t *testing.T) { - doTest(t, 10, time.Hour, func(t *testing.T, lru *lazylru.LazyLRU[string, string]) { - keys := make([]string, 100) - for i := 0; i < len(keys); i++ { - keys[i] = strconv.FormatInt(int64(i), 10) - lru.Set(keys[i], keys[i]) - } - - for _, key := range keys[:90] { - _, ok := lru.Get(key) - require.False(t, ok, "key: %s", key) - } - for _, key := range keys[90:] { - v, ok := lru.Get(key) - require.True(t, ok, "key: %s", key) - require.Equal(t, key, v, "key: %s", key) - } - }, - ExpectedStats{}. - WithKeysWritten(100). - WithKeysReadOK(10). - WithKeysReadNotFound(90). - WithEvictions(90), - ) -} - -func TestPushBeyondCapacitySave28(t *testing.T) { - doTest(t, 10, time.Hour, func(t *testing.T, lru *lazylru.LazyLRU[string, string]) { - keys := make([]string, 100) - for i := 0; i < len(keys); i++ { - keys[i] = strconv.FormatInt(int64(i), 10) - lru.Set(keys[i], keys[i]) - if i >= 28 { - _, ok := lru.Get("28") // keep 28 hot - require.True(t, ok, "failed on cycle %d", i) - if !ok { - break - } - } - } - _, ok28 := lru.Get("28") - require.True(t, ok28, "28") - _, ok27 := lru.Get("27") - require.False(t, ok27, "27") - }, - ExpectedStats{}. - WithKeysWritten(100). - WithKeysReadOK(100+1-28). - WithKeysReadNotFound(1), - ) -} - -func TestPushBeyondCapacitySave28WithMGet(t *testing.T) { - doTest(t, 10, time.Hour, func(t *testing.T, lru *lazylru.LazyLRU[string, string]) { - keys := make([]string, 100) - for i := 0; i < len(keys); i++ { - keys[i] = strconv.FormatInt(int64(i), 10) - lru.Set(keys[i], keys[i]) - if i >= 28 { - d := lru.MGet("28") // keep 28 hot - _, ok := d["28"] - require.True(t, ok, "failed on cycle %d", i) - if !ok { - break - } - } - } - _, ok28 := lru.Get("28") - require.True(t, ok28, "28") - _, ok27 := lru.Get("27") - require.False(t, ok27, "27") - }, - ExpectedStats{}. - WithKeysWritten(100). - WithKeysReadOK(100+1-28). - WithKeysReadNotFound(1), - ) -} - -func TestGetExpired(t *testing.T) { - doTest(t, 10, 0, func(t *testing.T, lru *lazylru.LazyLRU[string, string]) { - lru.Set("a", "a") - require.Equal(t, 1, lru.Len()) - v, ok := lru.Get("a") - require.False(t, ok) - require.Equal(t, "", v) - require.Equal(t, 0, lru.Len()) - }, - ExpectedStats{}. - WithKeysWritten(1). - WithKeysReadExpired(1), - ) -} - -func TestExpireCleanup(t *testing.T) { - doTest(t, 10, 1, func(t *testing.T, lru *lazylru.LazyLRU[string, string]) { - lru.Set("a", "a") - require.Equal(t, 1, lru.Len()) - time.Sleep(time.Millisecond * 100) - require.Equal(t, 0, lru.Len()) - }, - ExpectedStats{}. - WithKeysWritten(1). - WithKeysReaped(1), - ) -} - -func TestMGetSomeExpired(t *testing.T) { - doTest(t, 10, time.Hour, func(t *testing.T, lru *lazylru.LazyLRU[string, string]) { - lru.Set("a", "a") - lru.SetTTL("b", "b", 0) - require.Equal(t, 2, lru.Len()) - vals := lru.MGet("a", "b") - require.Equal(t, 1, len(vals)) - v, ok := vals["a"] - require.True(t, ok) - require.Equal(t, "a", v) - require.Equal(t, 1, lru.Len()) - }, - ExpectedStats{}. - WithKeysWritten(2). - WithKeysReadOK(1). - WithKeysReadExpired(1), - ) -} - -func TestNonGenericFactory(t *testing.T) { - lru := lazylru.New(10, time.Hour) - lru.Close() - lru.Set("abloy", "medeco") - v, ok := lru.Get("abloy") - require.True(t, ok) - vstr, ok := v.(string) - require.True(t, ok) - require.Equal(t, "medeco", vstr) - es := ExpectedStats{}.WithKeysWritten(1).WithKeysReadOK(1) - es.Test(t, lru.Stats()) -} - -func TestZeroSize(t *testing.T) { - lru := lazylru.New(0, time.Hour) - lru.Close() - lru.Set("abloy", "medeco") - _, ok := lru.Get("abloy") - require.False(t, ok) -} - -func TestNegativeSize(t *testing.T) { - lru := lazylru.New(-1, time.Hour) - lru.Close() - lru.Set("abloy", "medeco") - _, ok := lru.Get("abloy") - require.False(t, ok) -} - -func TestDelete(t *testing.T) { - doTest(t, 10, time.Hour, func(t *testing.T, lru *lazylru.LazyLRU[string, string]) { - lru.Set("abloy", "medeco") - _, ok := lru.Get("abloy") - require.True(t, ok) - lru.Delete("abloy") - _, ok = lru.Get("abloy") - require.False(t, ok) - }, - ExpectedStats{}.WithKeysWritten(1).WithKeysReadOK(1).WithKeysReadNotFound(1), - ) -} diff --git a/generic/pq.go b/generic/pq.go deleted file mode 100644 index 24738d2..0000000 --- a/generic/pq.go +++ /dev/null @@ -1,59 +0,0 @@ -package lazylru - -import ( - "time" - - heap "github.com/TriggerMail/lazylru/generic/containers/heap" -) - -// An item is something we manage in a insertNumber queue. -// The index is needed by update and is maintained by the heap.Interface methods. -type item[K any, V any] struct { - expiration time.Time - value V - key K - insertNumber uint64 - index int -} - -// itemPQ isn't thread safe, so it is the responsibility of the containing -// LazyLRU to be safe in the face of concurrent access -type itemPQ[K any, V any] []*item[K, V] - -func (pq itemPQ[K, V]) Len() int { return len(pq) } - -func (pq itemPQ[K, V]) Less(i, j int) bool { - // We want Pop to give us the lowest, not highest insertNumber so we use less than here. - return pq[i].insertNumber < pq[j].insertNumber -} - -func (pq itemPQ[K, V]) Swap(i, j int) { - pq[i], pq[j] = pq[j], pq[i] - pq[i].index = i - pq[j].index = j -} - -func (pq *itemPQ[K, V]) Push(pqi *item[K, V]) { - n := len(*pq) - pqi.index = n - *pq = append(*pq, pqi) -} - -func (pq *itemPQ[K, V]) Pop() *item[K, V] { - if len(*pq) == 0 { - return nil - } - old := *pq - n := len(old) - pqi := old[n-1] - old[n-1] = nil // avoid memory leak - pqi.index = -1 // for safety - *pq = old[0 : n-1] - return pqi -} - -// update modifies the insertNumber and value of an item in the queue. -func (pq *itemPQ[K, V]) update(pqi *item[K, V], insertNumber uint64) { - pqi.insertNumber = insertNumber - heap.Fix[*item[K, V]](pq, pqi.index) -} diff --git a/generic/pq_test.go b/generic/pq_test.go deleted file mode 100644 index fb9722b..0000000 --- a/generic/pq_test.go +++ /dev/null @@ -1,77 +0,0 @@ -package lazylru - -import ( - "testing" - - heap "github.com/TriggerMail/lazylru/generic/containers/heap" - - "github.com/stretchr/testify/require" -) - -func TestPopEmpty(t *testing.T) { - pq := itemPQ[string, int]{} - require.Nil(t, pq.Pop()) -} - -func TestPushPop(t *testing.T) { - pq := itemPQ[string, int]{} - - heap.Push[*item[string, int]](&pq, &item[string, int]{ - value: 13, - insertNumber: 0, - key: "schlage", - }) - pqi := heap.Pop[*item[string, int]](&pq) - require.Equal(t, "schlage", pqi.key) - require.Equal(t, 13, pqi.value) -} - -func TestPushPopOrdered(t *testing.T) { - pq := itemPQ[string, int]{} - - heap.Push[*item[string, int]](&pq, &item[string, int]{ - value: 13, - insertNumber: 0, - key: "schlage", - }) - heap.Push[*item[string, int]](&pq, &item[string, int]{ - value: 13, - insertNumber: 1, - key: "kwikset", - }) - heap.Push[*item[string, int]](&pq, &item[string, int]{ - value: 13, - insertNumber: 2, - key: "abloy", - }) - - require.Equal(t, "schlage", heap.Pop[*item[string, int]](&pq).key) - require.Equal(t, "kwikset", heap.Pop[*item[string, int]](&pq).key) - require.Equal(t, "abloy", heap.Pop[*item[string, int]](&pq).key) -} - -func TestPushPopUpdate(t *testing.T) { - pq := itemPQ[string, int]{} - - heap.Push[*item[string, int]](&pq, &item[string, int]{ - value: 13, - insertNumber: 0, - key: "schlage", - }) - heap.Push[*item[string, int]](&pq, &item[string, int]{ - value: 13, - insertNumber: 2, - key: "abloy", - }) - kwi := &item[string, int]{ - value: 13, - insertNumber: 1, - key: "kwikset", - } - heap.Push[*item[string, int]](&pq, kwi) - pq.update(kwi, 3) - - require.Equal(t, "schlage", heap.Pop[*item[string, int]](&pq).key) - require.Equal(t, "abloy", heap.Pop[*item[string, int]](&pq).key) - require.Equal(t, "kwikset", heap.Pop[*item[string, int]](&pq).key) -} diff --git a/generic/revive.toml b/generic/revive.toml deleted file mode 100644 index 2d4274f..0000000 --- a/generic/revive.toml +++ /dev/null @@ -1,27 +0,0 @@ -# this configuration is the same as the default, but warnings and errors return -# a non-zero value -ignoreGeneratedHeader = false -severity = "warning" -confidence = 0.8 -warningCode = 1 -errorCode = 1 - -[rule.blank-imports] -[rule.context-as-argument] -[rule.context-keys-type] -[rule.dot-imports] -[rule.error-return] -[rule.error-strings] -[rule.error-naming] -[rule.exported] -[rule.if-return] -[rule.increment-decrement] -[rule.var-naming] -[rule.var-declaration] -[rule.package-comments] -[rule.range] -[rule.receiver-naming] -[rule.time-naming] -[rule.unexported-return] -[rule.indent-error-flow] -[rule.errorf] \ No newline at end of file diff --git a/generic/sharded/README.md b/generic/sharded/README.md deleted file mode 100644 index b0860af..0000000 --- a/generic/sharded/README.md +++ /dev/null @@ -1,144 +0,0 @@ -# Sharded LazyLRU - -The `LazyLRU` works hard to reduce blocking by reducing reorder operations, but there are limits. Another common mechanism to reduce blocking is to shard the cache. Two keys that target separate shards will not conflict, at least not from a memory-safety perspective. If lock contention is a problem you have in your application, sharding may be for you! There are volumes of literature on this subject and this README is here to tell you how you _can_ shard your cache, not whether or not you should. - -The implementation here is a wrapper over the `lazylru.LazyLRU` cache. There are no attempts to spread out reaper threads or assist with memory locality. As a result, high shard counts will hurt performance. Considering that lock contention should reduce directly by the shard count, there are no advantages to high shard counts. - -## Dependencies - -`lazylru.LazyLRU` has no external dependencies beyond the standard library. However, `sharded.HashingSharder` relies on [github.com/zeebo/xxh3](https://github.com/zeebo/xxh3). - -## Usage - -```go -import ( - "time" - - lazylru "github.com/TriggerMail/lazylru/generic" - sharded "github.com/TriggerMail/lazylru/generic/sharded" -) - -func main() { - regularCache := lazylru.NewT[string, string](10, time.Minute) - shardedCache := sharded.NewT[string, string](10, time.Minute, 10, sharded.StringSharder) -} -``` - -In the example above, we are creating a flat cache and a sharded cache. The flat cache will hold 10 items. The sharded cache will hold up to 10 items in each of 10 shards, so up to 100 items. There is no mechanism to limit the total size of the sharded cache other than limiting the size of each shard. `sharded.LazyLRU` exposes the same interface as `lazylru.LazyLRU`, so it should be a drop-in replacement. - -## Sharding - -The sharding function should return integers, uniformly-distributed over the key space. This does _not_ need to be limited to values less than the shard count -- the library takes care of that. - -The sharded cache in the example above is using a helper function, `sharded.StringSharder`, rather than implementing a custom sharder function. There is also a `shared.BytesSharder` for byte-slice keys. - -### Custom types using the `HashingSharder` - -Caching with custom key types is also possible. Without access to some of the compiler guts, we can't do [what Go does for hashmaps](https://dave.cheney.net/2018/05/29/how-the-go-runtime-implements-maps-efficiently-without-generics). There is some compiler-time rewriting to use magic in the `runtime` package as well as hash functions implemented outside of Go that aren't available to us. We could also try to create a universal hasher, such as [mitchellh/hashstructure](https://github.com/mitchellh/hashstructure), but the reliance on reflection makes that a non-starter from a performance perspective. The same for an encoding-based solution like `encoding/gob`, which also relies on reflection. It might be possible to inspect the type of `K` at start time and use reflection to generate a hash function, but that's beyond the scope of what I'm trying to do here. - -Instead, we can use generics to allow the caller to define a hash function. There is another helper to assist in the creation of those sharder functions. These go through a hash function to get the necessary `uint64` value. The `HashingSharder` is a zero-allocation implementation based on `XXH3`, so as long as you don't make any allocations yourself, the resulting sharder should be pretty cheap. - -```go -import ( - "time" - - lazylru "github.com/TriggerMail/lazylru/generic" - sharded "github.com/TriggerMail/lazylru/generic/sharded" -) - -type CustomKeyType struct { - Field0 string - Field1 []byte - Field2 int -} - -func main() { - shardedCache := sharded.NewT[CustomKeyType, string]( - 10, - time.Minute, - 10, - sharded.HashingSharder(func(k CustomKeyType, h sharded.H) { - h.WriteString(k.Field0) - h.Write(k.Field1) - h.WriteUint64(uint64(k.Field2)) - }, - ) -} -``` - -On my laptop, the `HashingSharder` and its children the `StringSharder` and the `BytesSharder` proved to be fast and cheap with various key sizes. The `GobSharder`, which used the same pool of `XXH3` hashers, was 28x slower and allocated significant memory on each cycle. I have deleted the `GobSharder` for this reason. - -```text -$ go test -bench . -benchmem -goos: darwin -goarch: arm64 -pkg: github.com/TriggerMail/lazylru/generic/sharded -BenchmarkStringSharder/1-8 56328306 21.19 ns/op 0 B/op 0 allocs/op -BenchmarkStringSharder/4-8 57913196 20.57 ns/op 0 B/op 0 allocs/op -BenchmarkStringSharder/16-8 59431563 19.92 ns/op 0 B/op 0 allocs/op -BenchmarkStringSharder/64-8 54227484 21.80 ns/op 0 B/op 0 allocs/op -BenchmarkStringSharder/256-8 26014356 45.72 ns/op 0 B/op 0 allocs/op -BenchmarkBytesSharder/1-8 55908974 21.32 ns/op 0 B/op 0 allocs/op -BenchmarkBytesSharder/4-8 57933231 20.60 ns/op 0 B/op 0 allocs/op -BenchmarkBytesSharder/16-8 59253040 19.96 ns/op 0 B/op 0 allocs/op -BenchmarkBytesSharder/64-8 54259972 21.80 ns/op 0 B/op 0 allocs/op -BenchmarkBytesSharder/256-8 26069034 45.88 ns/op 0 B/op 0 allocs/op -BenchmarkCustomSharder-8 23089836 50.88 ns/op 0 B/op 0 allocs/op -BenchmarkGobSharder-8 794709 1412 ns/op 1128 B/op 24 allocs/op -``` - -### Custom sharder - -If the `HashingSharder` doesn't do what you need, any function that returns a `uint64` value will do. Integer-like keys are a great example of when a custom sharder may be appropriate. Just be aware something like "cast to `uint64`" means that any bias in common factors between your keyspace and the number of shards in the cache can result in uneven loading of the shards. - -## Conclusions about implementing sharding with Go generics - -While there is some real-world value to sharding a cache as we've done here, it's not something I need in any current project. The sharding here was intended to be an experiment on the use of generics in Go for something where the standard library couldn't help me. - -Just as the generic `lazylru.LazyLRU` implementation relies on a generic version of `containers/heap` that was copied from the standard library, sharding required a generic hasher to distribute keys among the shards. Total victory would have been to create a generic sharder that doesn't require any user-generated code to handle different key types, but has performance reasonably close to a custom, hand-tuned hasher for the given key type. I don't think we acheived that. - -Go's implementation for `map` is really the gold standard here. While `map` doesn't use generics, it does some compile-time tricks that are basically unavailable to library authors. Generics cover some of that ground, but there's a big gap left. It's conceivable that we could bridge that gap, but it would take a lot of code. - -Instead, I think we got about halfway there. The `HashingSharder` creates a mechanism for arbitrary key types to be used to shard, albeit with some end-user intervention. The value of generics there is that no casting or boxing is required to use the `HashingSharder`, and I'd argue it is a lot easier to use correctly than an analogous function written without generics. The performance is also excellent due to the power of `sync.Pool` to avoid allocations and the fantastic speed of [github.com/zeebo/xxh3](https://github.com/zeebo/xxh3). - -The other lesson here is that generics can spread across your API. In other languages with generics, this doesn't feel weird, but it does in Go. As an example, when .NET 2.0 brought generics to C#, it quickly became common to see generics everywhere, largely because the standard library in .NET 2.0 made broad use of generics. In Go, the compatibility promise means that the standard library is not going to be retrofit to support generics, nor should it. At least for now, it seems like generic code in Go is going to feel a little out-of-place, especially when the types cannot be inferred. - -Generics are not as powerful as C++ templates or generics in some other languages. Go's lack of polymorphism is enough to ensure that it probably never will be. The hasher here highlights that. Instead, it seems like Go generics are great when you put in things of type `T` and get things out of type `T`. Putting in `T` and getting out anything else, even a fixed type like `uint64` as we do here, is going to be a little messy unless there is an interface constraint you can rely on. - -## Performance - -The reason to shard is to reduce lock contention. However, that assumes that lock contention is the problem. The purpose of LazyLRU was to reduce exclusive locks, and thus lock contention. Benchmarks were run on my [laptop (8-core MacBook Pro M1 14" 2021)](benchmark_results_macbook_pro_m1_8.txt) and on a [Google Cloud N2 server (N2 "Ice Lake" 64 cores at 2.60GHz)](benchmark_results_n2-highcpu-64_64.txt). If we compare the unsharded vs. 64-way sharded performance with 1 thread and 64 threads, we should get some sense of the trade-offs. We will also compare oversized (guaranteed evictions), undersized (no evictions, no shuffles), and equal-sized (no evictions, shuffles) caches to see how the "lazyness" may save us some writes. Because the sharded caches use the regular LazyLRU behind the scenes, the capacity of each shard is `total/shard_count` so we aren't unfairly advantaging the sharded versions. - -All times in nanoseconds/operation, so lower is better. Mac testing was not done on a clean environment, so some variability is to be expected. The first set of numbers are from the Mac. The numbers after the gutter are from the server -- tables in markdown aren't great. - -### 100% writes - -| capacity | Shards | threads | unsharded | 16 shards | 64 shards | | unsharded | 16 shards | 64 shards | -| -------- | -----: | ------: | --------: | --------: | --------: |-| --------: | --------: | --------: | -| over | 1 | 1 | 103.0 | 126.6 | 108.9 | | 159.2 | 204.9 | 191.9 | -| over | 1 | 256 | 381.8 | 143.1 | 93.01 | | 228.8 | 89.90 | 35.65 | -| over | 1 | 65536 | 573.6 | 169.8 | 112.7 | | 223.5 | 121.5 | 40.94 | -| under | 1 | 1 | 318.4 | 294.0 | 214.9 | | 477.2 | 451.4 | 345.8 | -| under | 1 | 256 | 470.2 | 228.6 | 139.0 | | 581.4 | 154.8 | 49.18 | -| under | 1 | 65536 | 620.5 | 316.6 | 146.0 | | 606.8 | 157.4 | 61.02 | -| equal | 1 | 1 | 129.4 | 159.9 | 166.1 | | 210.4 | 260.2 | 269.5 | -| equal | 1 | 256 | 322.2 | 140.4 | 99.66 | | 367.0 | 81.49 | 26.67 | -| equal | 1 | 65536 | 553.8 | 229.6 | 122.9 | | 283.8 | 107.2 | 45.99 | - -### 1% writes, 99% reads - -| capacity | Shards | threads | unsharded | 16 shards | 64 shards | | unsharded | 16 shards | 64 shards | -| -------- | -----: | ------: | --------: | --------: | --------: |-| --------: | --------: | --------: | -| over | 1 | 1 | 72.66 | 104.9 | 89.66 | | 109.6 | 146.3 | 143.1 | -| over | 1 | 256 | 282.0 | 73.93 | 43.52 | | 185.8 | 39.78 | 17.44 | -| over | 1 | 65536 | 275.8 | 114.2 | 67.85 | | 134.4 | 43.88 | 20.08 | -| under | 1 | 1 | 63.50 | 84.80 | 65.50 | | 85.43 | 117.3 | 102.9 | -| under | 1 | 256 | 179.2 | 65.58 | 35.47 | | 176.0 | 30.77 | 10.58 | -| under | 1 | 65536 | 246.8 | 110.5 | 62.39 | | 144.3 | 37.23 | 25.61 | -| equal | 1 | 1 | 107.1 | 134.2 | 141.3 | | 167.0 | 203.0 | 206.7 | -| equal | 1 | 256 | 231.1 | 135.5 | 74.56 | | 422.5 | 73.29 | 30.92 | -| equal | 1 | 65536 | 302.9 | 228.0 | 138.0 | | 219.0 | 82.13 | 36.20 | - -This shows that sharding is somewhat effective when there are lots of exclusive locks due to writes and an abundance of threads that might block on those locks. It even looks like we could do even better with more shards. As expected, the 64-core server sees a bigger win from sharding than the 8-core Mac, though the Mac is 50% faster at running the hashing step. - -An additional learning from this testing was that the default source from [math/rand](https://pkg.go.dev/math/rand) is made thread-safe by locking on every call, which is its own source of contention. This overwhelmed the actual cache performance on the 64-core server, but was visible even on the 8-core Mac. Creating a new source for each worker thread eliminated that contention. As always, `go tool pprof` was awesome. diff --git a/generic/sharded/benchmark_results_macbook_pro_m1_8.txt b/generic/sharded/benchmark_results_macbook_pro_m1_8.txt deleted file mode 100644 index 3d29201..0000000 --- a/generic/sharded/benchmark_results_macbook_pro_m1_8.txt +++ /dev/null @@ -1,336 +0,0 @@ -goos: darwin -goarch: arm64 -pkg: github.com/TriggerMail/lazylru/generic/sharded -Benchmark/100W/0R/1S/1T_eqcap-8 10980187 102.8 ns/op -Benchmark/100W/0R/1S/16T_eqcap-8 3784981 326.8 ns/op -Benchmark/100W/0R/1S/256T_eqcap-8 2829307 386.5 ns/op -Benchmark/100W/0R/1S/65536T_eqcap-8 2094620 534.1 ns/op -Benchmark/100W/0R/4S/1T_eqcap-8 9101029 133.3 ns/op -Benchmark/100W/0R/4S/16T_eqcap-8 4629768 240.2 ns/op -Benchmark/100W/0R/4S/256T_eqcap-8 4349064 234.3 ns/op -Benchmark/100W/0R/4S/65536T_eqcap-8 3217366 326.6 ns/op -Benchmark/100W/0R/16S/1T_eqcap-8 8622939 140.1 ns/op -Benchmark/100W/0R/16S/16T_eqcap-8 8025259 153.2 ns/op -Benchmark/100W/0R/16S/256T_eqcap-8 7258987 158.6 ns/op -Benchmark/100W/0R/16S/65536T_eqcap-8 5226552 192.7 ns/op -Benchmark/100W/0R/64S/1T_eqcap-8 9199690 129.7 ns/op -Benchmark/100W/0R/64S/16T_eqcap-8 11097333 110.9 ns/op -Benchmark/100W/0R/64S/256T_eqcap-8 11089252 109.1 ns/op -Benchmark/100W/0R/64S/65536T_eqcap-8 8318820 144.6 ns/op -Benchmark/75W/25R/1S/1T_eqcap-8 10949587 108.8 ns/op -Benchmark/75W/25R/1S/16T_eqcap-8 6447008 192.1 ns/op -Benchmark/75W/25R/1S/256T_eqcap-8 6297391 186.3 ns/op -Benchmark/75W/25R/1S/65536T_eqcap-8 5010822 286.6 ns/op -Benchmark/75W/25R/4S/1T_eqcap-8 8981682 132.7 ns/op -Benchmark/75W/25R/4S/16T_eqcap-8 6839050 163.6 ns/op -Benchmark/75W/25R/4S/256T_eqcap-8 5737202 205.6 ns/op -Benchmark/75W/25R/4S/65536T_eqcap-8 3713520 306.4 ns/op -Benchmark/75W/25R/16S/1T_eqcap-8 8639401 138.3 ns/op -Benchmark/75W/25R/16S/16T_eqcap-8 8826640 138.6 ns/op -Benchmark/75W/25R/16S/256T_eqcap-8 6371859 182.8 ns/op -Benchmark/75W/25R/16S/65536T_eqcap-8 4982601 227.6 ns/op -Benchmark/75W/25R/64S/1T_eqcap-8 9747735 125.4 ns/op -Benchmark/75W/25R/64S/16T_eqcap-8 10865804 110.7 ns/op -Benchmark/75W/25R/64S/256T_eqcap-8 10581990 114.8 ns/op -Benchmark/75W/25R/64S/65536T_eqcap-8 7671223 145.0 ns/op -Benchmark/25W/75R/1S/1T_eqcap-8 13167886 91.40 ns/op -Benchmark/25W/75R/1S/16T_eqcap-8 7769541 154.2 ns/op -Benchmark/25W/75R/1S/256T_eqcap-8 7111741 166.0 ns/op -Benchmark/25W/75R/1S/65536T_eqcap-8 4574134 244.6 ns/op -Benchmark/25W/75R/4S/1T_eqcap-8 9596672 125.9 ns/op -Benchmark/25W/75R/4S/16T_eqcap-8 7940025 147.3 ns/op -Benchmark/25W/75R/4S/256T_eqcap-8 5743996 206.2 ns/op -Benchmark/25W/75R/4S/65536T_eqcap-8 2884527 350.4 ns/op -Benchmark/25W/75R/16S/1T_eqcap-8 9706758 122.6 ns/op -Benchmark/25W/75R/16S/16T_eqcap-8 10159431 114.1 ns/op -Benchmark/25W/75R/16S/256T_eqcap-8 7903621 151.0 ns/op -Benchmark/25W/75R/16S/65536T_eqcap-8 5755335 188.9 ns/op -Benchmark/25W/75R/64S/1T_eqcap-8 11620728 104.6 ns/op -Benchmark/25W/75R/64S/16T_eqcap-8 13300198 89.64 ns/op -Benchmark/25W/75R/64S/256T_eqcap-8 13361205 88.97 ns/op -Benchmark/25W/75R/64S/65536T_eqcap-8 9753144 115.2 ns/op -Benchmark/1W/99R/1S/1T_eqcap-8 14094501 84.92 ns/op -Benchmark/1W/99R/1S/16T_eqcap-8 7804942 151.0 ns/op -Benchmark/1W/99R/1S/256T_eqcap-8 6761940 176.8 ns/op -Benchmark/1W/99R/1S/65536T_eqcap-8 5599466 239.3 ns/op -Benchmark/1W/99R/4S/1T_eqcap-8 10320091 117.3 ns/op -Benchmark/1W/99R/4S/16T_eqcap-8 9401572 130.9 ns/op -Benchmark/1W/99R/4S/256T_eqcap-8 5189458 221.9 ns/op -Benchmark/1W/99R/4S/65536T_eqcap-8 4143156 271.0 ns/op -Benchmark/1W/99R/16S/1T_eqcap-8 10800294 110.4 ns/op -Benchmark/1W/99R/16S/16T_eqcap-8 11366878 102.0 ns/op -Benchmark/1W/99R/16S/256T_eqcap-8 9479412 126.0 ns/op -Benchmark/1W/99R/16S/65536T_eqcap-8 6223785 173.1 ns/op -Benchmark/1W/99R/64S/1T_eqcap-8 13623475 87.65 ns/op -Benchmark/1W/99R/64S/16T_eqcap-8 18289674 67.90 ns/op -Benchmark/1W/99R/64S/256T_eqcap-8 16238836 71.39 ns/op -Benchmark/1W/99R/64S/65536T_eqcap-8 10883954 111.3 ns/op -Benchmark/0W/100R/1S/1T_eqcap-8 14080312 86.21 ns/op -Benchmark/0W/100R/1S/16T_eqcap-8 7868106 151.0 ns/op -Benchmark/0W/100R/1S/256T_eqcap-8 6686202 178.0 ns/op -Benchmark/0W/100R/1S/65536T_eqcap-8 5583294 230.8 ns/op -Benchmark/0W/100R/4S/1T_eqcap-8 10555976 115.4 ns/op -Benchmark/0W/100R/4S/16T_eqcap-8 9366745 126.6 ns/op -Benchmark/0W/100R/4S/256T_eqcap-8 5037688 229.8 ns/op -Benchmark/0W/100R/4S/65536T_eqcap-8 3545570 297.4 ns/op -Benchmark/0W/100R/16S/1T_eqcap-8 10996093 108.8 ns/op -Benchmark/0W/100R/16S/16T_eqcap-8 12562785 101.3 ns/op -Benchmark/0W/100R/16S/256T_eqcap-8 9494709 124.2 ns/op -Benchmark/0W/100R/16S/65536T_eqcap-8 6350224 165.2 ns/op -Benchmark/0W/100R/64S/1T_eqcap-8 14154517 84.64 ns/op -Benchmark/0W/100R/64S/16T_eqcap-8 18357675 63.87 ns/op -Benchmark/0W/100R/64S/256T_eqcap-8 17627086 66.41 ns/op -Benchmark/0W/100R/64S/65536T_eqcap-8 10456092 101.0 ns/op -Benchmark/100W/0R/1S/1T_overcap-8 11432300 103.0 ns/op -Benchmark/100W/0R/1S/16T_overcap-8 4003344 297.3 ns/op -Benchmark/100W/0R/1S/256T_overcap-8 2665261 381.8 ns/op -Benchmark/100W/0R/1S/65536T_overcap-8 1812980 573.6 ns/op -Benchmark/100W/0R/4S/1T_overcap-8 9282524 128.9 ns/op -Benchmark/100W/0R/4S/16T_overcap-8 5026254 223.4 ns/op -Benchmark/100W/0R/4S/256T_overcap-8 5196283 235.4 ns/op -Benchmark/100W/0R/4S/65536T_overcap-8 3939276 275.8 ns/op -Benchmark/100W/0R/16S/1T_overcap-8 9482558 126.6 ns/op -Benchmark/100W/0R/16S/16T_overcap-8 8388940 140.6 ns/op -Benchmark/100W/0R/16S/256T_overcap-8 8157368 143.1 ns/op -Benchmark/100W/0R/16S/65536T_overcap-8 6507330 169.8 ns/op -Benchmark/100W/0R/64S/1T_overcap-8 11037454 108.9 ns/op -Benchmark/100W/0R/64S/16T_overcap-8 13023700 90.57 ns/op -Benchmark/100W/0R/64S/256T_overcap-8 12875869 93.01 ns/op -Benchmark/100W/0R/64S/65536T_overcap-8 10279410 112.7 ns/op -Benchmark/75W/25R/1S/1T_overcap-8 11189097 106.3 ns/op -Benchmark/75W/25R/1S/16T_overcap-8 6947432 172.3 ns/op -Benchmark/75W/25R/1S/256T_overcap-8 6631939 176.4 ns/op -Benchmark/75W/25R/1S/65536T_overcap-8 4609447 267.7 ns/op -Benchmark/75W/25R/4S/1T_overcap-8 9362266 128.4 ns/op -Benchmark/75W/25R/4S/16T_overcap-8 7544950 155.0 ns/op -Benchmark/75W/25R/4S/256T_overcap-8 4560762 234.5 ns/op -Benchmark/75W/25R/4S/65536T_overcap-8 3229886 332.8 ns/op -Benchmark/75W/25R/16S/1T_overcap-8 9455516 127.4 ns/op -Benchmark/75W/25R/16S/16T_overcap-8 10341502 117.4 ns/op -Benchmark/75W/25R/16S/256T_overcap-8 7064635 171.5 ns/op -Benchmark/75W/25R/16S/65536T_overcap-8 5279632 221.0 ns/op -Benchmark/75W/25R/64S/1T_overcap-8 11046481 108.4 ns/op -Benchmark/75W/25R/64S/16T_overcap-8 12575457 94.40 ns/op -Benchmark/75W/25R/64S/256T_overcap-8 11960013 97.53 ns/op -Benchmark/75W/25R/64S/65536T_overcap-8 8727750 128.4 ns/op -Benchmark/25W/75R/1S/1T_overcap-8 14702203 82.53 ns/op -Benchmark/25W/75R/1S/16T_overcap-8 8645846 135.7 ns/op -Benchmark/25W/75R/1S/256T_overcap-8 7558572 157.7 ns/op -Benchmark/25W/75R/1S/65536T_overcap-8 7557172 245.0 ns/op -Benchmark/25W/75R/4S/1T_overcap-8 10401928 117.1 ns/op -Benchmark/25W/75R/4S/16T_overcap-8 7343774 137.8 ns/op -Benchmark/25W/75R/4S/256T_overcap-8 5863146 203.2 ns/op -Benchmark/25W/75R/4S/65536T_overcap-8 3642111 295.2 ns/op -Benchmark/25W/75R/16S/1T_overcap-8 10431505 116.0 ns/op -Benchmark/25W/75R/16S/16T_overcap-8 10647985 108.2 ns/op -Benchmark/25W/75R/16S/256T_overcap-8 8978364 132.1 ns/op -Benchmark/25W/75R/16S/65536T_overcap-8 6584270 175.8 ns/op -Benchmark/25W/75R/64S/1T_overcap-8 12158546 98.77 ns/op -Benchmark/25W/75R/64S/16T_overcap-8 15702429 76.30 ns/op -Benchmark/25W/75R/64S/256T_overcap-8 15754456 76.58 ns/op -Benchmark/25W/75R/64S/65536T_overcap-8 11211652 102.5 ns/op -Benchmark/1W/99R/1S/1T_overcap-8 16893139 72.66 ns/op -Benchmark/1W/99R/1S/16T_overcap-8 8660935 157.7 ns/op -Benchmark/1W/99R/1S/256T_overcap-8 4234563 282.0 ns/op -Benchmark/1W/99R/1S/65536T_overcap-8 3806731 275.8 ns/op -Benchmark/1W/99R/4S/1T_overcap-8 11549270 103.6 ns/op -Benchmark/1W/99R/4S/16T_overcap-8 9959769 121.0 ns/op -Benchmark/1W/99R/4S/256T_overcap-8 7938478 150.4 ns/op -Benchmark/1W/99R/4S/65536T_overcap-8 5955622 172.8 ns/op -Benchmark/1W/99R/16S/1T_overcap-8 11422891 104.9 ns/op -Benchmark/1W/99R/16S/16T_overcap-8 19241085 62.92 ns/op -Benchmark/1W/99R/16S/256T_overcap-8 16757474 73.93 ns/op -Benchmark/1W/99R/16S/65536T_overcap-8 9159177 114.2 ns/op -Benchmark/1W/99R/64S/1T_overcap-8 13496060 89.66 ns/op -Benchmark/1W/99R/64S/16T_overcap-8 29133696 40.77 ns/op -Benchmark/1W/99R/64S/256T_overcap-8 27747295 43.52 ns/op -Benchmark/1W/99R/64S/65536T_overcap-8 16996104 67.85 ns/op -Benchmark/0W/100R/1S/1T_overcap-8 16798124 71.46 ns/op -Benchmark/0W/100R/1S/16T_overcap-8 5381670 241.8 ns/op -Benchmark/0W/100R/1S/256T_overcap-8 5043201 238.5 ns/op -Benchmark/0W/100R/1S/65536T_overcap-8 5489805 193.8 ns/op -Benchmark/0W/100R/4S/1T_overcap-8 11852598 101.7 ns/op -Benchmark/0W/100R/4S/16T_overcap-8 14477112 79.63 ns/op -Benchmark/0W/100R/4S/256T_overcap-8 14839408 80.28 ns/op -Benchmark/0W/100R/4S/65536T_overcap-8 14295012 82.68 ns/op -Benchmark/0W/100R/16S/1T_overcap-8 11802122 101.7 ns/op -Benchmark/0W/100R/16S/16T_overcap-8 26257008 43.93 ns/op -Benchmark/0W/100R/16S/256T_overcap-8 27041440 43.32 ns/op -Benchmark/0W/100R/16S/65536T_overcap-8 23559618 55.64 ns/op -Benchmark/0W/100R/64S/1T_overcap-8 13704717 87.36 ns/op -Benchmark/0W/100R/64S/16T_overcap-8 33571050 33.44 ns/op -Benchmark/0W/100R/64S/256T_overcap-8 34937676 33.36 ns/op -Benchmark/0W/100R/64S/65536T_overcap-8 28714323 38.45 ns/op -Benchmark/100W/0R/1S/1T_undercap-8 3722194 318.4 ns/op -Benchmark/100W/0R/1S/16T_undercap-8 2490330 441.1 ns/op -Benchmark/100W/0R/1S/256T_undercap-8 2402518 470.2 ns/op -Benchmark/100W/0R/1S/65536T_undercap-8 1903000 620.5 ns/op -Benchmark/100W/0R/4S/1T_undercap-8 3734510 319.5 ns/op -Benchmark/100W/0R/4S/16T_undercap-8 2616163 448.2 ns/op -Benchmark/100W/0R/4S/256T_undercap-8 3107865 427.2 ns/op -Benchmark/100W/0R/4S/65536T_undercap-8 2192397 517.1 ns/op -Benchmark/100W/0R/16S/1T_undercap-8 4048768 294.0 ns/op -Benchmark/100W/0R/16S/16T_undercap-8 5095898 234.8 ns/op -Benchmark/100W/0R/16S/256T_undercap-8 5196560 228.6 ns/op -Benchmark/100W/0R/16S/65536T_undercap-8 3354522 316.6 ns/op -Benchmark/100W/0R/64S/1T_undercap-8 5804509 214.9 ns/op -Benchmark/100W/0R/64S/16T_undercap-8 8984632 134.2 ns/op -Benchmark/100W/0R/64S/256T_undercap-8 9165376 139.0 ns/op -Benchmark/100W/0R/64S/65536T_undercap-8 7756608 146.0 ns/op -Benchmark/75W/25R/1S/1T_undercap-8 4553710 256.9 ns/op -Benchmark/75W/25R/1S/16T_undercap-8 3413487 359.6 ns/op -Benchmark/75W/25R/1S/256T_undercap-8 3131407 389.1 ns/op -Benchmark/75W/25R/1S/65536T_undercap-8 2367362 540.7 ns/op -Benchmark/75W/25R/4S/1T_undercap-8 4581457 267.1 ns/op -Benchmark/75W/25R/4S/16T_undercap-8 3961592 277.8 ns/op -Benchmark/75W/25R/4S/256T_undercap-8 2544501 469.4 ns/op -Benchmark/75W/25R/4S/65536T_undercap-8 1889661 588.2 ns/op -Benchmark/75W/25R/16S/1T_undercap-8 4958994 246.5 ns/op -Benchmark/75W/25R/16S/16T_undercap-8 5498210 217.2 ns/op -Benchmark/75W/25R/16S/256T_undercap-8 5150932 236.0 ns/op -Benchmark/75W/25R/16S/65536T_undercap-8 2974658 355.2 ns/op -Benchmark/75W/25R/64S/1T_undercap-8 6749450 174.6 ns/op -Benchmark/75W/25R/64S/16T_undercap-8 9742977 125.8 ns/op -Benchmark/75W/25R/64S/256T_undercap-8 10131084 126.8 ns/op -Benchmark/75W/25R/64S/65536T_undercap-8 5036461 200.5 ns/op -Benchmark/25W/75R/1S/1T_undercap-8 9503638 129.8 ns/op -Benchmark/25W/75R/1S/16T_undercap-8 6454201 189.4 ns/op -Benchmark/25W/75R/1S/256T_undercap-8 5331349 234.3 ns/op -Benchmark/25W/75R/1S/65536T_undercap-8 3704707 333.0 ns/op -Benchmark/25W/75R/4S/1T_undercap-8 8147097 151.3 ns/op -Benchmark/25W/75R/4S/16T_undercap-8 6683844 182.9 ns/op -Benchmark/25W/75R/4S/256T_undercap-8 3766495 321.7 ns/op -Benchmark/25W/75R/4S/65536T_undercap-8 2669346 423.3 ns/op -Benchmark/25W/75R/16S/1T_undercap-8 8580782 143.8 ns/op -Benchmark/25W/75R/16S/16T_undercap-8 8385248 149.1 ns/op -Benchmark/25W/75R/16S/256T_undercap-8 7795497 154.6 ns/op -Benchmark/25W/75R/16S/65536T_undercap-8 4453263 240.5 ns/op -Benchmark/25W/75R/64S/1T_undercap-8 11023635 105.6 ns/op -Benchmark/25W/75R/64S/16T_undercap-8 16151365 80.32 ns/op -Benchmark/25W/75R/64S/256T_undercap-8 15041174 82.06 ns/op -Benchmark/25W/75R/64S/65536T_undercap-8 7313824 138.7 ns/op -Benchmark/1W/99R/1S/1T_undercap-8 18691988 63.50 ns/op -Benchmark/1W/99R/1S/16T_undercap-8 11502574 93.32 ns/op -Benchmark/1W/99R/1S/256T_undercap-8 6127444 179.2 ns/op -Benchmark/1W/99R/1S/65536T_undercap-8 4523686 246.8 ns/op -Benchmark/1W/99R/4S/1T_undercap-8 14018022 86.28 ns/op -Benchmark/1W/99R/4S/16T_undercap-8 10705495 113.8 ns/op -Benchmark/1W/99R/4S/256T_undercap-8 8454492 134.7 ns/op -Benchmark/1W/99R/4S/65536T_undercap-8 5843305 183.6 ns/op -Benchmark/1W/99R/16S/1T_undercap-8 14242999 84.80 ns/op -Benchmark/1W/99R/16S/16T_undercap-8 19951298 59.51 ns/op -Benchmark/1W/99R/16S/256T_undercap-8 17181760 65.58 ns/op -Benchmark/1W/99R/16S/65536T_undercap-8 10437219 110.5 ns/op -Benchmark/1W/99R/64S/1T_undercap-8 18329121 65.50 ns/op -Benchmark/1W/99R/64S/16T_undercap-8 35469290 33.45 ns/op -Benchmark/1W/99R/64S/256T_undercap-8 34617754 35.47 ns/op -Benchmark/1W/99R/64S/65536T_undercap-8 16092235 62.39 ns/op -Benchmark/0W/100R/1S/1T_undercap-8 35094956 42.39 ns/op -Benchmark/0W/100R/1S/16T_undercap-8 8125663 166.9 ns/op -Benchmark/0W/100R/1S/256T_undercap-8 4590606 254.3 ns/op -Benchmark/0W/100R/1S/65536T_undercap-8 5103334 278.6 ns/op -Benchmark/0W/100R/4S/1T_undercap-8 19466074 61.39 ns/op -Benchmark/0W/100R/4S/16T_undercap-8 15381820 80.37 ns/op -Benchmark/0W/100R/4S/256T_undercap-8 11186763 108.0 ns/op -Benchmark/0W/100R/4S/65536T_undercap-8 7497133 139.1 ns/op -Benchmark/0W/100R/16S/1T_undercap-8 18568089 63.92 ns/op -Benchmark/0W/100R/16S/16T_undercap-8 28621117 41.24 ns/op -Benchmark/0W/100R/16S/256T_undercap-8 25830198 46.39 ns/op -Benchmark/0W/100R/16S/65536T_undercap-8 15185415 81.06 ns/op -Benchmark/0W/100R/64S/1T_undercap-8 22739582 52.47 ns/op -Benchmark/0W/100R/64S/16T_undercap-8 43889124 27.54 ns/op -Benchmark/0W/100R/64S/256T_undercap-8 42663442 27.64 ns/op -Benchmark/0W/100R/64S/65536T_undercap-8 27918279 42.04 ns/op -Benchmark/100W/0R/1S/1T_eqcap#01-8 9099370 129.4 ns/op -Benchmark/100W/0R/1S/16T_eqcap#01-8 3397436 305.4 ns/op -Benchmark/100W/0R/1S/256T_eqcap#01-8 3839294 322.2 ns/op -Benchmark/100W/0R/1S/65536T_eqcap#01-8 1883941 553.8 ns/op -Benchmark/100W/0R/4S/1T_eqcap#01-8 7437345 157.2 ns/op -Benchmark/100W/0R/4S/16T_eqcap#01-8 4761716 253.9 ns/op -Benchmark/100W/0R/4S/256T_eqcap#01-8 4844149 240.4 ns/op -Benchmark/100W/0R/4S/65536T_eqcap#01-8 2943500 363.8 ns/op -Benchmark/100W/0R/16S/1T_eqcap#01-8 7610894 159.9 ns/op -Benchmark/100W/0R/16S/16T_eqcap#01-8 8060462 142.5 ns/op -Benchmark/100W/0R/16S/256T_eqcap#01-8 7982053 140.4 ns/op -Benchmark/100W/0R/16S/65536T_eqcap#01-8 5355632 229.6 ns/op -Benchmark/100W/0R/64S/1T_eqcap#01-8 7333929 166.1 ns/op -Benchmark/100W/0R/64S/16T_eqcap#01-8 11796808 100.6 ns/op -Benchmark/100W/0R/64S/256T_eqcap#01-8 11949309 99.66 ns/op -Benchmark/100W/0R/64S/65536T_eqcap#01-8 9032144 122.9 ns/op -Benchmark/75W/25R/1S/1T_eqcap#01-8 9390142 129.5 ns/op -Benchmark/75W/25R/1S/16T_eqcap#01-8 5614596 194.3 ns/op -Benchmark/75W/25R/1S/256T_eqcap#01-8 5860246 200.5 ns/op -Benchmark/75W/25R/1S/65536T_eqcap#01-8 4581031 329.3 ns/op -Benchmark/75W/25R/4S/1T_eqcap#01-8 7777082 158.1 ns/op -Benchmark/75W/25R/4S/16T_eqcap#01-8 7059588 170.9 ns/op -Benchmark/75W/25R/4S/256T_eqcap#01-8 3072578 371.1 ns/op -Benchmark/75W/25R/4S/65536T_eqcap#01-8 2076680 539.6 ns/op -Benchmark/75W/25R/16S/1T_eqcap#01-8 7706112 158.8 ns/op -Benchmark/75W/25R/16S/16T_eqcap#01-8 7506301 162.5 ns/op -Benchmark/75W/25R/16S/256T_eqcap#01-8 6596586 176.5 ns/op -Benchmark/75W/25R/16S/65536T_eqcap#01-8 3685719 299.4 ns/op -Benchmark/75W/25R/64S/1T_eqcap#01-8 7566801 163.6 ns/op -Benchmark/75W/25R/64S/16T_eqcap#01-8 11558053 103.6 ns/op -Benchmark/75W/25R/64S/256T_eqcap#01-8 10626102 106.1 ns/op -Benchmark/75W/25R/64S/65536T_eqcap#01-8 6438658 208.2 ns/op -Benchmark/25W/75R/1S/1T_eqcap#01-8 10787486 111.7 ns/op -Benchmark/25W/75R/1S/16T_eqcap#01-8 6926718 169.5 ns/op -Benchmark/25W/75R/1S/256T_eqcap#01-8 6323166 186.0 ns/op -Benchmark/25W/75R/1S/65536T_eqcap#01-8 4751889 324.4 ns/op -Benchmark/25W/75R/4S/1T_eqcap#01-8 8353699 147.7 ns/op -Benchmark/25W/75R/4S/16T_eqcap#01-8 7399790 155.7 ns/op -Benchmark/25W/75R/4S/256T_eqcap#01-8 3638529 325.3 ns/op -Benchmark/25W/75R/4S/65536T_eqcap#01-8 2436922 453.0 ns/op -Benchmark/25W/75R/16S/1T_eqcap#01-8 7943362 146.9 ns/op -Benchmark/25W/75R/16S/16T_eqcap#01-8 8406228 140.4 ns/op -Benchmark/25W/75R/16S/256T_eqcap#01-8 7626820 153.5 ns/op -Benchmark/25W/75R/16S/65536T_eqcap#01-8 4136876 275.6 ns/op -Benchmark/25W/75R/64S/1T_eqcap#01-8 8073436 147.0 ns/op -Benchmark/25W/75R/64S/16T_eqcap#01-8 13372242 88.58 ns/op -Benchmark/25W/75R/64S/256T_eqcap#01-8 13543100 87.89 ns/op -Benchmark/25W/75R/64S/65536T_eqcap#01-8 6453088 191.4 ns/op -Benchmark/1W/99R/1S/1T_eqcap#01-8 10856826 107.1 ns/op -Benchmark/1W/99R/1S/16T_eqcap#01-8 6760134 174.8 ns/op -Benchmark/1W/99R/1S/256T_eqcap#01-8 4537556 231.1 ns/op -Benchmark/1W/99R/1S/65536T_eqcap#01-8 4239950 302.9 ns/op -Benchmark/1W/99R/4S/1T_eqcap#01-8 8947846 134.1 ns/op -Benchmark/1W/99R/4S/16T_eqcap#01-8 7937458 142.9 ns/op -Benchmark/1W/99R/4S/256T_eqcap#01-8 4093225 294.5 ns/op -Benchmark/1W/99R/4S/65536T_eqcap#01-8 3019171 374.4 ns/op -Benchmark/1W/99R/16S/1T_eqcap#01-8 8975750 134.2 ns/op -Benchmark/1W/99R/16S/16T_eqcap#01-8 9092570 128.5 ns/op -Benchmark/1W/99R/16S/256T_eqcap#01-8 8226110 135.5 ns/op -Benchmark/1W/99R/16S/65536T_eqcap#01-8 5024106 228.0 ns/op -Benchmark/1W/99R/64S/1T_eqcap#01-8 8715951 141.3 ns/op -Benchmark/1W/99R/64S/16T_eqcap#01-8 15987103 72.83 ns/op -Benchmark/1W/99R/64S/256T_eqcap#01-8 16361785 74.56 ns/op -Benchmark/1W/99R/64S/65536T_eqcap#01-8 7836910 138.0 ns/op -Benchmark/0W/100R/1S/1T_eqcap#01-8 10744960 107.0 ns/op -Benchmark/0W/100R/1S/16T_eqcap#01-8 6129645 174.2 ns/op -Benchmark/0W/100R/1S/256T_eqcap#01-8 6108204 210.4 ns/op -Benchmark/0W/100R/1S/65536T_eqcap#01-8 4963960 304.3 ns/op -Benchmark/0W/100R/4S/1T_eqcap#01-8 8661283 132.6 ns/op -Benchmark/0W/100R/4S/16T_eqcap#01-8 7978723 145.3 ns/op -Benchmark/0W/100R/4S/256T_eqcap#01-8 4016164 297.9 ns/op -Benchmark/0W/100R/4S/65536T_eqcap#01-8 3086778 373.1 ns/op -Benchmark/0W/100R/16S/1T_eqcap#01-8 8347053 133.9 ns/op -Benchmark/0W/100R/16S/16T_eqcap#01-8 9153046 131.3 ns/op -Benchmark/0W/100R/16S/256T_eqcap#01-8 8688595 138.3 ns/op -Benchmark/0W/100R/16S/65536T_eqcap#01-8 4657918 228.8 ns/op -Benchmark/0W/100R/64S/1T_eqcap#01-8 9083070 134.0 ns/op -Benchmark/0W/100R/64S/16T_eqcap#01-8 16189888 72.23 ns/op -Benchmark/0W/100R/64S/256T_eqcap#01-8 15963726 74.52 ns/op -Benchmark/0W/100R/64S/65536T_eqcap#01-8 8469031 138.4 ns/op -BenchmarkStringSharder/1-8 55692421 21.73 ns/op -BenchmarkStringSharder/4-8 57087696 20.92 ns/op -BenchmarkStringSharder/16-8 59222823 20.28 ns/op -BenchmarkStringSharder/64-8 53906943 22.24 ns/op -BenchmarkStringSharder/256-8 26144620 45.83 ns/op -BenchmarkBytesSharder/1-8 54748686 21.92 ns/op -BenchmarkBytesSharder/4-8 56411852 21.20 ns/op -BenchmarkBytesSharder/16-8 58202982 20.64 ns/op -BenchmarkBytesSharder/64-8 53351709 22.50 ns/op -BenchmarkBytesSharder/256-8 26132665 45.90 ns/op -BenchmarkCustomSharder-8 22613118 53.01 ns/op -PASS -ok github.com/TriggerMail/lazylru/generic/sharded 681.290s diff --git a/generic/sharded/benchmark_results_n2-highcpu-64_64.txt b/generic/sharded/benchmark_results_n2-highcpu-64_64.txt deleted file mode 100644 index 86eb86d..0000000 --- a/generic/sharded/benchmark_results_n2-highcpu-64_64.txt +++ /dev/null @@ -1,337 +0,0 @@ -goos: linux -goarch: amd64 -pkg: github.com/TriggerMail/lazylru/generic/sharded -cpu: Intel(R) Xeon(R) CPU @ 2.60GHz -Benchmark/100W/0R/1S/1T_eqcap-64 7455988 159.7 ns/op -Benchmark/100W/0R/1S/16T_eqcap-64 5907079 198.8 ns/op -Benchmark/100W/0R/1S/256T_eqcap-64 5292556 230.9 ns/op -Benchmark/100W/0R/1S/65536T_eqcap-64 5048348 225.4 ns/op -Benchmark/100W/0R/4S/1T_eqcap-64 4822468 211.9 ns/op -Benchmark/100W/0R/4S/16T_eqcap-64 4748713 265.6 ns/op -Benchmark/100W/0R/4S/256T_eqcap-64 4629442 259.1 ns/op -Benchmark/100W/0R/4S/65536T_eqcap-64 3644557 317.3 ns/op -Benchmark/100W/0R/16S/1T_eqcap-64 4539686 235.3 ns/op -Benchmark/100W/0R/16S/16T_eqcap-64 9693202 123.9 ns/op -Benchmark/100W/0R/16S/256T_eqcap-64 10310104 115.2 ns/op -Benchmark/100W/0R/16S/65536T_eqcap-64 7879351 150.2 ns/op -Benchmark/100W/0R/64S/1T_eqcap-64 4578079 226.3 ns/op -Benchmark/100W/0R/64S/16T_eqcap-64 17822827 64.77 ns/op -Benchmark/100W/0R/64S/256T_eqcap-64 24247794 50.24 ns/op -Benchmark/100W/0R/64S/65536T_eqcap-64 19303261 53.79 ns/op -Benchmark/75W/25R/1S/1T_eqcap-64 7664067 156.8 ns/op -Benchmark/75W/25R/1S/16T_eqcap-64 6343914 192.3 ns/op -Benchmark/75W/25R/1S/256T_eqcap-64 5441010 219.1 ns/op -Benchmark/75W/25R/1S/65536T_eqcap-64 5467110 210.9 ns/op -Benchmark/75W/25R/4S/1T_eqcap-64 5856294 206.7 ns/op -Benchmark/75W/25R/4S/16T_eqcap-64 4394935 282.8 ns/op -Benchmark/75W/25R/4S/256T_eqcap-64 4033884 303.2 ns/op -Benchmark/75W/25R/4S/65536T_eqcap-64 3253754 312.3 ns/op -Benchmark/75W/25R/16S/1T_eqcap-64 4750406 215.1 ns/op -Benchmark/75W/25R/16S/16T_eqcap-64 6609440 185.6 ns/op -Benchmark/75W/25R/16S/256T_eqcap-64 8059749 148.7 ns/op -Benchmark/75W/25R/16S/65536T_eqcap-64 5751169 184.2 ns/op -Benchmark/75W/25R/64S/1T_eqcap-64 4856647 210.9 ns/op -Benchmark/75W/25R/64S/16T_eqcap-64 10301136 118.4 ns/op -Benchmark/75W/25R/64S/256T_eqcap-64 17804900 69.37 ns/op -Benchmark/75W/25R/64S/65536T_eqcap-64 15753525 73.08 ns/op -Benchmark/25W/75R/1S/1T_eqcap-64 7038086 142.6 ns/op -Benchmark/25W/75R/1S/16T_eqcap-64 7097943 173.7 ns/op -Benchmark/25W/75R/1S/256T_eqcap-64 5103567 241.2 ns/op -Benchmark/25W/75R/1S/65536T_eqcap-64 6278612 183.4 ns/op -Benchmark/25W/75R/4S/1T_eqcap-64 5532499 190.0 ns/op -Benchmark/25W/75R/4S/16T_eqcap-64 4805125 249.9 ns/op -Benchmark/25W/75R/4S/256T_eqcap-64 4392862 270.7 ns/op -Benchmark/25W/75R/4S/65536T_eqcap-64 4099972 267.3 ns/op -Benchmark/25W/75R/16S/1T_eqcap-64 5580859 182.8 ns/op -Benchmark/25W/75R/16S/16T_eqcap-64 7573144 156.2 ns/op -Benchmark/25W/75R/16S/256T_eqcap-64 9542770 121.9 ns/op -Benchmark/25W/75R/16S/65536T_eqcap-64 8955052 136.8 ns/op -Benchmark/25W/75R/64S/1T_eqcap-64 6007238 168.1 ns/op -Benchmark/25W/75R/64S/16T_eqcap-64 12231644 97.74 ns/op -Benchmark/25W/75R/64S/256T_eqcap-64 22479620 50.85 ns/op -Benchmark/25W/75R/64S/65536T_eqcap-64 19019818 56.54 ns/op -Benchmark/1W/99R/1S/1T_eqcap-64 9219552 130.3 ns/op -Benchmark/1W/99R/1S/16T_eqcap-64 6588214 193.1 ns/op -Benchmark/1W/99R/1S/256T_eqcap-64 4362810 291.3 ns/op -Benchmark/1W/99R/1S/65536T_eqcap-64 7534992 154.0 ns/op -Benchmark/1W/99R/4S/1T_eqcap-64 6029914 168.5 ns/op -Benchmark/1W/99R/4S/16T_eqcap-64 5228506 226.8 ns/op -Benchmark/1W/99R/4S/256T_eqcap-64 5278444 225.6 ns/op -Benchmark/1W/99R/4S/65536T_eqcap-64 5211552 227.4 ns/op -Benchmark/1W/99R/16S/1T_eqcap-64 6343903 159.4 ns/op -Benchmark/1W/99R/16S/16T_eqcap-64 10183288 118.6 ns/op -Benchmark/1W/99R/16S/256T_eqcap-64 14617513 75.02 ns/op -Benchmark/1W/99R/16S/65536T_eqcap-64 13453840 84.59 ns/op -Benchmark/1W/99R/64S/1T_eqcap-64 8469181 140.7 ns/op -Benchmark/1W/99R/64S/16T_eqcap-64 17810095 66.70 ns/op -Benchmark/1W/99R/64S/256T_eqcap-64 40419390 27.67 ns/op -Benchmark/1W/99R/64S/65536T_eqcap-64 34158451 36.39 ns/op -Benchmark/0W/100R/1S/1T_eqcap-64 9588390 125.6 ns/op -Benchmark/0W/100R/1S/16T_eqcap-64 6558649 182.9 ns/op -Benchmark/0W/100R/1S/256T_eqcap-64 4528744 282.4 ns/op -Benchmark/0W/100R/1S/65536T_eqcap-64 7640264 152.6 ns/op -Benchmark/0W/100R/4S/1T_eqcap-64 6188918 164.6 ns/op -Benchmark/0W/100R/4S/16T_eqcap-64 5268746 222.9 ns/op -Benchmark/0W/100R/4S/256T_eqcap-64 5451903 216.9 ns/op -Benchmark/0W/100R/4S/65536T_eqcap-64 5279671 228.4 ns/op -Benchmark/0W/100R/16S/1T_eqcap-64 6501595 163.7 ns/op -Benchmark/0W/100R/16S/16T_eqcap-64 10364494 117.3 ns/op -Benchmark/0W/100R/16S/256T_eqcap-64 15794884 73.55 ns/op -Benchmark/0W/100R/16S/65536T_eqcap-64 14675685 79.63 ns/op -Benchmark/0W/100R/64S/1T_eqcap-64 8549598 141.6 ns/op -Benchmark/0W/100R/64S/16T_eqcap-64 18289982 62.91 ns/op -Benchmark/0W/100R/64S/256T_eqcap-64 42590642 27.11 ns/op -Benchmark/0W/100R/64S/65536T_eqcap-64 36670705 31.91 ns/op -Benchmark/100W/0R/1S/1T_overcap-64 6417517 159.2 ns/op -Benchmark/100W/0R/1S/16T_overcap-64 6094902 197.0 ns/op -Benchmark/100W/0R/1S/256T_overcap-64 5384846 228.8 ns/op -Benchmark/100W/0R/1S/65536T_overcap-64 4501941 223.5 ns/op -Benchmark/100W/0R/4S/1T_overcap-64 5189439 201.8 ns/op -Benchmark/100W/0R/4S/16T_overcap-64 4936912 240.0 ns/op -Benchmark/100W/0R/4S/256T_overcap-64 4953662 238.8 ns/op -Benchmark/100W/0R/4S/65536T_overcap-64 4319127 273.6 ns/op -Benchmark/100W/0R/16S/1T_overcap-64 5143477 204.9 ns/op -Benchmark/100W/0R/16S/16T_overcap-64 12175879 96.60 ns/op -Benchmark/100W/0R/16S/256T_overcap-64 13299685 89.90 ns/op -Benchmark/100W/0R/16S/65536T_overcap-64 9372346 121.5 ns/op -Benchmark/100W/0R/64S/1T_overcap-64 5341560 191.9 ns/op -Benchmark/100W/0R/64S/16T_overcap-64 23905484 47.95 ns/op -Benchmark/100W/0R/64S/256T_overcap-64 32534590 35.65 ns/op -Benchmark/100W/0R/64S/65536T_overcap-64 27185197 40.94 ns/op -Benchmark/75W/25R/1S/1T_overcap-64 7858790 153.8 ns/op -Benchmark/75W/25R/1S/16T_overcap-64 6507825 184.8 ns/op -Benchmark/75W/25R/1S/256T_overcap-64 5738199 223.0 ns/op -Benchmark/75W/25R/1S/65536T_overcap-64 5668519 196.7 ns/op -Benchmark/75W/25R/4S/1T_overcap-64 5399977 192.7 ns/op -Benchmark/75W/25R/4S/16T_overcap-64 4720045 267.4 ns/op -Benchmark/75W/25R/4S/256T_overcap-64 4176498 277.4 ns/op -Benchmark/75W/25R/4S/65536T_overcap-64 3433419 311.2 ns/op -Benchmark/75W/25R/16S/1T_overcap-64 5253060 197.0 ns/op -Benchmark/75W/25R/16S/16T_overcap-64 7177554 163.7 ns/op -Benchmark/75W/25R/16S/256T_overcap-64 9824901 119.2 ns/op -Benchmark/75W/25R/16S/65536T_overcap-64 6747517 154.1 ns/op -Benchmark/75W/25R/64S/1T_overcap-64 5475608 185.1 ns/op -Benchmark/75W/25R/64S/16T_overcap-64 11448162 103.9 ns/op -Benchmark/75W/25R/64S/256T_overcap-64 21002736 55.71 ns/op -Benchmark/75W/25R/64S/65536T_overcap-64 19343012 59.17 ns/op -Benchmark/25W/75R/1S/1T_overcap-64 8039840 125.6 ns/op -Benchmark/25W/75R/1S/16T_overcap-64 7524931 167.8 ns/op -Benchmark/25W/75R/1S/256T_overcap-64 4850719 274.6 ns/op -Benchmark/25W/75R/1S/65536T_overcap-64 6263556 166.3 ns/op -Benchmark/25W/75R/4S/1T_overcap-64 6197197 163.4 ns/op -Benchmark/25W/75R/4S/16T_overcap-64 5520657 215.5 ns/op -Benchmark/25W/75R/4S/256T_overcap-64 5305041 232.6 ns/op -Benchmark/25W/75R/4S/65536T_overcap-64 5045961 228.6 ns/op -Benchmark/25W/75R/16S/1T_overcap-64 6044812 165.9 ns/op -Benchmark/25W/75R/16S/16T_overcap-64 9197595 126.0 ns/op -Benchmark/25W/75R/16S/256T_overcap-64 11207336 105.5 ns/op -Benchmark/25W/75R/16S/65536T_overcap-64 10358186 114.5 ns/op -Benchmark/25W/75R/64S/1T_overcap-64 6038881 166.9 ns/op -Benchmark/25W/75R/64S/16T_overcap-64 13812106 84.78 ns/op -Benchmark/25W/75R/64S/256T_overcap-64 23431197 48.84 ns/op -Benchmark/25W/75R/64S/65536T_overcap-64 20621446 51.50 ns/op -Benchmark/1W/99R/1S/1T_overcap-64 10868082 109.6 ns/op -Benchmark/1W/99R/1S/16T_overcap-64 7818691 155.9 ns/op -Benchmark/1W/99R/1S/256T_overcap-64 6477452 185.8 ns/op -Benchmark/1W/99R/1S/65536T_overcap-64 8376778 134.4 ns/op -Benchmark/1W/99R/4S/1T_overcap-64 7089561 153.6 ns/op -Benchmark/1W/99R/4S/16T_overcap-64 14243436 84.56 ns/op -Benchmark/1W/99R/4S/256T_overcap-64 12078002 99.64 ns/op -Benchmark/1W/99R/4S/65536T_overcap-64 13113555 100.1 ns/op -Benchmark/1W/99R/16S/1T_overcap-64 6924524 146.3 ns/op -Benchmark/1W/99R/16S/16T_overcap-64 22271806 52.44 ns/op -Benchmark/1W/99R/16S/256T_overcap-64 29091061 39.78 ns/op -Benchmark/1W/99R/16S/65536T_overcap-64 28418948 43.88 ns/op -Benchmark/1W/99R/64S/1T_overcap-64 8437662 143.1 ns/op -Benchmark/1W/99R/64S/16T_overcap-64 32032180 34.52 ns/op -Benchmark/1W/99R/64S/256T_overcap-64 64156592 17.44 ns/op -Benchmark/1W/99R/64S/65536T_overcap-64 52098568 20.08 ns/op -Benchmark/0W/100R/1S/1T_overcap-64 11654509 101.0 ns/op -Benchmark/0W/100R/1S/16T_overcap-64 11795619 101.1 ns/op -Benchmark/0W/100R/1S/256T_overcap-64 15232983 76.74 ns/op -Benchmark/0W/100R/1S/65536T_overcap-64 14269597 78.05 ns/op -Benchmark/0W/100R/4S/1T_overcap-64 8252636 148.0 ns/op -Benchmark/0W/100R/4S/16T_overcap-64 24195858 49.36 ns/op -Benchmark/0W/100R/4S/256T_overcap-64 26229447 46.88 ns/op -Benchmark/0W/100R/4S/65536T_overcap-64 24391197 52.10 ns/op -Benchmark/0W/100R/16S/1T_overcap-64 8412134 148.3 ns/op -Benchmark/0W/100R/16S/16T_overcap-64 41820062 25.52 ns/op -Benchmark/0W/100R/16S/256T_overcap-64 53592946 22.15 ns/op -Benchmark/0W/100R/16S/65536T_overcap-64 43359944 29.84 ns/op -Benchmark/0W/100R/64S/1T_overcap-64 8112162 144.5 ns/op -Benchmark/0W/100R/64S/16T_overcap-64 76081896 15.62 ns/op -Benchmark/0W/100R/64S/256T_overcap-64 123626461 9.653 ns/op -Benchmark/0W/100R/64S/65536T_overcap-64 109681065 14.21 ns/op -Benchmark/100W/0R/1S/1T_undercap-64 2497064 477.2 ns/op -Benchmark/100W/0R/1S/16T_undercap-64 2194178 549.0 ns/op -Benchmark/100W/0R/1S/256T_undercap-64 2096997 581.4 ns/op -Benchmark/100W/0R/1S/65536T_undercap-64 1876206 606.8 ns/op -Benchmark/100W/0R/4S/1T_undercap-64 2350129 491.8 ns/op -Benchmark/100W/0R/4S/16T_undercap-64 2576154 472.8 ns/op -Benchmark/100W/0R/4S/256T_undercap-64 2625927 443.6 ns/op -Benchmark/100W/0R/4S/65536T_undercap-64 2392376 484.2 ns/op -Benchmark/100W/0R/16S/1T_undercap-64 2485603 451.4 ns/op -Benchmark/100W/0R/16S/16T_undercap-64 6431912 181.6 ns/op -Benchmark/100W/0R/16S/256T_undercap-64 8245759 154.8 ns/op -Benchmark/100W/0R/16S/65536T_undercap-64 7450460 157.4 ns/op -Benchmark/100W/0R/64S/1T_undercap-64 3175782 345.8 ns/op -Benchmark/100W/0R/64S/16T_undercap-64 13458570 87.31 ns/op -Benchmark/100W/0R/64S/256T_undercap-64 21878200 49.18 ns/op -Benchmark/100W/0R/64S/65536T_undercap-64 19652800 61.02 ns/op -Benchmark/75W/25R/1S/1T_undercap-64 2768404 386.4 ns/op -Benchmark/75W/25R/1S/16T_undercap-64 2750817 460.0 ns/op -Benchmark/75W/25R/1S/256T_undercap-64 2410812 502.0 ns/op -Benchmark/75W/25R/1S/65536T_undercap-64 2195251 504.1 ns/op -Benchmark/75W/25R/4S/1T_undercap-64 2749435 407.3 ns/op -Benchmark/75W/25R/4S/16T_undercap-64 2538493 468.9 ns/op -Benchmark/75W/25R/4S/256T_undercap-64 2876186 426.1 ns/op -Benchmark/75W/25R/4S/65536T_undercap-64 2509060 464.9 ns/op -Benchmark/75W/25R/16S/1T_undercap-64 2963498 373.6 ns/op -Benchmark/75W/25R/16S/16T_undercap-64 5270922 228.6 ns/op -Benchmark/75W/25R/16S/256T_undercap-64 8825030 136.4 ns/op -Benchmark/75W/25R/16S/65536T_undercap-64 7180467 149.7 ns/op -Benchmark/75W/25R/64S/1T_undercap-64 3636529 295.0 ns/op -Benchmark/75W/25R/64S/16T_undercap-64 11006620 116.2 ns/op -Benchmark/75W/25R/64S/256T_undercap-64 25512924 50.80 ns/op -Benchmark/75W/25R/64S/65536T_undercap-64 21477668 58.32 ns/op -Benchmark/25W/75R/1S/1T_undercap-64 5299102 189.0 ns/op -Benchmark/25W/75R/1S/16T_undercap-64 5156736 235.8 ns/op -Benchmark/25W/75R/1S/256T_undercap-64 3320425 383.8 ns/op -Benchmark/25W/75R/1S/65536T_undercap-64 4084995 254.5 ns/op -Benchmark/25W/75R/4S/1T_undercap-64 5299771 218.3 ns/op -Benchmark/25W/75R/4S/16T_undercap-64 4137565 288.1 ns/op -Benchmark/25W/75R/4S/256T_undercap-64 4670262 256.3 ns/op -Benchmark/25W/75R/4S/65536T_undercap-64 4062151 287.2 ns/op -Benchmark/25W/75R/16S/1T_undercap-64 4967277 210.9 ns/op -Benchmark/25W/75R/16S/16T_undercap-64 9539822 130.0 ns/op -Benchmark/25W/75R/16S/256T_undercap-64 15223117 81.36 ns/op -Benchmark/25W/75R/16S/65536T_undercap-64 11065038 93.57 ns/op -Benchmark/25W/75R/64S/1T_undercap-64 7122370 168.5 ns/op -Benchmark/25W/75R/64S/16T_undercap-64 18431936 67.92 ns/op -Benchmark/25W/75R/64S/256T_undercap-64 36079814 36.25 ns/op -Benchmark/25W/75R/64S/65536T_undercap-64 28462555 41.59 ns/op -Benchmark/1W/99R/1S/1T_undercap-64 11790448 85.43 ns/op -Benchmark/1W/99R/1S/16T_undercap-64 8781844 136.8 ns/op -Benchmark/1W/99R/1S/256T_undercap-64 6666882 176.0 ns/op -Benchmark/1W/99R/1S/65536T_undercap-64 9586593 144.3 ns/op -Benchmark/1W/99R/4S/1T_undercap-64 8508338 123.5 ns/op -Benchmark/1W/99R/4S/16T_undercap-64 14877984 83.00 ns/op -Benchmark/1W/99R/4S/256T_undercap-64 12130929 94.19 ns/op -Benchmark/1W/99R/4S/65536T_undercap-64 12901507 99.17 ns/op -Benchmark/1W/99R/16S/1T_undercap-64 10310854 117.3 ns/op -Benchmark/1W/99R/16S/16T_undercap-64 24628221 47.57 ns/op -Benchmark/1W/99R/16S/256T_undercap-64 38832448 30.77 ns/op -Benchmark/1W/99R/16S/65536T_undercap-64 31065667 37.23 ns/op -Benchmark/1W/99R/64S/1T_undercap-64 11625804 102.9 ns/op -Benchmark/1W/99R/64S/16T_undercap-64 41145703 29.97 ns/op -Benchmark/1W/99R/64S/256T_undercap-64 100000000 10.58 ns/op -Benchmark/1W/99R/64S/65536T_undercap-64 42747807 25.61 ns/op -Benchmark/0W/100R/1S/1T_undercap-64 16171641 64.73 ns/op -Benchmark/0W/100R/1S/16T_undercap-64 12583098 92.17 ns/op -Benchmark/0W/100R/1S/256T_undercap-64 8925471 140.3 ns/op -Benchmark/0W/100R/1S/65536T_undercap-64 15055190 102.6 ns/op -Benchmark/0W/100R/4S/1T_undercap-64 11323935 92.64 ns/op -Benchmark/0W/100R/4S/16T_undercap-64 22119450 54.52 ns/op -Benchmark/0W/100R/4S/256T_undercap-64 14568933 82.76 ns/op -Benchmark/0W/100R/4S/65536T_undercap-64 15293562 81.56 ns/op -Benchmark/0W/100R/16S/1T_undercap-64 13261983 93.99 ns/op -Benchmark/0W/100R/16S/16T_undercap-64 35895334 31.77 ns/op -Benchmark/0W/100R/16S/256T_undercap-64 43164102 27.45 ns/op -Benchmark/0W/100R/16S/65536T_undercap-64 36052634 32.12 ns/op -Benchmark/0W/100R/64S/1T_undercap-64 16050496 74.10 ns/op -Benchmark/0W/100R/64S/16T_undercap-64 64232628 17.37 ns/op -Benchmark/0W/100R/64S/256T_undercap-64 142430203 8.480 ns/op -Benchmark/0W/100R/64S/65536T_undercap-64 77797573 16.32 ns/op -Benchmark/100W/0R/1S/1T_eqcap#01-64 4830670 210.4 ns/op -Benchmark/100W/0R/1S/16T_eqcap#01-64 3563752 338.1 ns/op -Benchmark/100W/0R/1S/256T_eqcap#01-64 3243112 367.0 ns/op -Benchmark/100W/0R/1S/65536T_eqcap#01-64 3779554 283.8 ns/op -Benchmark/100W/0R/4S/1T_eqcap#01-64 4201594 253.7 ns/op -Benchmark/100W/0R/4S/16T_eqcap#01-64 4752001 254.6 ns/op -Benchmark/100W/0R/4S/256T_eqcap#01-64 4692968 254.3 ns/op -Benchmark/100W/0R/4S/65536T_eqcap#01-64 3978236 284.8 ns/op -Benchmark/100W/0R/16S/1T_eqcap#01-64 4063428 260.2 ns/op -Benchmark/100W/0R/16S/16T_eqcap#01-64 11666902 95.98 ns/op -Benchmark/100W/0R/16S/256T_eqcap#01-64 14420056 81.49 ns/op -Benchmark/100W/0R/16S/65536T_eqcap#01-64 11624604 107.2 ns/op -Benchmark/100W/0R/64S/1T_eqcap#01-64 3936000 269.5 ns/op -Benchmark/100W/0R/64S/16T_eqcap#01-64 24054152 49.86 ns/op -Benchmark/100W/0R/64S/256T_eqcap#01-64 44586886 26.67 ns/op -Benchmark/100W/0R/64S/65536T_eqcap#01-64 26615695 45.99 ns/op -Benchmark/75W/25R/1S/1T_eqcap#01-64 4813254 210.1 ns/op -Benchmark/75W/25R/1S/16T_eqcap#01-64 4092772 296.4 ns/op -Benchmark/75W/25R/1S/256T_eqcap#01-64 3549930 350.5 ns/op -Benchmark/75W/25R/1S/65536T_eqcap#01-64 3801076 281.4 ns/op -Benchmark/75W/25R/4S/1T_eqcap#01-64 4298455 250.2 ns/op -Benchmark/75W/25R/4S/16T_eqcap#01-64 3596553 335.3 ns/op -Benchmark/75W/25R/4S/256T_eqcap#01-64 3940414 304.8 ns/op -Benchmark/75W/25R/4S/65536T_eqcap#01-64 3124129 354.6 ns/op -Benchmark/75W/25R/16S/1T_eqcap#01-64 4124191 245.7 ns/op -Benchmark/75W/25R/16S/16T_eqcap#01-64 7194312 171.5 ns/op -Benchmark/75W/25R/16S/256T_eqcap#01-64 12473350 95.25 ns/op -Benchmark/75W/25R/16S/65536T_eqcap#01-64 8798745 126.9 ns/op -Benchmark/75W/25R/64S/1T_eqcap#01-64 4102531 260.2 ns/op -Benchmark/75W/25R/64S/16T_eqcap#01-64 12502371 93.28 ns/op -Benchmark/75W/25R/64S/256T_eqcap#01-64 35186503 33.68 ns/op -Benchmark/75W/25R/64S/65536T_eqcap#01-64 23715357 50.86 ns/op -Benchmark/25W/75R/1S/1T_eqcap#01-64 5338872 189.4 ns/op -Benchmark/25W/75R/1S/16T_eqcap#01-64 4705712 257.5 ns/op -Benchmark/25W/75R/1S/256T_eqcap#01-64 3411235 371.8 ns/op -Benchmark/25W/75R/1S/65536T_eqcap#01-64 4405827 238.3 ns/op -Benchmark/25W/75R/4S/1T_eqcap#01-64 4692376 225.4 ns/op -Benchmark/25W/75R/4S/16T_eqcap#01-64 3924135 305.8 ns/op -Benchmark/25W/75R/4S/256T_eqcap#01-64 4304388 274.0 ns/op -Benchmark/25W/75R/4S/65536T_eqcap#01-64 3941193 299.9 ns/op -Benchmark/25W/75R/16S/1T_eqcap#01-64 4772356 220.3 ns/op -Benchmark/25W/75R/16S/16T_eqcap#01-64 8145172 150.9 ns/op -Benchmark/25W/75R/16S/256T_eqcap#01-64 13936396 83.49 ns/op -Benchmark/25W/75R/16S/65536T_eqcap#01-64 11082525 103.7 ns/op -Benchmark/25W/75R/64S/1T_eqcap#01-64 4626573 229.4 ns/op -Benchmark/25W/75R/64S/16T_eqcap#01-64 14654905 79.66 ns/op -Benchmark/25W/75R/64S/256T_eqcap#01-64 34158146 34.87 ns/op -Benchmark/25W/75R/64S/65536T_eqcap#01-64 23926622 47.83 ns/op -Benchmark/1W/99R/1S/1T_eqcap#01-64 7069638 167.0 ns/op -Benchmark/1W/99R/1S/16T_eqcap#01-64 4589054 261.4 ns/op -Benchmark/1W/99R/1S/256T_eqcap#01-64 3026444 422.5 ns/op -Benchmark/1W/99R/1S/65536T_eqcap#01-64 4790068 219.0 ns/op -Benchmark/1W/99R/4S/1T_eqcap#01-64 5125884 208.4 ns/op -Benchmark/1W/99R/4S/16T_eqcap#01-64 4510575 264.0 ns/op -Benchmark/1W/99R/4S/256T_eqcap#01-64 5047071 235.6 ns/op -Benchmark/1W/99R/4S/65536T_eqcap#01-64 5014365 237.6 ns/op -Benchmark/1W/99R/16S/1T_eqcap#01-64 5200881 203.0 ns/op -Benchmark/1W/99R/16S/16T_eqcap#01-64 9927532 120.3 ns/op -Benchmark/1W/99R/16S/256T_eqcap#01-64 16087827 73.29 ns/op -Benchmark/1W/99R/16S/65536T_eqcap#01-64 13707086 82.13 ns/op -Benchmark/1W/99R/64S/1T_eqcap#01-64 5021683 206.7 ns/op -Benchmark/1W/99R/64S/16T_eqcap#01-64 17050701 68.75 ns/op -Benchmark/1W/99R/64S/256T_eqcap#01-64 38387090 30.92 ns/op -Benchmark/1W/99R/64S/65536T_eqcap#01-64 31338237 36.20 ns/op -Benchmark/0W/100R/1S/1T_eqcap#01-64 6046707 173.3 ns/op -Benchmark/0W/100R/1S/16T_eqcap#01-64 4544962 265.1 ns/op -Benchmark/0W/100R/1S/256T_eqcap#01-64 2917411 431.3 ns/op -Benchmark/0W/100R/1S/65536T_eqcap#01-64 4932452 221.3 ns/op -Benchmark/0W/100R/4S/1T_eqcap#01-64 5059036 206.4 ns/op -Benchmark/0W/100R/4S/16T_eqcap#01-64 4638258 263.1 ns/op -Benchmark/0W/100R/4S/256T_eqcap#01-64 5186674 232.1 ns/op -Benchmark/0W/100R/4S/65536T_eqcap#01-64 5129036 229.6 ns/op -Benchmark/0W/100R/16S/1T_eqcap#01-64 5217052 206.8 ns/op -Benchmark/0W/100R/16S/16T_eqcap#01-64 9953164 118.2 ns/op -Benchmark/0W/100R/16S/256T_eqcap#01-64 16206282 73.75 ns/op -Benchmark/0W/100R/16S/65536T_eqcap#01-64 14044218 80.87 ns/op -Benchmark/0W/100R/64S/1T_eqcap#01-64 5138592 207.2 ns/op -Benchmark/0W/100R/64S/16T_eqcap#01-64 17334014 69.18 ns/op -Benchmark/0W/100R/64S/256T_eqcap#01-64 39012428 30.50 ns/op -Benchmark/0W/100R/64S/65536T_eqcap#01-64 34789518 36.13 ns/op -BenchmarkStringSharder/1-64 35473635 30.02 ns/op -BenchmarkStringSharder/4-64 38362208 30.00 ns/op -BenchmarkStringSharder/16-64 37217360 30.29 ns/op -BenchmarkStringSharder/64-64 32306324 36.08 ns/op -BenchmarkStringSharder/256-64 23939040 51.84 ns/op -BenchmarkBytesSharder/1-64 40583445 29.89 ns/op -BenchmarkBytesSharder/4-64 38763214 32.01 ns/op -BenchmarkBytesSharder/16-64 37777782 30.51 ns/op -BenchmarkBytesSharder/64-64 31833506 36.40 ns/op -BenchmarkBytesSharder/256-64 22426018 50.98 ns/op -BenchmarkCustomSharder-64 17537882 70.97 ns/op -PASS -ok github.com/TriggerMail/lazylru/generic/sharded 603.303s diff --git a/generic/sharded/expected_stats_test.go b/generic/sharded/expected_stats_test.go deleted file mode 100644 index 164058f..0000000 --- a/generic/sharded/expected_stats_test.go +++ /dev/null @@ -1,93 +0,0 @@ -package sharded_test - -import ( - "testing" - - lazylru "github.com/TriggerMail/lazylru/generic" - "github.com/stretchr/testify/require" -) - -type ExpectedStats struct { - KeysWritten *uint32 - KeysReadOK *uint32 - KeysReadNotFound *uint32 - KeysReadExpired *uint32 - Shuffles *uint32 - Evictions *uint32 - KeysReaped *uint32 - ReaperCycles *uint32 -} - -func (es ExpectedStats) WithKeysWritten(v uint32) ExpectedStats { - es.KeysWritten = &v - return es -} - -func (es ExpectedStats) WithKeysReadOK(v uint32) ExpectedStats { - es.KeysReadOK = &v - return es -} - -func (es ExpectedStats) WithKeysReadNotFound(v uint32) ExpectedStats { - es.KeysReadNotFound = &v - return es -} - -func (es ExpectedStats) WithKeysReadExpired(v uint32) ExpectedStats { - es.KeysReadExpired = &v - return es -} - -func (es ExpectedStats) WithShuffles(v uint32) ExpectedStats { - es.Shuffles = &v - return es -} - -func (es ExpectedStats) WithEvictions(v uint32) ExpectedStats { - es.Evictions = &v - return es -} - -func (es ExpectedStats) WithKeysReaped(v uint32) ExpectedStats { - es.KeysReaped = &v - return es -} - -func (es ExpectedStats) WithReaperCycles(v uint32) ExpectedStats { - es.ReaperCycles = &v - return es -} - -func (es ExpectedStats) Test(t *testing.T, stats lazylru.Stats) { - if es.KeysWritten != nil { - require.Equal(t, int(*es.KeysWritten), int(stats.KeysWritten), "keys written") - } - - if es.KeysReadOK != nil { - require.Equal(t, int(*es.KeysReadOK), int(stats.KeysReadOK), "keys read ok") - } - - if es.KeysReadNotFound != nil { - require.Equal(t, int(*es.KeysReadNotFound), int(stats.KeysReadNotFound), "keys read not found") - } - - if es.KeysReadExpired != nil { - require.Equal(t, int(*es.KeysReadExpired), int(stats.KeysReadExpired), "keys read expired") - } - - if es.Shuffles != nil { - require.Equal(t, int(*es.Shuffles), int(stats.Shuffles), "shuffles") - } - - if es.Evictions != nil { - require.Equal(t, int(*es.Evictions), int(stats.Evictions), "evictions") - } - - if es.KeysReaped != nil { - require.Equal(t, int(*es.KeysReaped), int(stats.KeysReaped), "keys reaped") - } - - if es.ReaperCycles != nil { - require.Equal(t, int(*es.ReaperCycles), int(stats.ReaperCycles), "reaper cycles") - } -} diff --git a/generic/sharded/hasher.go b/generic/sharded/hasher.go deleted file mode 100644 index f8c9887..0000000 --- a/generic/sharded/hasher.go +++ /dev/null @@ -1,111 +0,0 @@ -package sharded - -import ( - "encoding/binary" - "math" - "sync" - - "github.com/zeebo/xxh3" -) - -// these pools allow the sharding operations to amortize to zero allocations -var bufpool = sync.Pool{New: func() any { buf := make([]byte, 8); return &buf }} - -type hasher xxh3.Hasher - -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -func (h *hasher) Reset() { - (*xxh3.Hasher)(h).Reset() -} - -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -func (h *hasher) Sum64() uint64 { - return (*xxh3.Hasher)(h).Sum64() -} - -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -func (h *hasher) Write(buf []byte) (int, error) { - return (*xxh3.Hasher)(h).Write(buf) -} - -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -func (h *hasher) WriteString(s string) (int, error) { - return (*xxh3.Hasher)(h).WriteString(s) -} - -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -func (h *hasher) WriteUint64(v uint64) { - bufP := bufpool.Get().(*[]byte) - buf := *bufP - binary.LittleEndian.PutUint64(buf, v) - _, _ = h.Write(buf) - bufpool.Put(bufP) -} - -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -func (h *hasher) WriteUint32(v uint32) { - bufP := bufpool.Get().(*[]byte) - buf := (*bufP)[0:4] - binary.LittleEndian.PutUint32(buf, v) - _, _ = h.Write(buf) - bufpool.Put(bufP) -} - -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -func (h *hasher) WriteUint16(v uint16) { - bufP := bufpool.Get().(*[]byte) - buf := (*bufP)[0:2] - binary.LittleEndian.PutUint16(buf, v) - _, _ = h.Write(buf) - bufpool.Put(bufP) -} - -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -func (h *hasher) WriteUint8(v uint8) { - bufP := bufpool.Get().(*[]byte) - buf := (*bufP)[0:1] - buf[0] = v - _, _ = h.Write(buf) - bufpool.Put(bufP) -} - -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -func (h *hasher) WriteFloat32(v float32) { - h.WriteUint32(math.Float32bits(v)) -} - -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -func (h *hasher) WriteFloat64(v float64) { - h.WriteUint64(math.Float64bits(v)) -} - -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -func (h *hasher) WriteBool(v bool) { - b := uint8(0) - if v { - b = 1 - } - h.WriteUint8(b) -} diff --git a/generic/sharded/hasher_test.go b/generic/sharded/hasher_test.go deleted file mode 100644 index 74c7fa5..0000000 --- a/generic/sharded/hasher_test.go +++ /dev/null @@ -1,131 +0,0 @@ -package sharded - -import ( - "testing" - - "github.com/stretchr/testify/require" - "github.com/zeebo/xxh3" -) - -func TestStrings(t *testing.T) { - h := (*hasher)(xxh3.New()) - _, err := h.WriteString("x") - require.NoError(t, err) - v1 := h.Sum64() - h.Reset() - _, err = h.WriteString("x") - require.NoError(t, err) - v2 := h.Sum64() - _, err = h.WriteString("x") - require.NoError(t, err) - v3 := h.Sum64() - require.Equal(t, v1, v2) - require.NotEqual(t, v1, v3) -} - -func TestBytes(t *testing.T) { - h := (*hasher)(xxh3.New()) - _, err := h.Write([]byte("x")) - require.NoError(t, err) - v1 := h.Sum64() - h.Reset() - _, err = h.Write([]byte("x")) - require.NoError(t, err) - v2 := h.Sum64() - _, err = h.Write([]byte("x")) - require.NoError(t, err) - v3 := h.Sum64() - require.Equal(t, v1, v2) - require.NotEqual(t, v1, v3) -} - -func TestUint8(t *testing.T) { - h := (*hasher)(xxh3.New()) - h.WriteUint8(1) - v1 := h.Sum64() - h.Reset() - h.WriteUint8(1) - v2 := h.Sum64() - h.WriteUint8(1) - v3 := h.Sum64() - require.Equal(t, v1, v2) - require.NotEqual(t, v1, v3) -} - -func TestUint16(t *testing.T) { - h := (*hasher)(xxh3.New()) - h.WriteUint16(1) - v1 := h.Sum64() - h.Reset() - h.WriteUint16(1) - v2 := h.Sum64() - h.WriteUint16(1) - v3 := h.Sum64() - require.Equal(t, v1, v2) - require.NotEqual(t, v1, v3) -} - -func TestUint32(t *testing.T) { - h := (*hasher)(xxh3.New()) - h.WriteUint32(1) - v1 := h.Sum64() - h.Reset() - h.WriteUint32(1) - v2 := h.Sum64() - h.WriteUint32(1) - v3 := h.Sum64() - require.Equal(t, v1, v2) - require.NotEqual(t, v1, v3) -} - -func TestUint64(t *testing.T) { - h := (*hasher)(xxh3.New()) - h.WriteUint64(1) - v1 := h.Sum64() - h.Reset() - h.WriteUint64(1) - v2 := h.Sum64() - h.WriteUint64(1) - v3 := h.Sum64() - require.Equal(t, v1, v2) - require.NotEqual(t, v1, v3) -} - -func TestFloat32(t *testing.T) { - h := (*hasher)(xxh3.New()) - h.WriteFloat32(1) - v1 := h.Sum64() - h.Reset() - h.WriteFloat32(1) - v2 := h.Sum64() - h.WriteFloat32(1) - v3 := h.Sum64() - require.Equal(t, v1, v2) - require.NotEqual(t, v1, v3) -} - -func TestFloat64(t *testing.T) { - h := (*hasher)(xxh3.New()) - h.WriteFloat64(1) - v1 := h.Sum64() - h.Reset() - h.WriteFloat64(1) - v2 := h.Sum64() - h.WriteFloat64(1) - v3 := h.Sum64() - require.Equal(t, v1, v2) - require.NotEqual(t, v1, v3) -} - -func TestBool(t *testing.T) { - h := (*hasher)(xxh3.New()) - h.WriteBool(true) - v1 := h.Sum64() - h.Reset() - h.WriteBool(true) - v2 := h.Sum64() - h.WriteBool(true) - v3 := h.Sum64() - require.Equal(t, v1, v2) - require.NotEqual(t, v1, v3) -} diff --git a/generic/sharded/lazylru_benchmark_test.go b/generic/sharded/lazylru_benchmark_test.go deleted file mode 100644 index 827b0a2..0000000 --- a/generic/sharded/lazylru_benchmark_test.go +++ /dev/null @@ -1,115 +0,0 @@ -package sharded_test - -import ( - "fmt" - "math/rand/v2" - "runtime" - "strconv" - "sync" - "testing" - "time" - - lazylru "github.com/TriggerMail/lazylru/generic" - sharded "github.com/TriggerMail/lazylru/generic/sharded" -) - -const keycnt = 1 << 14 - -var keys = func() []string { - k := make([]string, keycnt) - - for i := 0; i < keycnt; i++ { - k[i] = strconv.Itoa(i) - } - return k -}() - -type benchconfig struct { - capacity int - keyCount int - readRate float64 - shards int - concurrency int -} - -func (bc benchconfig) Name() string { - comment := "eqcap" - if bc.capacity > bc.keyCount { - comment = "overcap" - } else if bc.capacity < bc.keyCount { - comment = "undercap" - } - return fmt.Sprintf("%dW/%dR/%dS/%dT_%s", 100-int(100*bc.readRate), int(100*bc.readRate), bc.shards, bc.concurrency, comment) -} - -func (bc benchconfig) Run(b *testing.B) { - var lru interface { - Set(string, int) - Get(string) (int, bool) - Close() - } - - if bc.shards <= 1 { - lru = lazylru.NewT[string, int](bc.capacity, time.Minute) - } else { - if bc.capacity < bc.shards { - b.Fatalf("Capacity (%d) must be greater than shard count (%d)", bc.capacity, bc.shards) - } - lru = sharded.NewT[string, int](bc.capacity/bc.shards, time.Minute, bc.shards, sharded.StringSharder) - } - - defer lru.Close() - for i := 0; i < bc.keyCount; i++ { - lru.Set(keys[i], i) - } - runtime.GC() - b.ResetTimer() - var wg sync.WaitGroup - wg.Add(bc.concurrency) - - for c := 0; c < bc.concurrency; c++ { - go func(c int) { - for i := c; i < b.N; i += bc.concurrency { - ix := rand.IntN(bc.keyCount) //nolint:gosec - if rand.Float64() < bc.readRate { //nolint:gosec - lru.Get(keys[ix]) - } else { - lru.Set(keys[ix], ix) - } - } - wg.Done() - }(c) - } - wg.Wait() - b.StopTimer() -} - -func Benchmark(b *testing.B) { - for _, cfg := range []struct { - capacity int - keyCount int - }{ - {1 << 8, 1 << 8}, // this is meant as a warm-up - {1 << 14, 1 << 8}, - {1 << 8, 1 << 14}, - {1 << 14, 1 << 14}, - } { - if cfg.keyCount > keycnt { - b.Fatalf("configured keyCount (%d) cannot be greater than the global keycnt (%d)", cfg.keyCount, keycnt) - } - for _, readRate := range []float64{0, 0.25, 0.75, 0.99, 1} { - for _, shards := range []int{1, 4, 16, 64} { - for _, concurrency := range []int{1, 1 << 4, 1 << 8, 1 << 16} { - bc := benchconfig{ - capacity: cfg.capacity, - keyCount: cfg.keyCount, - readRate: readRate, - shards: shards, - concurrency: concurrency, - } - b.Run(bc.Name(), bc.Run) - } - } - } - } -} diff --git a/generic/sharded/shard_helper_key.go b/generic/sharded/shard_helper_key.go deleted file mode 100644 index 4a15e0b..0000000 --- a/generic/sharded/shard_helper_key.go +++ /dev/null @@ -1,77 +0,0 @@ -package sharded - -import "sort" - -// keyShardHelper is used to group keys by the shards they target. This is used -// in MGet. -type keyShardHelper[K comparable] struct { - keys []K - shardIndices []int -} - -// newKeyShardHelper is used to group keys for MGet -func newKeyShardHelper[K comparable](keys []K, fIx func(K) int) *keyShardHelper[K] { - keyCopy := make([]K, len(keys)) - shardIndices := make([]int, len(keys)) - for i := 0; i < len(keys); i++ { - keyCopy[i] = keys[i] - shardIndices[i] = fIx(keys[i]) - } - retval := &keyShardHelper[K]{keyCopy, shardIndices} - sort.Sort(retval) - return retval -} - -// Len gets the length. This is part of sort.Interface -// -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -func (s *keyShardHelper[K]) Len() int { return len(s.keys) } - -// Less compares the target shards of two keys by index. This is part of -// sort.Interface -// -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -func (s *keyShardHelper[K]) Less(i, j int) bool { - return s.shardIndices[i] < s.shardIndices[j] -} - -// Swap is used to switch the positions of two keys by index. This is part of -// sort.Interface -// -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -func (s *keyShardHelper[K]) Swap(i, j int) { - s.keys[i], s.keys[j] = s.keys[j], s.keys[i] - s.shardIndices[i], s.shardIndices[j] = s.shardIndices[j], s.shardIndices[i] -} - -// TakeGroup returns the first set of keys that have the same target shard, and -// the index of that target shard. If there are no more groups left, the target -// shard index will be negative. -// -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -func (s *keyShardHelper[K]) TakeGroup() (int, []K) { - if len(s.keys) == 0 { - return -1, nil - } - shardix := s.shardIndices[0] - for e := 1; e < len(s.keys); e++ { - if s.shardIndices[e] != shardix { - retval := s.keys[0:e] - s.keys = s.keys[e:] - s.shardIndices = s.shardIndices[e:] - return shardix, retval - } - } - retval := s.keys - s.keys = nil - s.shardIndices = nil - return shardix, retval -} diff --git a/generic/sharded/shard_helper_kv.go b/generic/sharded/shard_helper_kv.go deleted file mode 100644 index 874fab0..0000000 --- a/generic/sharded/shard_helper_kv.go +++ /dev/null @@ -1,85 +0,0 @@ -package sharded - -import "sort" - -// kvShardHelper is used to group keys and associated values by the shards they -// target. This is used in MSet and MSetTTL. -type kvShardHelper[K comparable, V any] struct { - keys []K - values []V - shardIndices []int -} - -// newKVShardHelper is used to group keys and values for MSet and MSetTTL -func newKVShardHelper[K comparable, V any](keys []K, values []V, fIx func(K) int) *kvShardHelper[K, V] { - keyCopy := make([]K, len(keys)) - valCopy := make([]V, len(values)) - shardIndices := make([]int, len(keys)) - for i := 0; i < len(keys); i++ { - keyCopy[i] = keys[i] - valCopy[i] = values[i] - shardIndices[i] = fIx(keys[i]) - } - retval := &kvShardHelper[K, V]{keyCopy, valCopy, shardIndices} - sort.Sort(retval) - return retval -} - -// Len gets the length. This is part of sort.Interface -// -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -func (s *kvShardHelper[K, V]) Len() int { return len(s.keys) } - -// Less compares the target shards of two key/value pairs by index. This is part -// of sort.Interface -// -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -func (s *kvShardHelper[K, V]) Less(i, j int) bool { - return s.shardIndices[i] < s.shardIndices[j] -} - -// Swap is used to switch the positions of two key/value pairs by index. This -// is part of sort.Interface -// -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -func (s *kvShardHelper[K, V]) Swap(i, j int) { - s.keys[i], s.keys[j] = s.keys[j], s.keys[i] - s.values[i], s.values[j] = s.values[j], s.values[i] - s.shardIndices[i], s.shardIndices[j] = s.shardIndices[j], s.shardIndices[i] -} - -// TakeGroup returns the first set of keys and values that have the same target -// shard, and the index of that target shard. If there are no more groups left, -// the target shard index will be negative. -// -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -func (s *kvShardHelper[K, V]) TakeGroup() (int, []K, []V) { - if len(s.keys) == 0 { - return -1, nil, nil - } - shardix := s.shardIndices[0] - for e := 1; e < len(s.keys); e++ { - if s.shardIndices[e] != shardix { - retkeys := s.keys[0:e] - retvals := s.values[0:e] - s.keys = s.keys[e:] - s.values = s.values[e:] - s.shardIndices = s.shardIndices[e:] - return shardix, retkeys, retvals - } - } - retkeys := s.keys - retvals := s.values - s.keys = nil - s.values = nil - s.shardIndices = nil - return shardix, retkeys, retvals -} diff --git a/generic/sharded/shard_helper_test.go b/generic/sharded/shard_helper_test.go deleted file mode 100644 index 4ebee1e..0000000 --- a/generic/sharded/shard_helper_test.go +++ /dev/null @@ -1,85 +0,0 @@ -package sharded - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestKeyShardHelper(t *testing.T) { - ks := newKeyShardHelper( - []string{"a", "b", "c"}, - func(string) int { - return 123 - }) - require.Equal(t, 123, ks.shardIndices[0]) - require.Equal(t, 123, ks.shardIndices[1]) - require.Equal(t, 123, ks.shardIndices[2]) - ix, keys := ks.TakeGroup() - require.Equal(t, 123, ix) - require.Equal(t, 3, len(keys)) - ix, keys = ks.TakeGroup() - require.Equal(t, -1, ix) - require.Equal(t, 0, len(keys)) - - ks = &keyShardHelper[string]{ - []string{"a", "b", "c"}, - []int{0, 1, 1}, - } - ix, keys = ks.TakeGroup() - require.Equal(t, 0, ix) - require.Equal(t, 1, len(keys)) - ix, keys = ks.TakeGroup() - require.Equal(t, 1, ix) - require.Equal(t, 2, len(keys)) - ix, keys = ks.TakeGroup() - require.Equal(t, -1, ix) - require.Equal(t, 0, len(keys)) -} - -func TestKVShardHelper(t *testing.T) { - ks := newKVShardHelper( - []string{"ak", "bk", "ck"}, - []string{"av", "bv", "cv"}, - func(k string) int { - return int(StringSharder(k) % 10) - }, - ) - require.Equal(t, 3, len(ks.keys)) - for i := 0; i < len(ks.keys); i++ { - require.Equal(t, int(StringSharder(ks.keys[i])%10), ks.shardIndices[i]) - // compare first letters for match - require.Equal(t, ks.keys[i][0], ks.values[i][0]) - } - ix, keys, vals := ks.TakeGroup() - for ix > 0 { - for i := 0; i < len(keys); i++ { - require.Equal(t, ix, int(StringSharder(keys[i])%10)) - require.Equal(t, keys[i][0], vals[i][0]) - } - ix, keys, vals = ks.TakeGroup() - } - require.Equal(t, -1, ix) - require.Equal(t, 0, len(keys)) - require.Equal(t, 0, len(vals)) - require.Equal(t, 0, len(ks.keys)) - require.Equal(t, 0, len(ks.values)) - - ks = &kvShardHelper[string, string]{ - []string{"ak", "bk", "ck"}, - []string{"av", "bv", "cv"}, - []int{0, 1, 1}, - } - ix, keys, vals = ks.TakeGroup() - require.Equal(t, 0, ix) - require.Equal(t, 1, len(keys)) - require.Equal(t, 1, len(vals)) - ix, keys, vals = ks.TakeGroup() - require.Equal(t, 1, ix) - require.Equal(t, 2, len(keys)) - require.Equal(t, 2, len(vals)) - ix, keys, vals = ks.TakeGroup() - require.Equal(t, -1, ix) - require.Equal(t, 0, len(keys)) - require.Equal(t, 0, len(vals)) -} diff --git a/generic/sharded/sharded.go b/generic/sharded/sharded.go deleted file mode 100644 index f05323c..0000000 --- a/generic/sharded/sharded.go +++ /dev/null @@ -1,244 +0,0 @@ -package sharded - -import ( - "errors" - "time" - - lazylru "github.com/TriggerMail/lazylru/generic" -) - -// LazyLRU is a sharded version of the lazylru.LazyLRU cache. The goal of this -// cache is to reduce lock contention via sharding. This may have a negative -// impact on memory locality, so your mileage may vary. -// -// LazyLRU is an LRU cache that only reshuffles values if it is somewhat full. -// This is a cache implementation that uses a hash table for lookups and a -// priority queue to approximate LRU. Approximate because the usage is not -// updated on every get. Rather, items close to the head of the queue, those -// most likely to be read again and least likely to age out, are not updated. -// This assumption does not hold under every condition -- if the cache is -// undersized and churning a lot, this implementation will perform worse than an -// LRU that updates on every read. -// -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -type LazyLRU[K comparable, V any] struct { - sharder func(K) uint64 - shards []*lazylru.LazyLRU[K, V] - ttl time.Duration -} - -// New creates a new sharded cache with strings for keys and any (interface{}) -// for values -// -// Deprecated: To avoid the casting, use the generic NewT interface instead -// -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -func New(maxItemsPerShard int, ttl time.Duration, numShards int) *LazyLRU[string, any] { - return NewT[string, any](maxItemsPerShard, ttl, numShards, StringSharder) -} - -// NewT creates a new sharded cache. The sharder function must be consistent -// and should be as uniformly-distributed over the expected source keys as -// possible. For string and []byte keys, the pre-canned StringSharder and -// BytesSharder are appropriate. These are both based on the HashingSharder, -// which callers can use to create sharder functions for custom types. -// -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -func NewT[K comparable, V any](maxItemsPerShard int, ttl time.Duration, numShards int, sharder func(K) uint64) *LazyLRU[K, V] { - shards := make([]*lazylru.LazyLRU[K, V], numShards) - for i := 0; i < numShards; i++ { - shards[i] = lazylru.NewT[K, V](maxItemsPerShard, ttl) - } - - return &LazyLRU[K, V]{sharder, shards, ttl} -} - -// ShardIx determines the target shard for the provided key -// -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -func (slru *LazyLRU[K, V]) ShardIx(key K) int { - return int(slru.sharder(key) % uint64(len(slru.shards))) -} - -// IsRunning indicates whether the background reaper is active on at least one -// of the shards -// -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -func (slru *LazyLRU[K, V]) IsRunning() bool { - for _, s := range slru.shards { - if s.IsRunning() { - return true - } - } - return false -} - -// Reap removes all expired items from the cache -// -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -func (slru *LazyLRU[K, V]) Reap() { - for _, s := range slru.shards { - s.Reap() - } -} - -// Get retrieves a value from the cache. The returned bool indicates whether the -// key was found in the cache. -// -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -func (slru *LazyLRU[K, V]) Get(key K) (V, bool) { - return slru.shards[slru.ShardIx(key)].Get(key) -} - -// MGet retrieves values from the cache. Missing values will not be returned. -// -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -func (slru *LazyLRU[K, V]) MGet(keys ...K) map[K]V { - retval := map[K]V{} - if len(keys) == 0 { - return retval - } - if len(keys) == 1 { - v, ok := slru.Get(keys[0]) - if ok { - retval[keys[0]] = v - } - return retval - } - shardMapper := newKeyShardHelper(keys, slru.ShardIx) - for { - shardIx, skeys := shardMapper.TakeGroup() - if shardIx < 0 { - return retval - } - for k, v := range slru.shards[shardIx].MGet(skeys...) { - retval[k] = v - } - } -} - -// Set writes to the cache -// -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -func (slru *LazyLRU[K, V]) Set(key K, value V) { - slru.shards[slru.ShardIx(key)].Set(key, value) -} - -// SetTTL writes to the cache, expiring with the given time-to-live value -// -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -func (slru *LazyLRU[K, V]) SetTTL(key K, value V, ttl time.Duration) { - slru.shards[slru.ShardIx(key)].SetTTL(key, value, ttl) -} - -// MSet writes multiple keys and values to the cache. If the "key" and "value" -// parameters are of different lengths, this method will return an error. -// -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -func (slru *LazyLRU[K, V]) MSet(keys []K, values []V) error { - return slru.MSetTTL(keys, values, slru.ttl) -} - -// MSetTTL writes multiple keys and values to the cache, expiring with the given -// time-to-live value. If the "key" and "value" parameters are of different -// lengths, this method will return an error. -// -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -func (slru *LazyLRU[K, V]) MSetTTL(keys []K, values []V, ttl time.Duration) error { - // we don't need to store stuff that is already expired - if ttl <= 0 { - return nil - } - if len(keys) != len(values) { - return errors.New("Mismatch between number of keys and number of values") - } - - if len(keys) == 0 { - return nil - } - if len(keys) == 1 { - slru.SetTTL(keys[0], values[0], ttl) - return nil - } - shardMapper := newKVShardHelper(keys, values, slru.ShardIx) - for { - shardIx, skeys, svals := shardMapper.TakeGroup() - if shardIx < 0 { - return nil - } - if err := slru.shards[shardIx].MSetTTL(skeys, svals, ttl); err != nil { - return err - } - } -} - -// Len returns the number of items in the cache -// -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -func (slru *LazyLRU[K, V]) Len() int { - retval := 0 - for _, s := range slru.shards { - retval += s.Len() - } - return retval -} - -// Close stops the reaper process. This is safe to call multiple times. -// -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -func (slru *LazyLRU[K, V]) Close() { - for _, s := range slru.shards { - s.Close() - } -} - -// Stats gets a copy of the stats held by the cache. Note that this is a copy, -// so returned objects will not update as the service continues to execute. The -// returned value is a sum of each statistic across all shards. -// -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -func (slru *LazyLRU[K, V]) Stats() lazylru.Stats { - stats := slru.shards[0].Stats() - for i := 1; i < len(slru.shards); i++ { - lstats := slru.shards[i].Stats() - stats.KeysWritten += lstats.KeysWritten - stats.KeysReadOK += lstats.KeysReadOK - stats.KeysReadNotFound += lstats.KeysReadNotFound - stats.KeysReadExpired += lstats.KeysReadExpired - stats.Shuffles += lstats.Shuffles - stats.Evictions += lstats.Evictions - stats.KeysReaped += lstats.KeysReaped - stats.ReaperCycles += lstats.ReaperCycles - } - return stats -} diff --git a/generic/sharded/sharded_test.go b/generic/sharded/sharded_test.go deleted file mode 100644 index 5c41b25..0000000 --- a/generic/sharded/sharded_test.go +++ /dev/null @@ -1,417 +0,0 @@ -package sharded_test - -import ( - "strconv" - "testing" - "time" - - "github.com/TriggerMail/lazylru/generic/sharded" - "github.com/stretchr/testify/require" -) - -func doShardedTest[K comparable, V any](t *testing.T, maxItems int, ttl time.Duration, numShards int, selector func(K) uint64, test func(t *testing.T, lru *sharded.LazyLRU[K, V]), expected ExpectedStats) { - lru := sharded.NewT[K, V](maxItems, ttl, numShards, selector) - test(t, lru) - lru.Close() - expected.Test(t, lru.Stats()) -} - -func TestMakeNew(t *testing.T) { - doShardedTest(t, 10, time.Hour, 10, sharded.StringSharder, func(t *testing.T, lru *sharded.LazyLRU[string, int]) { - require.NotNil(t, lru) - }, - ExpectedStats{}, - ) -} - -func TestGetUnknown(t *testing.T) { - doShardedTest(t, 10, time.Hour, 10, sharded.StringSharder, func(t *testing.T, lru *sharded.LazyLRU[string, int]) { - v, ok := lru.Get("something new") - require.Equal(t, 0, v) - require.False(t, ok) - }, - ExpectedStats{}.WithKeysReadNotFound(1), - ) -} - -func TestGetKnown(t *testing.T) { - doShardedTest(t, 10, time.Hour, 10, sharded.StringSharder, func(t *testing.T, lru *sharded.LazyLRU[string, string]) { - lru.Set("abloy", "medeco") - v, ok := lru.Get("abloy") - require.True(t, ok) - require.Equal(t, "medeco", v) - }, - ExpectedStats{}.WithKeysWritten(1).WithKeysReadOK(1), - ) -} - -func TestMGetUnknown(t *testing.T) { - doShardedTest(t, 10, time.Hour, 10, sharded.StringSharder, func(t *testing.T, lru *sharded.LazyLRU[string, string]) { - found := lru.MGet("a", "b", "c") - require.Equal(t, 0, len(found)) - }, - ExpectedStats{}.WithKeysReadNotFound(3), - ) -} - -func TestMGetKnown(t *testing.T) { - doShardedTest(t, 10, time.Hour, 10, sharded.StringSharder, func(t *testing.T, lru *sharded.LazyLRU[string, string]) { - err := lru.MSet( - []string{"abloy", "schlage"}, - []string{"medeco", "kwikset"}, - ) - require.NoError(t, err) - found := lru.MGet("abloy", "schlage") - require.Equal(t, 2, len(found)) - v, ok := found["abloy"] - require.True(t, ok) - require.Equal(t, "medeco", v) - }, - ExpectedStats{}.WithKeysWritten(2).WithKeysReadOK(2), - ) -} - -func TestSetNTimes(t *testing.T) { - doShardedTest(t, 10, time.Hour, 10, sharded.StringSharder, func(t *testing.T, lru *sharded.LazyLRU[string, string]) { - require.Equal(t, 0, lru.Len()) - lru.Set("abloy", "schlage") - require.Equal(t, 1, lru.Len()) - for i := 0; i < 1000; i++ { - lru.Set("abloy", "schlage") - } - require.Equal(t, 1, lru.Len()) - }, - ExpectedStats{}.WithKeysWritten(1001), - ) -} - -func TestMGetOneKnown(t *testing.T) { - doShardedTest(t, 10, time.Hour, 10, sharded.StringSharder, func(t *testing.T, lru *sharded.LazyLRU[string, string]) { - lru.Set("abloy", "medeco") - - found := lru.MGet("abloy") - require.Equal(t, 1, len(found)) - - v, ok := found["abloy"] - require.True(t, ok) - require.Equal(t, "medeco", v) - }, - ExpectedStats{}.WithKeysWritten(1).WithKeysReadOK(1), - ) -} - -func TestMSetBad(t *testing.T) { - doShardedTest(t, 10, time.Hour, 10, sharded.StringSharder, func(t *testing.T, lru *sharded.LazyLRU[string, string]) { - err := lru.MSet( - []string{"abloy"}, - []string{"medeco", "kwikset"}, - ) - require.Error(t, err) - }, - ExpectedStats{}.WithKeysWritten(0), - ) -} - -func TestMSetTooMany(t *testing.T) { - doShardedTest(t, 2, time.Hour, 2, sharded.StringSharder, func(t *testing.T, lru *sharded.LazyLRU[string, string]) { - err := lru.MSet( - []string{"a", "b", "c", "d", "e", "f", "g"}, - []string{"a", "b", "c", "d", "e", "f", "g"}, - ) - require.NoError(t, err) - require.Equal(t, 4, lru.Len()) - }, - ExpectedStats{}.WithKeysWritten(7).WithEvictions(3), - ) -} - -func TestMSetTooManyTwice(t *testing.T) { - doShardedTest(t, 2, time.Hour, 2, sharded.StringSharder, func(t *testing.T, lru *sharded.LazyLRU[string, string]) { - err := lru.MSet( - []string{"a", "b", "c", "d", "e", "f", "g"}, - []string{"a", "b", "c", "d", "e", "f", "g"}, - ) - require.NoError(t, err) - require.Equal(t, 4, lru.Len()) - found := lru.MGet("a", "b", "c", "d", "e", "f", "g") - require.Equal(t, 4, len(found)) - - // "g" will still be in the set, but "a" will evict something - err = lru.MSet( - []string{"a", "g"}, - []string{"a", "g"}, - ) - - require.NoError(t, err) - require.Equal(t, 4, lru.Len()) - _, ok := lru.Get("f") - require.True(t, ok) - _, ok = lru.Get("g") - require.True(t, ok) - }, - ExpectedStats{}. - WithKeysWritten(9). - WithEvictions(4). - WithKeysReadOK(6). - WithKeysReadNotFound(3), - ) -} - -func TestMGetExpired(t *testing.T) { - doShardedTest(t, 5, time.Millisecond, 10, sharded.StringSharder, func(t *testing.T, lru *sharded.LazyLRU[string, string]) { - lru.Set("abloy", "medeco") - time.Sleep(time.Millisecond * 10) - - found := lru.MGet("abloy") - require.Equal(t, 0, len(found)) - }, - ExpectedStats{}. - WithKeysWritten(1). - WithKeysReadExpired(0). - WithKeysReadNotFound(1). - WithKeysReaped(1), - ) -} - -func TestClose(t *testing.T) { - lru := sharded.NewT[string, string](10, time.Hour, 10, sharded.StringSharder) - require.True(t, lru.IsRunning()) - lru.Close() - time.Sleep(time.Millisecond * 10) - require.False(t, lru.IsRunning()) - lru.Close() // ensure double-close is safe -} - -func TestCloseWithReap(t *testing.T) { - doShardedTest(t, 10, 10*time.Millisecond, 10, sharded.StringSharder, func(t *testing.T, lru *sharded.LazyLRU[string, int]) { - require.True(t, lru.IsRunning()) - - lru.SetTTL("abloy", 0, time.Hour) - err := lru.MSetTTL( - []string{"a", "b", "c", "d", "e"}, - []int{1, 2, 3, 4, 5}, - 1, - ) - require.NoError(t, err) - require.Equal(t, 6, lru.Len()) - time.Sleep(time.Millisecond * 20) - require.True(t, lru.IsRunning()) - require.Equal(t, 1, lru.Len()) - lru.Close() - time.Sleep(time.Millisecond * 10) - require.False(t, lru.IsRunning()) - }, - ExpectedStats{}. - WithKeysWritten(6). - WithKeysReaped(5), - ) -} - -func TestReap(t *testing.T) { - doShardedTest(t, 10, time.Hour, 10, sharded.StringSharder, func(t *testing.T, lru *sharded.LazyLRU[string, string]) { - lru.Reap() - - lru.SetTTL("abloy", "medeco", time.Millisecond*10) - - found := lru.MGet("abloy") - require.Equal(t, 1, len(found)) - - time.Sleep(time.Millisecond * 10) - lru.Reap() - found = lru.MGet("abloy") - require.Equal(t, 0, len(found)) - require.Equal(t, 0, lru.Len()) - }, - ExpectedStats{}. - WithKeysWritten(1). - WithKeysReadOK(1). - WithKeysReadNotFound(1). - WithKeysReaped(1). - // make sure that we actually reaped the key, not that the read of an - // expired key did it - WithKeysReadExpired(0), - ) -} - -func TestPushBeyondCapacity(t *testing.T) { - doShardedTest(t, 10, time.Hour, 10, sharded.StringSharder, func(t *testing.T, lru *sharded.LazyLRU[string, string]) { - keys := make([]string, 1000) - for i := 0; i < len(keys); i++ { - keys[i] = strconv.FormatInt(int64(i), 10) - lru.Set(keys[i], keys[i]) - } - - cnt := 0 - for i := 0; i < len(keys); i++ { - keys[i] = strconv.FormatInt(int64(i), 10) - if v, ok := lru.Get(keys[i]); ok { - cnt++ - require.Equal(t, keys[i], v) - } - } - require.Equal(t, 100, cnt) - }, - ExpectedStats{}. - WithKeysWritten(1000). - WithKeysReadOK(100). - WithKeysReadNotFound(900). - WithEvictions(900), - ) -} - -func TestPushBeyondCapacitySave28(t *testing.T) { - doShardedTest(t, 10, time.Hour, 10, sharded.StringSharder, func(t *testing.T, lru *sharded.LazyLRU[string, string]) { - keys := make([]string, 1000) - for i := 0; i < len(keys); i++ { - keys[i] = strconv.FormatInt(int64(i), 10) - lru.Set(keys[i], keys[i]) - if i >= 28 { - _, ok := lru.Get("28") // keep 28 hot - require.True(t, ok, "failed on cycle %d", i) - if !ok { - break - } - } - } - _, ok28 := lru.Get("28") - require.True(t, ok28, "28") - _, ok27 := lru.Get("27") - require.False(t, ok27, "27") - }, - ExpectedStats{}. - WithKeysWritten(1000). - WithKeysReadOK(1000+1-28). - WithKeysReadNotFound(1), - ) -} - -func TestPushBeyondCapacitySave28WithMGet(t *testing.T) { - doShardedTest(t, 10, time.Hour, 10, sharded.StringSharder, func(t *testing.T, lru *sharded.LazyLRU[string, string]) { - keys := make([]string, 1000) - for i := 0; i < len(keys); i++ { - keys[i] = strconv.FormatInt(int64(i), 10) - lru.Set(keys[i], keys[i]) - if i >= 28 { - d := lru.MGet("28") // keep 28 hot - _, ok := d["28"] - require.True(t, ok, "failed on cycle %d", i) - if !ok { - break - } - } - } - _, ok28 := lru.Get("28") - require.True(t, ok28, "28") - _, ok27 := lru.Get("27") - require.False(t, ok27, "27") - }, - ExpectedStats{}. - WithKeysWritten(1000). - WithKeysReadOK(1000+1-28). - WithKeysReadNotFound(1), - ) -} - -func TestGetExpired(t *testing.T) { - doShardedTest(t, 10, 0, 10, sharded.StringSharder, func(t *testing.T, lru *sharded.LazyLRU[string, string]) { - lru.Set("a", "a") - require.Equal(t, 1, lru.Len()) - v, ok := lru.Get("a") - require.False(t, ok) - require.Equal(t, "", v) - require.Equal(t, 0, lru.Len()) - }, - ExpectedStats{}. - WithKeysWritten(1). - WithKeysReadExpired(1), - ) -} - -func TestExpireCleanup(t *testing.T) { - doShardedTest(t, 10, 1, 10, sharded.StringSharder, func(t *testing.T, lru *sharded.LazyLRU[string, string]) { - lru.Set("a", "a") - require.Equal(t, 1, lru.Len()) - time.Sleep(time.Millisecond * 100) - require.Equal(t, 0, lru.Len()) - }, - ExpectedStats{}. - WithKeysWritten(1). - WithKeysReaped(1), - ) -} - -func TestMGetSomeExpired(t *testing.T) { - doShardedTest(t, 10, time.Hour, 10, sharded.StringSharder, func(t *testing.T, lru *sharded.LazyLRU[string, string]) { - lru.Set("a", "a") - lru.SetTTL("b", "b", 0) - require.Equal(t, 2, lru.Len()) - vals := lru.MGet("a", "b") - require.Equal(t, 1, len(vals)) - v, ok := vals["a"] - require.True(t, ok) - require.Equal(t, "a", v) - require.Equal(t, 1, lru.Len()) - }, - ExpectedStats{}. - WithKeysWritten(2). - WithKeysReadOK(1). - WithKeysReadExpired(1), - ) -} - -func TestMSetOneItem(t *testing.T) { - doShardedTest(t, 10, time.Hour, 10, sharded.StringSharder, func(t *testing.T, lru *sharded.LazyLRU[string, string]) { - err := lru.MSet([]string{"a"}, []string{"a"}) - require.NoError(t, err) - require.Equal(t, 1, lru.Len()) - vals := lru.MGet("a", "b") - require.Equal(t, 1, len(vals)) - }, - ExpectedStats{}. - WithKeysWritten(1). - WithKeysReadOK(1). - WithKeysReadNotFound(1), - ) -} - -func TestMGetEmpty(t *testing.T) { - doShardedTest(t, 10, time.Hour, 10, sharded.StringSharder, func(t *testing.T, lru *sharded.LazyLRU[string, string]) { - err := lru.MSet(nil, nil) - require.NoError(t, err) - require.Equal(t, 0, lru.Len()) - vals := lru.MGet("a", "b") - require.Equal(t, 0, len(vals)) - }, - ExpectedStats{}. - WithKeysWritten(0). - WithKeysReadOK(0). - WithKeysReadNotFound(2), - ) -} - -func TestMSetEmpty(t *testing.T) { - doShardedTest(t, 10, time.Hour, 10, sharded.StringSharder, func(t *testing.T, lru *sharded.LazyLRU[string, string]) { - vals := lru.MGet() - require.Equal(t, 0, len(vals)) - }, - ExpectedStats{}. - WithKeysWritten(0). - WithKeysReadOK(0). - WithKeysReadNotFound(0), - ) -} - -func TestMSetZeroTTL(t *testing.T) { - doShardedTest(t, 10, 0, 10, sharded.StringSharder, func(t *testing.T, lru *sharded.LazyLRU[string, string]) { - err := lru.MSet([]string{"a"}, []string{"a"}) - require.NoError(t, err) - require.Equal(t, 0, lru.Len()) - vals := lru.MGet("a", "b") - require.Equal(t, 0, len(vals)) - }, - ExpectedStats{}. - WithKeysWritten(0). - WithKeysReadOK(0). - WithKeysReadNotFound(2), - ) -} diff --git a/generic/sharded/sharder.go b/generic/sharded/sharder.go deleted file mode 100644 index fcee7c5..0000000 --- a/generic/sharded/sharder.go +++ /dev/null @@ -1,64 +0,0 @@ -package sharded - -import ( - "sync" - - "github.com/zeebo/xxh3" -) - -// H is an interface to an underlying hasher -// -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -type H interface { - Write(buf []byte) (int, error) - WriteString(buf string) (int, error) - WriteUint64(uint64) - WriteUint32(uint32) - WriteUint16(uint16) - WriteUint8(uint8) - WriteFloat32(float32) - WriteFloat64(float64) - WriteBool(bool) -} - -// these pools allow the sharding operations to amortize to zero allocations -var ( - hpool = sync.Pool{New: func() any { return (*hasher)(xxh3.New()) }} -) - -// StringSharder can be used to shard with string keys -// -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -var StringSharder = HashingSharder(func(k string, h H) { - _, _ = h.WriteString(k) -}) - -// BytesSharder can be used to shard with byte slice keys -// -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -var BytesSharder = HashingSharder(func(k []byte, h H) { - _, _ = h.Write(k) -}) - -// HashingSharder can be used to shard any type that can be written -// to a hasher -// -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -func HashingSharder[K any](f func(K, H)) func(K) uint64 { - return func(key K) uint64 { - h := hpool.Get().(*hasher) - h.Reset() - f(key, h) - retval := h.Sum64() - hpool.Put(h) - return retval - } -} diff --git a/generic/sharded/sharder_test.go b/generic/sharded/sharder_test.go deleted file mode 100644 index b1651d2..0000000 --- a/generic/sharded/sharder_test.go +++ /dev/null @@ -1,203 +0,0 @@ -package sharded_test - -import ( - "math/rand/v2" - "sort" - "strconv" - "testing" - - "github.com/TriggerMail/lazylru/generic/sharded" - "github.com/stretchr/testify/require" -) - -func TestStringSharder(t *testing.T) { - require.Equal(t, - sharded.StringSharder("foo"), - sharded.StringSharder("foo"), - ) - - require.NotEqual(t, - sharded.StringSharder("foo"), - sharded.StringSharder("bar"), - ) -} - -func TestBytesSharder(t *testing.T) { - require.Equal(t, - sharded.BytesSharder([]byte("foo")), - sharded.BytesSharder([]byte("foo")), - ) - - require.NotEqual(t, - sharded.BytesSharder([]byte("foo")), - sharded.BytesSharder([]byte("bar")), - ) -} - -func TestStructSharder(t *testing.T) { - type MyStruct struct { - name string - category string - count int - } - - structSharder := sharded.HashingSharder(func(v MyStruct, h sharded.H) { - _, err := h.WriteString(v.name) - require.NoError(t, err) - _, err = h.WriteString("|") - require.NoError(t, err) - _, err = h.WriteString(v.category) - require.NoError(t, err) - h.WriteUint64(uint64(v.count)) - }) - - testData := []MyStruct{ - {"foo", "cat1", 0}, - {"foo", "cat2", 0}, - {"foo", "cat1", 1}, - {"foo", "cat2", 1}, - {"bar", "cat1", 0}, - {"bar", "cat2", 0}, - {"bar", "cat1", 1}, - {"bar", "cat2", 1}, - } - shards := make([]int, len(testData)) - for i, td := range testData { - shards[i] = int(structSharder(td)) - } - sort.Ints(shards) - for i := 1; i < len(shards); i++ { - require.NotEqual(t, shards[i-1], shards[i]) - } -} - -func TestStructSharderNums(t *testing.T) { - type MyStruct struct { - a int - b int32 - c int16 - d int8 - } - - structSharder := sharded.HashingSharder(func(v MyStruct, h sharded.H) { - h.WriteUint64(uint64(v.a)) - h.WriteUint32(uint32(v.b)) - h.WriteUint16(uint16(v.c)) - h.WriteUint8(uint8(v.d)) - }) - - testData := []MyStruct{ - {0, 0, 0, 0}, - {0, 0, 0, 1}, - {0, 0, 1, 0}, - {0, 0, 1, 1}, - {0, 1, 0, 0}, - {0, 1, 0, 1}, - {0, 1, 1, 0}, - {0, 1, 1, 1}, - {1, 0, 0, 0}, - {1, 0, 0, 1}, - {1, 0, 1, 0}, - {1, 0, 1, 1}, - {1, 1, 0, 0}, - {1, 1, 0, 1}, - {1, 1, 1, 0}, - {1, 1, 1, 1}, - } - shards := make([]int, len(testData)) - for i, td := range testData { - shards[i] = int(structSharder(td)) - } - sort.Ints(shards) - for i := 1; i < len(shards); i++ { - require.NotEqual(t, shards[i-1], shards[i]) - } -} - -func BenchmarkStringSharder(b *testing.B) { - chars := []byte( - "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + - "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + - "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + - "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + - "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") - - for _, keysize := range []int{1, 4, 16, 64, 256} { - b.Run(strconv.Itoa(keysize), func(b *testing.B) { - sources := make([]string, 1000) - for i := 0; i < 1000; i++ { - rand.Shuffle(len(chars), func(i, j int) { - chars[i], chars[j] = chars[j], chars[i] - }) - sources[i] = string(chars[0:keysize]) - } - b.ResetTimer() - for i := 0; i < b.N; i++ { - sharded.StringSharder(sources[i%len(sources)]) - } - }) - } -} - -func BenchmarkBytesSharder(b *testing.B) { - chars := []byte( - "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + - "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + - "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + - "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + - "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") - - for _, keysize := range []int{1, 4, 16, 64, 256} { - b.Run(strconv.Itoa(keysize), func(b *testing.B) { - sources := make([][]byte, 1000) - for i := 0; i < 1000; i++ { - sources[i] = make([]byte, keysize) - rand.Shuffle(len(chars), func(i, j int) { - chars[i], chars[j] = chars[j], chars[i] - }) - copy(sources[i], chars) - } - b.ResetTimer() - for i := 0; i < b.N; i++ { - sharded.BytesSharder(sources[i%len(sources)]) - } - }) - } -} - -func BenchmarkCustomSharder(b *testing.B) { - chars := []byte( - "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + - "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + - "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + - "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + - "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") - - type Custom struct { - a string - b string - c int - } - - sources := make([]Custom, 1000) - for i := 0; i < 1000; i++ { - rand.Shuffle(len(chars), func(i, j int) { - chars[i], chars[j] = chars[j], chars[i] - }) - sources[i] = Custom{ - a: string(chars[0:12]), - b: string(chars[0:12]), - c: i, - } - } - sharder := sharded.HashingSharder(func(k Custom, h sharded.H) { - _, _ = h.WriteString(k.a) - _, _ = h.WriteString("|") - _, _ = h.WriteString(k.b) - h.WriteUint64(uint64(k.c)) - }) - b.ResetTimer() - for i := 0; i < b.N; i++ { - sharder(sources[i%len(sources)]) - } -} diff --git a/generic/stats.go b/generic/stats.go deleted file mode 100644 index 7d5748e..0000000 --- a/generic/stats.go +++ /dev/null @@ -1,17 +0,0 @@ -package lazylru - -// Stats represends counts of actions against the cache. -// -// Deprecated: The "github.com/TriggerMail/lazylru/generic" package has been -// deprecated. Please point all references to "github.com/TriggerMail/lazylru", -// which now includes the generic API. -type Stats struct { - KeysWritten uint32 - KeysReadOK uint32 - KeysReadNotFound uint32 - KeysReadExpired uint32 - Shuffles uint32 - Evictions uint32 - KeysReaped uint32 - ReaperCycles uint32 -} diff --git a/go.mod b/go.mod index af5d2a9..5da847f 100644 --- a/go.mod +++ b/go.mod @@ -4,17 +4,13 @@ go 1.23 require ( github.com/stretchr/testify v1.9.0 - golang.org/x/sync v0.7.0 -) - -require ( - github.com/klauspost/cpuid/v2 v2.2.8 // indirect - golang.org/x/sys v0.21.0 // indirect + github.com/zeebo/xxh3 v1.0.2 + golang.org/x/sync v0.8.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect + github.com/klauspost/cpuid/v2 v2.0.9 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/zeebo/xxh3 v1.0.2 gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 5873294..0356b04 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= -github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= @@ -10,11 +10,8 @@ github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/go.work.sum b/go.work.sum deleted file mode 100644 index 04d558f..0000000 --- a/go.work.sum +++ /dev/null @@ -1,8 +0,0 @@ -github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/zeebo/assert v1.3.1/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= -github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= -go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= -golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/revive.toml b/revive.toml deleted file mode 100644 index 2d4274f..0000000 --- a/revive.toml +++ /dev/null @@ -1,27 +0,0 @@ -# this configuration is the same as the default, but warnings and errors return -# a non-zero value -ignoreGeneratedHeader = false -severity = "warning" -confidence = 0.8 -warningCode = 1 -errorCode = 1 - -[rule.blank-imports] -[rule.context-as-argument] -[rule.context-keys-type] -[rule.dot-imports] -[rule.error-return] -[rule.error-strings] -[rule.error-naming] -[rule.exported] -[rule.if-return] -[rule.increment-decrement] -[rule.var-naming] -[rule.var-declaration] -[rule.package-comments] -[rule.range] -[rule.receiver-naming] -[rule.time-naming] -[rule.unexported-return] -[rule.indent-error-flow] -[rule.errorf] \ No newline at end of file