diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml
new file mode 100644
index 0000000..3fa1c62
--- /dev/null
+++ b/.github/workflows/clippy.yml
@@ -0,0 +1,27 @@
+name: Clippy
+
+on:
+ push:
+ branches: [ main, master ]
+ pull_request:
+ branches: [ main, master ]
+
+env:
+ CARGO_TERM_COLOR: always
+
+jobs:
+ clippy:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Setup Rust
+ uses: actions-rust-lang/setup-rust-toolchain@v1
+ with:
+ toolchain: stable
+ components: clippy
+
+ - name: Run Clippy
+ run: cargo clippy --all-targets --all-features -- -D warnings
+ working-directory: ./rust
\ No newline at end of file
diff --git a/.github/workflows/fmt.yml b/.github/workflows/fmt.yml
new file mode 100644
index 0000000..01a400b
--- /dev/null
+++ b/.github/workflows/fmt.yml
@@ -0,0 +1,27 @@
+name: Format
+
+on:
+ push:
+ branches: [ main, master ]
+ pull_request:
+ branches: [ main, master ]
+
+env:
+ CARGO_TERM_COLOR: always
+
+jobs:
+ fmt:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Setup Rust
+ uses: actions-rust-lang/setup-rust-toolchain@v1
+ with:
+ toolchain: stable
+ components: rustfmt
+
+ - name: Check formatting
+ run: cargo fmt --all -- --check
+ working-directory: ./rust
\ No newline at end of file
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..5d88d4e
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,30 @@
+name: Test
+
+on:
+ push:
+ branches: [ main, master ]
+ pull_request:
+ branches: [ main, master ]
+
+env:
+ CARGO_TERM_COLOR: always
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Setup Rust
+ uses: actions-rust-lang/setup-rust-toolchain@v1
+ with:
+ toolchain: stable
+
+ - name: Run tests
+ run: cargo test --verbose
+ working-directory: ./rust
+
+ - name: Run tests with all features
+ run: cargo test --all-features --verbose
+ working-directory: ./rust
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 5046052..59523fd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,8 @@
*~
-__pycache__/*
\ No newline at end of file
+__pycache__/*
+/rust/target/
+/venv/
+/out/
+target/*
+coverage/*
+.idea/*
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
deleted file mode 100644
index a16e67b..0000000
--- a/.gitlab-ci.yml
+++ /dev/null
@@ -1,54 +0,0 @@
-# Hacked version of cicd template, without the venv. Glued python and go together.
-
-
-stages:
- - test
- - build
-
-# Change pip's cache directory to be inside the project directory since we can
-# only cache local items.
-variables:
- PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
-
-# Pip's cache doesn't store the python packages
-# https://pip.pypa.io/en/stable/topics/caching/
-#
-# If you want to also cache the installed packages, you have to install
-# them in a virtualenv and cache it as well.
-cache:
- paths:
- - .cache/pip
- - venv/
-
-pytest:
- stage: test
- image: python:latest
- script:
- - python --version
- - python test_bufferpool.py
-
-
-gotest:
- stage: test
- image: golang:latest
- script:
-# - go vet $(go list ./... | grep -v /vendor/)
-# - go test -v -race $(go list ./... | grep -v /vendor/)
- - go install gotest.tools/gotestsum@latest
- - cd go
- - gotestsum --junitfile report.xml --format standard-verbose
- artifacts:
- when: always
- reports:
- junit: go/report.xml
-
-compile:
- stage: build
- image: golang:latest
- script:
- - cd go
- - mkdir -p bin
- - go build -o bin ./...
- artifacts:
- paths:
- - go/bin
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..96c8291
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,696 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "anes"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
+
+[[package]]
+name = "anstyle"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"
+
+[[package]]
+name = "autocfg"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
+
+[[package]]
+name = "bufferpool"
+version = "0.1.0"
+dependencies = [
+ "criterion",
+ "fastrand",
+ "rand",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
+
+[[package]]
+name = "cast"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "ciborium"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e"
+dependencies = [
+ "ciborium-io",
+ "ciborium-ll",
+ "serde",
+]
+
+[[package]]
+name = "ciborium-io"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757"
+
+[[package]]
+name = "ciborium-ll"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9"
+dependencies = [
+ "ciborium-io",
+ "half",
+]
+
+[[package]]
+name = "clap"
+version = "4.5.47"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7eac00902d9d136acd712710d71823fb8ac8004ca445a89e73a41d45aa712931"
+dependencies = [
+ "clap_builder",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.5.47"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ad9bbf750e73b5884fb8a211a9424a1906c1e156724260fdae972f31d70e1d6"
+dependencies = [
+ "anstyle",
+ "clap_lex",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
+
+[[package]]
+name = "criterion"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f"
+dependencies = [
+ "anes",
+ "cast",
+ "ciborium",
+ "clap",
+ "criterion-plot",
+ "is-terminal",
+ "itertools",
+ "num-traits",
+ "once_cell",
+ "oorandom",
+ "plotters",
+ "rayon",
+ "regex",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "tinytemplate",
+ "walkdir",
+]
+
+[[package]]
+name = "criterion-plot"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
+dependencies = [
+ "cast",
+ "itertools",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
+dependencies = [
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
+
+[[package]]
+name = "crunchy"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
+
+[[package]]
+name = "either"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
+
+[[package]]
+name = "fastrand"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
+
+[[package]]
+name = "getrandom"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "half"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9"
+dependencies = [
+ "cfg-if",
+ "crunchy",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
+
+[[package]]
+name = "is-terminal"
+version = "0.4.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "itertools"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
+
+[[package]]
+name = "js-sys"
+version = "0.3.78"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c0b063578492ceec17683ef2f8c5e89121fbd0b172cbc280635ab7567db2738"
+dependencies = [
+ "once_cell",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.147"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
+
+[[package]]
+name = "log"
+version = "0.4.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
+
+[[package]]
+name = "memchr"
+version = "2.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.21.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
+
+[[package]]
+name = "oorandom"
+version = "11.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"
+
+[[package]]
+name = "plotters"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747"
+dependencies = [
+ "num-traits",
+ "plotters-backend",
+ "plotters-svg",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "plotters-backend"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a"
+
+[[package]]
+name = "plotters-svg"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670"
+dependencies = [
+ "plotters-backend",
+]
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.101"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "rayon"
+version = "1.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f"
+dependencies = [
+ "either",
+ "rayon-core",
+]
+
+[[package]]
+name = "rayon-core"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
+dependencies = [
+ "crossbeam-deque",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "regex"
+version = "1.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
+
+[[package]]
+name = "rustversion"
+version = "1.0.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
+
+[[package]]
+name = "ryu"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.223"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a505d71960adde88e293da5cb5eda57093379f64e61cf77bf0e6a63af07a7bac"
+dependencies = [
+ "serde_core",
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_core"
+version = "1.0.223"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "20f57cbd357666aa7b3ac84a90b4ea328f1d4ddb6772b430caa5d9e1309bb9e9"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.223"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d428d07faf17e306e699ec1e91996e5a165ba5d6bce5b5155173e91a8a01a56"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.145"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
+dependencies = [
+ "itoa",
+ "memchr",
+ "ryu",
+ "serde",
+ "serde_core",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "tinytemplate"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
+dependencies = [
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
+
+[[package]]
+name = "walkdir"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.101"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e14915cadd45b529bb8d1f343c4ed0ac1de926144b746e2710f9cd05df6603b"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "rustversion",
+ "wasm-bindgen-macro",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.101"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e28d1ba982ca7923fd01448d5c30c6864d0a14109560296a162f80f305fb93bb"
+dependencies = [
+ "bumpalo",
+ "log",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.101"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c3d463ae3eff775b0c45df9da45d68837702ac35af998361e2c84e7c5ec1b0d"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.101"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7bb4ce89b08211f923caf51d527662b75bdc9c9c7aab40f86dcb9fb85ac552aa"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.101"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f143854a3b13752c6950862c906306adb27c7e839f7414cec8fea35beab624c1"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "web-sys"
+version = "0.3.78"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77e4b637749ff0d92b8fad63aa1f7cff3cbe125fd49c175cd6345e7272638b12"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "winapi-util"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
+dependencies = [
+ "windows-sys 0.61.0",
+]
+
+[[package]]
+name = "windows-link"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65"
+
+[[package]]
+name = "windows-sys"
+version = "0.59.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.61.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..4ed6efe
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,3 @@
+[workspace]
+members = ["rust"]
+resolver = "2"
\ No newline at end of file
diff --git a/go/makefile b/go/makefile
new file mode 100644
index 0000000..fc7087b
--- /dev/null
+++ b/go/makefile
@@ -0,0 +1,7 @@
+
+report.xml: $(shell find src/ -type f -name '*.go')
+ gotestsum --junitfile report.xml --format standard-verbose
+
+bin/cmd:
+ mkdir -p bin
+ go build -o bin ./...
diff --git a/go/report.xml b/go/report.xml
new file mode 100644
index 0000000..39705a1
--- /dev/null
+++ b/go/report.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/readme.org b/readme.org
index 84a40d1..d014608 100644
--- a/readme.org
+++ b/readme.org
@@ -23,17 +23,31 @@ simplifies testing, deployments, etc.
Go: half done.
+This will be used in my own projects.
+
** Rust
-In progress. Will be a multi-threaded reference implementation.
+This will be the gold standard implementation.
+
+*** notes
+
+The top-level concept will be to load a block of data into pool and then mutate it via get/write from there.
+
+From a pure refchecking perspective, the conventional references are difficult to address with a lifetime.
+
+Dataframes are therefore Box for now.
+
+
+** Common Lisp
+Sophisticated projects.
* license
(C) Affero GPL v 3.0 - https://www.gnu.org/licenses/agpl-3.0.en.html
- This means, in short, you MUST use share all source code this is linked or associated with.
+ This means, in short, you MUST use share all source code this is linked or associated with.
Contact me if you want to use it commercially.
diff --git a/rust/Cargo.toml b/rust/Cargo.toml
new file mode 100644
index 0000000..7de15b8
--- /dev/null
+++ b/rust/Cargo.toml
@@ -0,0 +1,22 @@
+[package]
+name = "bufferpool"
+version = "0.1.0"
+edition = "2024"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+# TODO: Consider removing serde for json io
+# <= 1.0.171 due to serde-rs/serde#2538
+serde = { version = "1", features = ["derive"] }
+
+rand = "0.8.5"
+serde_json = "1.0.145"
+
+[dev-dependencies]
+criterion = { version = "0.5", features = ["html_reports"] }
+fastrand = "2.0"
+
+[[bench]]
+name = "eviction_benchmark"
+harness = false
diff --git a/rust/benches/eviction_benchmark.rs b/rust/benches/eviction_benchmark.rs
new file mode 100644
index 0000000..5ad48e2
--- /dev/null
+++ b/rust/benches/eviction_benchmark.rs
@@ -0,0 +1,503 @@
+use bufferpool::bufferpool;
+use bufferpool::framepool::{self, FramePool};
+use criterion::{BenchmarkId, Criterion, black_box, criterion_group, criterion_main};
+use std::sync::Arc;
+
+/// Benchmark configuration for eviction strategy analysis
+#[derive(Clone)]
+pub struct BenchmarkConfig {
+ pub name: &'static str,
+ pub buffer_slots: usize,
+ pub total_items: usize,
+ pub access_pattern: AccessPattern,
+ pub workload_type: WorkloadType,
+}
+
+#[derive(Clone)]
+pub enum AccessPattern {
+ Sequential,
+ Random(Vec),
+ Working(Vec), // Simulates working set locality
+ LruWorst, // Pattern designed to defeat LRU
+}
+
+#[derive(Clone)]
+pub enum WorkloadType {
+ ReadOnly,
+ WriteHeavy(f64), // Percentage of write operations
+ Mixed(f64, f64), // (read_pct, write_pct)
+}
+
+/// Performance metrics collected during benchmarking
+#[derive(Debug, Clone)]
+pub struct PerformanceMetrics {
+ pub strategy_name: String,
+ pub buffer_slots: usize,
+ pub total_operations: usize,
+ pub cache_hits: usize,
+ pub cache_misses: usize,
+ pub evictions: usize,
+ pub writes_performed: usize,
+ pub elapsed_nanos: u128,
+}
+
+impl PerformanceMetrics {
+ pub fn hit_rate(&self) -> f64 {
+ self.cache_hits as f64 / (self.cache_hits + self.cache_misses) as f64
+ }
+
+ pub fn operations_per_second(&self) -> f64 {
+ (self.total_operations as f64) / (self.elapsed_nanos as f64 / 1_000_000_000.0)
+ }
+
+ pub fn avg_latency_nanos(&self) -> f64 {
+ self.elapsed_nanos as f64 / self.total_operations as f64
+ }
+}
+
+/// Eviction strategy function type alias
+type EvictionStrategy = fn(
+ &[Option>],
+ &bufferpool::unique_stack::UniqueStack,
+) -> Result;
+
+/// Benchmark runner for eviction strategies
+pub struct EvictionBenchmark {
+ strategies: Vec<(&'static str, EvictionStrategy)>,
+ configs: Vec,
+}
+
+impl Default for EvictionBenchmark {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl EvictionBenchmark {
+ pub fn new() -> Self {
+ Self {
+ strategies: vec![
+ ("bottom_evictor", bufferpool::bottom_evictor),
+ ("random_evictor", bufferpool::random_evictor),
+ ],
+ configs: vec![
+ // Small buffer stress tests
+ BenchmarkConfig {
+ name: "small_buffer_sequential",
+ buffer_slots: 3,
+ total_items: 100,
+ access_pattern: AccessPattern::Sequential,
+ workload_type: WorkloadType::ReadOnly,
+ },
+ BenchmarkConfig {
+ name: "small_buffer_random",
+ buffer_slots: 3,
+ total_items: 100,
+ access_pattern: AccessPattern::Random(
+ (0..1000).map(|_| fastrand::u64(0..100)).collect(),
+ ),
+ workload_type: WorkloadType::ReadOnly,
+ },
+ BenchmarkConfig {
+ name: "working_set_locality",
+ buffer_slots: 5,
+ total_items: 50,
+ access_pattern: AccessPattern::Working(
+ // 80% of accesses to 20% of data (80/20 rule)
+ Self::generate_working_set_pattern(50, 10, 1000),
+ ),
+ workload_type: WorkloadType::ReadOnly,
+ },
+ BenchmarkConfig {
+ name: "mixed_workload",
+ buffer_slots: 8,
+ total_items: 200,
+ access_pattern: AccessPattern::Random(
+ (0..500).map(|_| fastrand::u64(0..200)).collect(),
+ ),
+ workload_type: WorkloadType::Mixed(0.7, 0.3), // 70% read, 30% write
+ },
+ // Medium buffer tests
+ BenchmarkConfig {
+ name: "medium_buffer_stress",
+ buffer_slots: 16,
+ total_items: 1000,
+ access_pattern: AccessPattern::Random(
+ (0..2000).map(|_| fastrand::u64(0..1000)).collect(),
+ ),
+ workload_type: WorkloadType::WriteHeavy(0.4), // 40% writes
+ },
+ // Large dataset tests
+ BenchmarkConfig {
+ name: "large_dataset_scan",
+ buffer_slots: 32,
+ total_items: 10000,
+ access_pattern: AccessPattern::Sequential,
+ workload_type: WorkloadType::ReadOnly,
+ },
+ ],
+ }
+ }
+
+ /// Generate access pattern that simulates working set locality
+ fn generate_working_set_pattern(
+ total_items: usize,
+ working_set_size: usize,
+ num_accesses: usize,
+ ) -> Vec {
+ let mut pattern = Vec::with_capacity(num_accesses);
+
+ for _ in 0..num_accesses {
+ // 80% chance to access working set, 20% chance to access other items
+ if fastrand::f64() < 0.8 {
+ pattern.push(fastrand::u64(0..working_set_size as u64));
+ } else {
+ pattern.push(fastrand::u64(working_set_size as u64..total_items as u64));
+ }
+ }
+
+ pattern
+ }
+
+ /// Run benchmark for a specific strategy and configuration
+ pub fn run_single_benchmark(
+ &self,
+ strategy_name: &str,
+ strategy_fn: EvictionStrategy,
+ config: &BenchmarkConfig,
+ ) -> PerformanceMetrics {
+ let start_time = std::time::Instant::now();
+
+ // Setup memory pool
+ let mut mem_pool = framepool::MemPool::new();
+ as FramePool>::resize(
+ &mut mem_pool,
+ config.total_items as u64,
+ )
+ .unwrap();
+
+ // Initialize data
+ for i in 0..config.total_items {
+ let data = Arc::new(format!("item_{:06}", i));
+ as FramePool>::put_frame(
+ &mut mem_pool,
+ i as u64,
+ data,
+ )
+ .unwrap();
+ }
+
+ // Create buffer pool with the eviction strategy
+ let mut buffer_pool: bufferpool::BufferPool =
+ bufferpool::BufferPool::new(config.buffer_slots, &mut mem_pool, strategy_fn);
+
+ // Generate access sequence based on pattern
+ let access_sequence = self.generate_access_sequence(config);
+
+ let mut cache_hits = 0;
+ let mut cache_misses = 0;
+ let mut writes_performed = 0;
+
+ // Execute the benchmark workload
+ for &idx in &access_sequence {
+ match &config.workload_type {
+ WorkloadType::ReadOnly => {
+ if let Some(_page) = buffer_pool.get_page(idx) {
+ cache_hits += 1;
+ } else {
+ cache_misses += 1;
+ }
+ }
+ WorkloadType::WriteHeavy(write_ratio) => {
+ if fastrand::f64() < *write_ratio {
+ // Write operation
+ if let Some(page) = buffer_pool.get_page(idx) {
+ page.with_data(|data: &mut String| {
+ *data = format!("modified_item_{:06}", idx);
+ });
+ writes_performed += 1;
+ cache_hits += 1;
+ } else {
+ cache_misses += 1;
+ }
+ } else {
+ // Read operation
+ if let Some(_page) = buffer_pool.get_page(idx) {
+ cache_hits += 1;
+ } else {
+ cache_misses += 1;
+ }
+ }
+ }
+ WorkloadType::Mixed(read_ratio, write_ratio) => {
+ let op_type = fastrand::f64();
+ if op_type < *read_ratio {
+ // Read operation
+ if let Some(_page) = buffer_pool.get_page(idx) {
+ cache_hits += 1;
+ } else {
+ cache_misses += 1;
+ }
+ } else if op_type < read_ratio + write_ratio {
+ // Write operation
+ if let Some(page) = buffer_pool.get_page(idx) {
+ page.with_data(|data: &mut String| {
+ *data = format!("modified_item_{:06}", idx);
+ });
+ writes_performed += 1;
+ cache_hits += 1;
+ } else {
+ cache_misses += 1;
+ }
+ }
+ // Remaining percentage is no-op (simulates other system activity)
+ }
+ }
+ }
+
+ let elapsed = start_time.elapsed();
+
+ PerformanceMetrics {
+ strategy_name: strategy_name.to_string(),
+ buffer_slots: config.buffer_slots,
+ total_operations: access_sequence.len(),
+ cache_hits,
+ cache_misses,
+ evictions: cache_misses, // Approximation - each miss likely causes eviction
+ writes_performed,
+ elapsed_nanos: elapsed.as_nanos(),
+ }
+ }
+
+ /// Generate access sequence based on the access pattern
+ fn generate_access_sequence(&self, config: &BenchmarkConfig) -> Vec {
+ match &config.access_pattern {
+ AccessPattern::Sequential => (0..config.total_items)
+ .cycle()
+ .take(config.total_items * 2)
+ .map(|i| i as u64)
+ .collect(),
+ AccessPattern::Random(pattern) => pattern.clone(),
+ AccessPattern::Working(pattern) => pattern.clone(),
+ AccessPattern::LruWorst => {
+ // Generate pattern that's worst case for LRU: access N+1 items repeatedly
+ let mut pattern = Vec::new();
+ for _ in 0..1000 {
+ for i in 0..=(config.buffer_slots) {
+ pattern.push(i as u64);
+ }
+ }
+ pattern
+ }
+ }
+ }
+
+ /// Run comprehensive benchmark suite
+ pub fn run_benchmark_suite(&self) -> Vec {
+ let mut results = Vec::new();
+
+ for config in &self.configs {
+ for (strategy_name, strategy_fn) in &self.strategies {
+ let metrics = self.run_single_benchmark(strategy_name, *strategy_fn, config);
+ results.push(metrics);
+ }
+ }
+
+ results
+ }
+
+ /// Generate detailed performance report
+ pub fn generate_report(results: Vec) -> String {
+ let mut report = String::new();
+ report.push_str("# Eviction Strategy Performance Analysis\n\n");
+
+ // Group results by configuration
+ let mut by_config: std::collections::HashMap> =
+ std::collections::HashMap::new();
+
+ for result in &results {
+ let config_key = format!(
+ "{}_{}_slots",
+ result.buffer_slots,
+ result.total_operations / result.buffer_slots
+ );
+ by_config.entry(config_key).or_default().push(result);
+ }
+
+ for (config_name, config_results) in by_config {
+ report.push_str(&format!("## Configuration: {}\n\n", config_name));
+ report.push_str("| Strategy | Hit Rate | Ops/sec | Avg Latency (ns) | Evictions |\n");
+ report.push_str("|----------|----------|---------|------------------|----------|\n");
+
+ for result in config_results {
+ report.push_str(&format!(
+ "| {} | {:.2}% | {:.0} | {:.2} | {} |\n",
+ result.strategy_name,
+ result.hit_rate() * 100.0,
+ result.operations_per_second(),
+ result.avg_latency_nanos(),
+ result.evictions
+ ));
+ }
+ report.push('\n');
+ }
+
+ report
+ }
+}
+
+/// Criterion benchmark functions
+fn benchmark_eviction_strategies(c: &mut Criterion) {
+ let benchmark = EvictionBenchmark::new();
+
+ let mut group = c.benchmark_group("eviction_strategies");
+
+ // Test different buffer sizes with fixed workload
+ for buffer_size in [2, 4, 8, 16, 32] {
+ let config = BenchmarkConfig {
+ name: "fixed_workload",
+ buffer_slots: buffer_size,
+ total_items: 100,
+ access_pattern: AccessPattern::Random(
+ (0..500).map(|_| fastrand::u64(0..100)).collect(),
+ ),
+ workload_type: WorkloadType::ReadOnly,
+ };
+
+ for (strategy_name, strategy_fn) in &benchmark.strategies {
+ group.bench_with_input(
+ BenchmarkId::new(*strategy_name, buffer_size),
+ &buffer_size,
+ |b, _| {
+ b.iter(|| {
+ black_box(benchmark.run_single_benchmark(
+ strategy_name,
+ *strategy_fn,
+ &config,
+ ))
+ })
+ },
+ );
+ }
+ }
+
+ group.finish();
+}
+
+fn benchmark_slot_allocation_analysis(c: &mut Criterion) {
+ let benchmark = EvictionBenchmark::new();
+
+ let mut group = c.benchmark_group("slot_allocation");
+
+ // Test how performance scales with buffer pool size
+ let total_items = 1000;
+ for buffer_ratio in [0.01, 0.05, 0.1, 0.2, 0.5] {
+ let buffer_slots = ((total_items as f64) * buffer_ratio) as usize;
+ let config = BenchmarkConfig {
+ name: "scaling_test",
+ buffer_slots,
+ total_items,
+ access_pattern: AccessPattern::Random(
+ (0..2000)
+ .map(|_| fastrand::u64(0..total_items as u64))
+ .collect(),
+ ),
+ workload_type: WorkloadType::ReadOnly,
+ };
+
+ group.bench_with_input(
+ BenchmarkId::new("bottom_evictor", format!("{:.0}%", buffer_ratio * 100.0)),
+ &buffer_ratio,
+ |b, _| {
+ b.iter(|| {
+ black_box(benchmark.run_single_benchmark(
+ "bottom_evictor",
+ bufferpool::bottom_evictor,
+ &config,
+ ))
+ })
+ },
+ );
+ }
+
+ group.finish();
+}
+
+criterion_group!(
+ benches,
+ benchmark_eviction_strategies,
+ benchmark_slot_allocation_analysis
+);
+criterion_main!(benches);
+
+#[cfg(test)]
+mod tests {
+ #[allow(unused_imports)]
+ use super::{
+ AccessPattern, BenchmarkConfig, EvictionBenchmark, PerformanceMetrics, WorkloadType,
+ };
+ #[allow(unused_imports)]
+ use bufferpool::bufferpool;
+
+ #[test]
+ fn test_benchmark_runs_successfully() {
+ let benchmark = EvictionBenchmark::new();
+ let config = BenchmarkConfig {
+ name: "test_config",
+ buffer_slots: 2,
+ total_items: 10,
+ access_pattern: AccessPattern::Sequential,
+ workload_type: WorkloadType::ReadOnly,
+ };
+
+ let result =
+ benchmark.run_single_benchmark("bottom_evictor", bufferpool::bottom_evictor, &config);
+
+ assert!(result.total_operations > 0);
+ assert!(result.elapsed_nanos > 0);
+ assert_eq!(result.strategy_name, "bottom_evictor");
+ assert_eq!(result.buffer_slots, 2);
+ }
+
+ #[test]
+ fn test_performance_metrics_calculations() {
+ let metrics = PerformanceMetrics {
+ strategy_name: "test".to_string(),
+ buffer_slots: 4,
+ total_operations: 100,
+ cache_hits: 80,
+ cache_misses: 20,
+ evictions: 20,
+ writes_performed: 10,
+ elapsed_nanos: 1_000_000, // 1ms
+ };
+
+ assert!((metrics.hit_rate() - 0.8).abs() < 0.001);
+ assert!((metrics.operations_per_second() - 100_000.0).abs() < 1.0);
+ assert!((metrics.avg_latency_nanos() - 10_000.0).abs() < 1.0);
+ }
+
+ #[test]
+ fn test_working_set_pattern_generation() {
+ let pattern = EvictionBenchmark::generate_working_set_pattern(100, 20, 1000);
+ assert_eq!(pattern.len(), 1000);
+
+ // Most accesses should be in working set (0-19)
+ let working_set_accesses = pattern.iter().filter(|&&x| x < 20).count();
+ assert!(working_set_accesses > 600); // Should be around 80%
+ }
+
+ #[test]
+ fn test_benchmark_suite_completeness() {
+ let benchmark = EvictionBenchmark::new();
+ let results = benchmark.run_benchmark_suite();
+
+ let expected_results = benchmark.configs.len() * benchmark.strategies.len();
+ assert_eq!(results.len(), expected_results);
+
+ // Each strategy should be tested
+ for (strategy_name, _) in &benchmark.strategies {
+ assert!(results.iter().any(|r| r.strategy_name == *strategy_name));
+ }
+ }
+}
diff --git a/rust/src/bin/benchmark_runner.rs b/rust/src/bin/benchmark_runner.rs
new file mode 100644
index 0000000..29727d2
--- /dev/null
+++ b/rust/src/bin/benchmark_runner.rs
@@ -0,0 +1,595 @@
+use bufferpool::bufferpool;
+use bufferpool::framepool::{self, FramePool};
+use std::sync::Arc;
+use std::time::Instant;
+
+/// Standalone benchmark runner for eviction strategy analysis
+fn main() {
+ println!("BufferPool Eviction Strategy Benchmark");
+ println!("======================================\n");
+
+ let benchmark = EvictionBenchmark::new();
+ let results = benchmark.run_benchmark_suite();
+
+ let report = EvictionBenchmark::generate_report(results);
+ println!("{}", report);
+}
+
+/// Benchmark configuration for eviction strategy analysis
+#[derive(Clone)]
+pub struct BenchmarkConfig {
+ pub name: &'static str,
+ pub buffer_slots: usize,
+ pub total_items: usize,
+ pub access_pattern: AccessPattern,
+ pub workload_type: WorkloadType,
+}
+
+#[derive(Clone)]
+pub enum AccessPattern {
+ Sequential,
+ Random(Vec),
+ Working(Vec), // Simulates working set locality
+ LruWorst, // Pattern designed to defeat LRU-like strategies
+}
+
+#[derive(Clone)]
+pub enum WorkloadType {
+ ReadOnly,
+ WriteHeavy(f64), // Percentage of write operations
+ Mixed(f64, f64), // (read_pct, write_pct)
+}
+
+/// Performance metrics collected during benchmarking
+#[derive(Debug, Clone)]
+pub struct PerformanceMetrics {
+ pub strategy_name: String,
+ pub config_name: String,
+ pub buffer_slots: usize,
+ pub total_items: usize,
+ pub total_operations: usize,
+ pub cache_hits: usize,
+ pub cache_misses: usize,
+ pub evictions: usize,
+ pub writes_performed: usize,
+ pub elapsed_nanos: u128,
+}
+
+impl PerformanceMetrics {
+ pub fn hit_rate(&self) -> f64 {
+ if self.cache_hits + self.cache_misses == 0 {
+ 0.0
+ } else {
+ self.cache_hits as f64 / (self.cache_hits + self.cache_misses) as f64
+ }
+ }
+
+ pub fn operations_per_second(&self) -> f64 {
+ if self.elapsed_nanos == 0 {
+ 0.0
+ } else {
+ (self.total_operations as f64) / (self.elapsed_nanos as f64 / 1_000_000_000.0)
+ }
+ }
+
+ pub fn avg_latency_nanos(&self) -> f64 {
+ if self.total_operations == 0 {
+ 0.0
+ } else {
+ self.elapsed_nanos as f64 / self.total_operations as f64
+ }
+ }
+
+ pub fn miss_rate(&self) -> f64 {
+ 1.0 - self.hit_rate()
+ }
+
+ pub fn evictions_per_1k_ops(&self) -> f64 {
+ if self.total_operations == 0 {
+ 0.0
+ } else {
+ (self.evictions as f64 / self.total_operations as f64) * 1000.0
+ }
+ }
+}
+
+/// Eviction strategy function type alias
+type EvictionStrategy = fn(
+ &[Option>],
+ &bufferpool::unique_stack::UniqueStack,
+) -> Result;
+
+/// Simple random number generator using Linear Congruential Generator
+struct SimpleRng {
+ state: u64,
+}
+
+impl SimpleRng {
+ fn new(seed: u64) -> Self {
+ Self { state: seed }
+ }
+
+ fn next_u64(&mut self) -> u64 {
+ self.state = self.state.wrapping_mul(1103515245).wrapping_add(12345);
+ self.state
+ }
+
+ fn next_range(&mut self, min: u64, max: u64) -> u64 {
+ min + (self.next_u64() % (max - min))
+ }
+
+ fn next_f64(&mut self) -> f64 {
+ (self.next_u64() as f64) / (u64::MAX as f64)
+ }
+}
+
+/// Benchmark runner for eviction strategies
+pub struct EvictionBenchmark {
+ strategies: Vec<(&'static str, EvictionStrategy)>,
+ configs: Vec,
+}
+
+impl Default for EvictionBenchmark {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl EvictionBenchmark {
+ pub fn new() -> Self {
+ Self {
+ strategies: vec![
+ ("bottom_evictor", bufferpool::bottom_evictor),
+ ("random_evictor", bufferpool::random_evictor),
+ ],
+ configs: Self::create_benchmark_configs(),
+ }
+ }
+
+ fn create_benchmark_configs() -> Vec {
+ let mut rng = SimpleRng::new(42);
+
+ vec![
+ // Small buffer stress tests
+ BenchmarkConfig {
+ name: "small_buffer_sequential",
+ buffer_slots: 3,
+ total_items: 50,
+ access_pattern: AccessPattern::Sequential,
+ workload_type: WorkloadType::ReadOnly,
+ },
+ BenchmarkConfig {
+ name: "small_buffer_random",
+ buffer_slots: 3,
+ total_items: 50,
+ access_pattern: AccessPattern::Random(
+ (0..200).map(|_| rng.next_range(0, 50)).collect(),
+ ),
+ workload_type: WorkloadType::ReadOnly,
+ },
+ BenchmarkConfig {
+ name: "working_set_locality",
+ buffer_slots: 5,
+ total_items: 25,
+ access_pattern: AccessPattern::Working(Self::generate_working_set_pattern(
+ 25, 5, 200,
+ )),
+ workload_type: WorkloadType::ReadOnly,
+ },
+ BenchmarkConfig {
+ name: "mixed_workload",
+ buffer_slots: 8,
+ total_items: 100,
+ access_pattern: AccessPattern::Random({
+ let mut rng = SimpleRng::new(123);
+ (0..300).map(|_| rng.next_range(0, 100)).collect()
+ }),
+ workload_type: WorkloadType::Mixed(0.7, 0.3), // 70% read, 30% write
+ },
+ // Medium buffer tests
+ BenchmarkConfig {
+ name: "medium_buffer_stress",
+ buffer_slots: 16,
+ total_items: 200,
+ access_pattern: AccessPattern::Random({
+ let mut rng = SimpleRng::new(456);
+ (0..500).map(|_| rng.next_range(0, 200)).collect()
+ }),
+ workload_type: WorkloadType::WriteHeavy(0.4), // 40% writes
+ },
+ // Large dataset tests
+ BenchmarkConfig {
+ name: "large_dataset_scan",
+ buffer_slots: 32,
+ total_items: 1000,
+ access_pattern: AccessPattern::Sequential,
+ workload_type: WorkloadType::ReadOnly,
+ },
+ // Adversarial patterns
+ BenchmarkConfig {
+ name: "lru_worst_case",
+ buffer_slots: 4,
+ total_items: 10,
+ access_pattern: AccessPattern::LruWorst,
+ workload_type: WorkloadType::ReadOnly,
+ },
+ // Buffer allocation analysis
+ BenchmarkConfig {
+ name: "tiny_buffer_pressure",
+ buffer_slots: 2,
+ total_items: 100,
+ access_pattern: AccessPattern::Random({
+ let mut rng = SimpleRng::new(789);
+ (0..400).map(|_| rng.next_range(0, 100)).collect()
+ }),
+ workload_type: WorkloadType::ReadOnly,
+ },
+ // Guaranteed cache miss scenarios
+ BenchmarkConfig {
+ name: "extreme_pressure",
+ buffer_slots: 1,
+ total_items: 50,
+ access_pattern: AccessPattern::Random({
+ let mut rng = SimpleRng::new(999);
+ (0..200).map(|_| rng.next_range(0, 50)).collect()
+ }),
+ workload_type: WorkloadType::ReadOnly,
+ },
+ BenchmarkConfig {
+ name: "thrashing_scenario",
+ buffer_slots: 3,
+ total_items: 100,
+ access_pattern: AccessPattern::Random({
+ let mut rng = SimpleRng::new(111);
+ // Access pattern that constantly evicts - wide spread across all items
+ (0..500).map(|_| rng.next_range(0, 100)).collect()
+ }),
+ workload_type: WorkloadType::ReadOnly,
+ },
+ BenchmarkConfig {
+ name: "large_buffer_efficiency",
+ buffer_slots: 64,
+ total_items: 100,
+ access_pattern: AccessPattern::Random({
+ let mut rng = SimpleRng::new(321);
+ (0..200).map(|_| rng.next_range(0, 100)).collect()
+ }),
+ workload_type: WorkloadType::ReadOnly,
+ },
+ ]
+ }
+
+ /// Generate access pattern that simulates working set locality
+ fn generate_working_set_pattern(
+ total_items: usize,
+ working_set_size: usize,
+ num_accesses: usize,
+ ) -> Vec {
+ let mut pattern = Vec::with_capacity(num_accesses);
+ let mut rng = SimpleRng::new(42);
+
+ for _ in 0..num_accesses {
+ // 80% chance to access working set, 20% chance to access other items
+ if rng.next_f64() < 0.8 {
+ pattern.push(rng.next_range(0, working_set_size as u64));
+ } else {
+ pattern.push(rng.next_range(working_set_size as u64, total_items as u64));
+ }
+ }
+
+ pattern
+ }
+
+ /// Run benchmark for a specific strategy and configuration
+ pub fn run_single_benchmark(
+ &self,
+ strategy_name: &str,
+ strategy_fn: EvictionStrategy,
+ config: &BenchmarkConfig,
+ ) -> PerformanceMetrics {
+ let start_time = Instant::now();
+
+ // Setup memory pool
+ let mut mem_pool = framepool::MemPool::new();
+ as FramePool>::resize(
+ &mut mem_pool,
+ config.total_items as u64,
+ )
+ .unwrap();
+
+ // Initialize data
+ for i in 0..config.total_items {
+ let data = Arc::new(format!("item_{:06}", i));
+ as FramePool>::put_frame(
+ &mut mem_pool,
+ i as u64,
+ data,
+ )
+ .unwrap();
+ }
+
+ // Create buffer pool with the eviction strategy
+ let mut buffer_pool: bufferpool::BufferPool =
+ bufferpool::BufferPool::new(config.buffer_slots, &mut mem_pool, strategy_fn);
+
+ // Generate access sequence based on pattern
+ let access_sequence = self.generate_access_sequence(config);
+
+ let mut cache_hits = 0;
+ let mut cache_misses = 0;
+ let mut writes_performed = 0;
+ let mut rng = SimpleRng::new(42);
+
+ // Track which pages are currently in the buffer pool to detect hits vs misses
+ let mut pages_in_buffer = std::collections::HashSet::new();
+
+ // Execute the benchmark workload
+ for &idx in &access_sequence {
+ match &config.workload_type {
+ WorkloadType::ReadOnly => {
+ let was_in_buffer = pages_in_buffer.contains(&idx);
+
+ if let Some(_page) = buffer_pool.get_page(idx) {
+ if was_in_buffer {
+ cache_hits += 1;
+ } else {
+ cache_misses += 1;
+ pages_in_buffer.insert(idx);
+
+ // If buffer is full, we need to track what gets evicted
+ if pages_in_buffer.len() > config.buffer_slots {
+ // Simple approximation: assume least recently used was evicted
+ // In reality, this depends on the eviction strategy
+ pages_in_buffer.clear();
+ pages_in_buffer.insert(idx);
+ }
+ }
+ } else {
+ cache_misses += 1;
+ }
+ }
+ WorkloadType::WriteHeavy(write_ratio) => {
+ let was_in_buffer = pages_in_buffer.contains(&idx);
+
+ if rng.next_f64() < *write_ratio {
+ // Write operation
+ if let Some(page) = buffer_pool.get_page(idx) {
+ page.with_data(|data: &mut String| {
+ *data = format!("modified_item_{:06}", idx);
+ });
+ writes_performed += 1;
+
+ if was_in_buffer {
+ cache_hits += 1;
+ } else {
+ cache_misses += 1;
+ pages_in_buffer.insert(idx);
+ if pages_in_buffer.len() > config.buffer_slots {
+ pages_in_buffer.clear();
+ pages_in_buffer.insert(idx);
+ }
+ }
+ } else {
+ cache_misses += 1;
+ }
+ } else {
+ // Read operation
+ if let Some(_page) = buffer_pool.get_page(idx) {
+ if was_in_buffer {
+ cache_hits += 1;
+ } else {
+ cache_misses += 1;
+ pages_in_buffer.insert(idx);
+ if pages_in_buffer.len() > config.buffer_slots {
+ pages_in_buffer.clear();
+ pages_in_buffer.insert(idx);
+ }
+ }
+ } else {
+ cache_misses += 1;
+ }
+ }
+ }
+ WorkloadType::Mixed(read_ratio, write_ratio) => {
+ let was_in_buffer = pages_in_buffer.contains(&idx);
+ let op_type = rng.next_f64();
+
+ if op_type < *read_ratio {
+ // Read operation
+ if let Some(_page) = buffer_pool.get_page(idx) {
+ if was_in_buffer {
+ cache_hits += 1;
+ } else {
+ cache_misses += 1;
+ pages_in_buffer.insert(idx);
+ if pages_in_buffer.len() > config.buffer_slots {
+ pages_in_buffer.clear();
+ pages_in_buffer.insert(idx);
+ }
+ }
+ } else {
+ cache_misses += 1;
+ }
+ } else if op_type < read_ratio + write_ratio {
+ // Write operation
+ if let Some(page) = buffer_pool.get_page(idx) {
+ page.with_data(|data: &mut String| {
+ *data = format!("modified_item_{:06}", idx);
+ });
+ writes_performed += 1;
+
+ if was_in_buffer {
+ cache_hits += 1;
+ } else {
+ cache_misses += 1;
+ pages_in_buffer.insert(idx);
+ if pages_in_buffer.len() > config.buffer_slots {
+ pages_in_buffer.clear();
+ pages_in_buffer.insert(idx);
+ }
+ }
+ } else {
+ cache_misses += 1;
+ }
+ }
+ // Remaining percentage is no-op (simulates other system activity)
+ }
+ }
+ }
+
+ let elapsed = start_time.elapsed();
+
+ PerformanceMetrics {
+ strategy_name: strategy_name.to_string(),
+ config_name: config.name.to_string(),
+ buffer_slots: config.buffer_slots,
+ total_items: config.total_items,
+ total_operations: access_sequence.len(),
+ cache_hits,
+ cache_misses,
+ evictions: cache_misses, // Approximation - each miss likely causes eviction
+ writes_performed,
+ elapsed_nanos: elapsed.as_nanos(),
+ }
+ }
+
+ /// Generate access sequence based on the access pattern
+ fn generate_access_sequence(&self, config: &BenchmarkConfig) -> Vec {
+ match &config.access_pattern {
+ AccessPattern::Sequential => (0..config.total_items)
+ .cycle()
+ .take(config.total_items * 2)
+ .map(|i| i as u64)
+ .collect(),
+ AccessPattern::Random(pattern) => pattern.clone(),
+ AccessPattern::Working(pattern) => pattern.clone(),
+ AccessPattern::LruWorst => {
+ // Generate pattern that's worst case for LRU: access N+1 items repeatedly
+ let mut pattern = Vec::new();
+ for _ in 0..100 {
+ for i in 0..=(config.buffer_slots) {
+ pattern.push(i as u64);
+ }
+ }
+ pattern
+ }
+ }
+ }
+
+ /// Run comprehensive benchmark suite
+ pub fn run_benchmark_suite(&self) -> Vec {
+ let mut results = Vec::new();
+
+ println!("Running benchmark suite...\n");
+
+ for (config_idx, config) in self.configs.iter().enumerate() {
+ println!(
+ "Running config {}/{}: {}",
+ config_idx + 1,
+ self.configs.len(),
+ config.name
+ );
+
+ for (strategy_name, strategy_fn) in &self.strategies {
+ print!(" Testing {} ... ", strategy_name);
+ let metrics = self.run_single_benchmark(strategy_name, *strategy_fn, config);
+ println!(
+ "Hit rate: {:.1}%, Ops/sec: {:.0}",
+ metrics.hit_rate() * 100.0,
+ metrics.operations_per_second()
+ );
+ results.push(metrics);
+ }
+ println!();
+ }
+
+ results
+ }
+
+ /// Generate detailed performance report
+ pub fn generate_report(results: Vec) -> String {
+ let mut report = String::new();
+ report.push_str("# Eviction Strategy Performance Analysis\n\n");
+
+ // Group results by configuration
+ let mut by_config: std::collections::HashMap> =
+ std::collections::HashMap::new();
+
+ for result in &results {
+ by_config
+ .entry(result.config_name.clone())
+ .or_default()
+ .push(result);
+ }
+
+ // Sort configs by name for consistent output
+ let mut config_names: Vec<_> = by_config.keys().cloned().collect();
+ config_names.sort();
+
+ for config_name in config_names {
+ let config_results = by_config.get(&config_name).unwrap();
+ let first_result = config_results[0];
+
+ report.push_str(&format!("## {}\n", config_name));
+ report.push_str(&format!("- Buffer slots: {}\n", first_result.buffer_slots));
+ report.push_str(&format!("- Total items: {}\n", first_result.total_items));
+ report.push_str(&format!(
+ "- Operations: {}\n\n",
+ first_result.total_operations
+ ));
+
+ report.push_str("| Strategy | Hit Rate | Miss Rate | Ops/sec | Avg Latency (ns) | Evictions/1k ops |\n");
+ report.push_str("|----------|----------|-----------|---------|------------------|------------------|\n");
+
+ for result in config_results {
+ report.push_str(&format!(
+ "| {} | {:.1}% | {:.1}% | {:.0} | {:.1} | {:.1} |\n",
+ result.strategy_name,
+ result.hit_rate() * 100.0,
+ result.miss_rate() * 100.0,
+ result.operations_per_second(),
+ result.avg_latency_nanos(),
+ result.evictions_per_1k_ops()
+ ));
+ }
+ report.push('\n');
+ }
+
+ // Add summary analysis
+ report.push_str("## Summary Analysis\n\n");
+
+ // Calculate average performance by strategy
+ let mut strategy_totals: std::collections::HashMap =
+ std::collections::HashMap::new();
+
+ for result in &results {
+ let entry = strategy_totals
+ .entry(result.strategy_name.clone())
+ .or_insert((0.0, 0.0, 0));
+ entry.0 += result.hit_rate();
+ entry.1 += result.operations_per_second();
+ entry.2 += 1;
+ }
+
+ report.push_str("### Average Performance by Strategy\n\n");
+ report.push_str("| Strategy | Avg Hit Rate | Avg Ops/sec |\n");
+ report.push_str("|----------|--------------|-------------|\n");
+
+ for (strategy, (hit_rate_sum, ops_sum, count)) in strategy_totals {
+ report.push_str(&format!(
+ "| {} | {:.1}% | {:.0} |\n",
+ strategy,
+ (hit_rate_sum / count as f64) * 100.0,
+ ops_sum / count as f64
+ ));
+ }
+
+ report.push_str("\n### Key Insights\n\n");
+ report.push_str("- **Buffer Size Impact**: Larger buffers generally improve hit rates but show diminishing returns\n");
+ report.push_str("- **Access Pattern Sensitivity**: Random access patterns stress eviction strategies more than sequential\n");
+ report.push_str("- **Working Set Locality**: Strategies perform better when access patterns exhibit temporal locality\n");
+ report.push_str("- **Write Performance**: Mixed workloads can reduce effective cache performance due to dirty page management\n");
+
+ report
+ }
+}
diff --git a/rust/src/bufferpool/mod.rs b/rust/src/bufferpool/mod.rs
new file mode 100644
index 0000000..7973b08
--- /dev/null
+++ b/rust/src/bufferpool/mod.rs
@@ -0,0 +1,936 @@
+use rand;
+use rand::{Rng, thread_rng};
+use std::collections::HashMap;
+use std::sync::Arc;
+
+// Re-export modules for integration tests
+pub use crate::framepool;
+pub use crate::unique_stack;
+
+type BufferPoolId = u64;
+type FramePoolId = u64;
+
+type EvictorFn = fn(
+ &[Option>],
+ &unique_stack::UniqueStack,
+) -> Result;
+
+#[derive(Debug)]
+pub enum BufferPoolErrors {
+ NoEvictablePage,
+ NoPageAvailable,
+}
+
+impl std::fmt::Display for BufferPoolErrors {
+ fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
+ fmt.write_str(match self {
+ Self::NoEvictablePage => "no evictable pages",
+ Self::NoPageAvailable => "no available pages",
+ })
+ }
+}
+
+impl std::error::Error for BufferPoolErrors {}
+
+pub fn random_evictor(
+ pages: &[Option>],
+ _: &unique_stack::UniqueStack,
+) -> Result {
+ let mut rng = thread_rng();
+ let len = pages.len();
+ let mut trials = 0;
+ loop {
+ let n: usize = rng.gen_range(0..len);
+ match &pages[n as usize] {
+ None => continue,
+ Some(page) => {
+ if page.is_pinned() {
+ trials += 1;
+ if trials > len {
+ return Err(BufferPoolErrors::NoEvictablePage);
+ }
+ continue;
+ } else {
+ return Ok(n as BufferPoolId);
+ }
+ }
+ }
+ }
+}
+
+pub fn bottom_evictor(
+ pages: &[Option>],
+ lru: &unique_stack::UniqueStack,
+) -> Result
+where
+ T: Clone,
+{
+ for i in lru.order() {
+ match &pages[i as usize] {
+ None => continue,
+ Some(page) => {
+ if page.is_pinned() {
+ continue;
+ } else {
+ return Ok(i as BufferPoolId);
+ }
+ }
+ }
+ }
+ Err(BufferPoolErrors::NoEvictablePage)
+}
+
+pub struct BufferPool<'a, T>
+where
+ T: Clone,
+{
+ // number of pages this bufferpool holds
+ size: usize,
+ // the pages that are loaded
+ // None indicates an unloaded page.
+ // BufferPoolIDs index into this.
+ pages: Vec