diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..aaaff79 --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,50 @@ +name: Rust + +on: + push: + branches: main + paths: + - 'rust/**' + - '.github/workflows/rust.yml' + pull_request: + paths: + - 'rust/**' + - '.github/workflows/rust.yml' + +env: + CARGO_TERM_COLOR: always + RUST_BACKTRACE: 1 + +defaults: + run: + working-directory: rust + +jobs: + test: + name: Test (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + steps: + - uses: actions/checkout@v4 + + - name: Setup Rust + uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly-2022-08-22 + + - name: Cache cargo registry + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + rust/target + key: ${{ runner.os }}-cargo-${{ hashFiles('rust/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo- + + - name: Run tests + run: cargo test --release diff --git a/rust/.gitignore b/rust/.gitignore new file mode 100644 index 0000000..944611a --- /dev/null +++ b/rust/.gitignore @@ -0,0 +1,3 @@ +/target/ +*.db +*.links diff --git a/rust/Cargo.lock b/rust/Cargo.lock new file mode 100644 index 0000000..cdc7556 --- /dev/null +++ b/rust/Cargo.lock @@ -0,0 +1,875 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +dependencies = [ + "memchr", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "beef" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bumpalo" +version = "3.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cc" +version = "1.2.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "bitflags", + "textwrap", + "unicode-width", +] + +[[package]] +name = "criterion" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b01d6de93b2b6c65e17c634a26653a29d107b3c98c607c765bf38d041531cd8f" +dependencies = [ + "atty", + "cast", + "clap", + "criterion-plot", + "csv", + "itertools", + "lazy_static", + "num-traits", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_cbor", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2673cc8207403546f45f5fd319a974b1e6983ad1a3ee7e6041650013be041876" +dependencies = [ + "cast", + "itertools", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[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 = "csv" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782" +dependencies = [ + "memchr", +] + +[[package]] +name = "delegate" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d70a2d4995466955a415223acf3c9c934b9ff2339631cdf4ffc893da4bacd717" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "doublets" +version = "0.1.0-pre+beta.15" +source = "git+https://github.com/linksplatform/doublets-rs.git?rev=5522d91c536654934b7181829f6efb570fb8bb44#5522d91c536654934b7181829f6efb570fb8bb44" +dependencies = [ + "bumpalo", + "cfg-if", + "leak_slice", + "platform-data", + "platform-mem", + "platform-trees", + "tap", + "thiserror", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + +[[package]] +name = "fastrand" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +dependencies = [ + "instant", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41" + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "half" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403" + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashlink" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" + +[[package]] +name = "js-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leak_slice" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecf3387da9fb41906394e1306ddd3cd26dd9b7177af11c19b45b364b743aed26" + +[[package]] +name = "libc" +version = "0.2.180" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" + +[[package]] +name = "libsqlite3-sys" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "898745e570c7d0453cc1fbc4a701eb6c662ed54e8fec8b7d14be137ebeeb9d14" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "memmap2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" +dependencies = [ + "libc", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi 0.5.2", + "libc", +] + +[[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 = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "platform-data" +version = "0.1.0-beta.3" +source = "git+https://github.com/linksplatform/doublets-rs.git?rev=5522d91c536654934b7181829f6efb570fb8bb44#5522d91c536654934b7181829f6efb570fb8bb44" +dependencies = [ + "beef", + "funty", + "thiserror", +] + +[[package]] +name = "platform-mem" +version = "0.1.0-pre+beta.2" +source = "git+https://github.com/linksplatform/doublets-rs.git?rev=5522d91c536654934b7181829f6efb570fb8bb44#5522d91c536654934b7181829f6efb570fb8bb44" +dependencies = [ + "delegate", + "memmap2", + "tap", + "tempfile", + "thiserror", +] + +[[package]] +name = "platform-trees" +version = "0.1.0-beta.1" +source = "git+https://github.com/linksplatform/doublets-rs.git?rev=5522d91c536654934b7181829f6efb570fb8bb44#5522d91c536654934b7181829f6efb570fb8bb44" +dependencies = [ + "funty", + "platform-data", +] + +[[package]] +name = "plotters" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a15b6eccb8484002195a3e44fe65a4ce8e93a625797a063735536fd59cb01cf3" +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 = "proc-macro2" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rayon" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "356a0625f1954f730c0201cdab48611198dc6ce21f4acff55089b5a78e6e835b" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "num_cpus", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "rusqlite" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85127183a999f7db96d1a976a309eebbfb6ea3b0b400ddd8340190129de6eb7a" +dependencies = [ + "bitflags", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "memchr", + "smallvec", +] + +[[package]] +name = "ryu" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" + +[[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.185" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be9b6f69f1dfd54c3b568ffa45c310d6973a5e5148fd40cf515acaf38cf5bc31" + +[[package]] +name = "serde_cbor" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" +dependencies = [ + "half", + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.136" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0652c533506ad7a2e353cce269330d6afd8bdfb6d75e0ace5b35aacbd7b9e9" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "sqlite-vs-doublets" +version = "0.1.0" +dependencies = [ + "bumpalo", + "criterion", + "doublets", + "fastrand", + "getrandom", + "itoa", + "once_cell", + "proc-macro2", + "quote", + "rayon", + "rayon-core", + "regex", + "regex-automata", + "regex-syntax", + "rusqlite", + "ryu", + "same-file", + "serde_derive", + "syn", + "tempfile", + "unicode-width", + "walkdir", + "winapi-util", +] + +[[package]] +name = "syn" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "thiserror" +version = "1.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5ab016db510546d856297882807df8da66a16fb8c4101cb8b30054b0d5b2d9c" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[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-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" + +[[package]] +name = "web-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/rust/Cargo.toml b/rust/Cargo.toml new file mode 100644 index 0000000..463bfc8 --- /dev/null +++ b/rust/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "sqlite-vs-doublets" +version = "0.1.0" +edition = "2021" +license = "Unlicense" +description = "Benchmark comparing SQLite vs Doublets performance for basic CRUD operations" + +[[bench]] +name = "bench" +harness = false + +[dependencies] +rusqlite = { version = "=0.27.0", features = ["bundled"] } +doublets = { git = "https://github.com/linksplatform/doublets-rs.git", rev = "5522d91c536654934b7181829f6efb570fb8bb44" } +once_cell = "1.14" + +[dev-dependencies] +criterion = { version = "=0.3.6", default-features = false } + +# Pin dependencies for compatibility with nightly-2022-08-22 (rustc 1.65) +serde_derive = "=1.0.136" +proc-macro2 = "=1.0.36" +quote = "=1.0.15" +syn = "=1.0.86" +bumpalo = "=3.11.1" +rayon = "=1.6.1" +rayon-core = "=1.10.2" +itoa = "=1.0.5" +ryu = "=1.0.12" +unicode-width = "=0.1.10" +tempfile = "=3.3.0" +fastrand = "=1.8.0" +getrandom = "=0.2.8" +winapi-util = "=0.1.5" +walkdir = "=2.3.2" +same-file = "=1.0.6" +regex = "=1.7.1" +regex-automata = "=0.1.10" +regex-syntax = "=0.6.28" + +[profile.release] +lto = true +codegen-units = 1 diff --git a/rust/benches/bench.rs b/rust/benches/bench.rs new file mode 100644 index 0000000..7d58782 --- /dev/null +++ b/rust/benches/bench.rs @@ -0,0 +1,391 @@ +//! Benchmark suite comparing SQLite vs Doublets performance +//! +//! This benchmark measures basic CRUD operations with links on both storage systems. + +use criterion::{criterion_group, criterion_main, Criterion, Throughput}; +use sqlite_vs_doublets::{ + benched::{DoubletsSplitVolatileBenched, DoubletsUnitedVolatileBenched, SqliteMemoryBenched}, + Benched, Links, BACKGROUND_LINK_COUNT, BENCHMARK_LINK_COUNT, +}; +use std::time::Duration; + +/// Run create operations benchmark +fn bench_create(c: &mut Criterion) { + let mut group = c.benchmark_group("create"); + let link_count = *BENCHMARK_LINK_COUNT; + group.throughput(Throughput::Elements(link_count as u64)); + + group.bench_function("SQLite_Memory", |b| { + let mut benched = SqliteMemoryBenched::setup(()); + b.iter(|| { + let mut fork = benched.fork(); + // Create background links + for _ in 0..*BACKGROUND_LINK_COUNT { + fork.create_point(); + } + // Benchmark operation + for _ in 0..link_count { + fork.create_point(); + } + }); + }); + + group.bench_function("Doublets_United_Volatile", |b| { + let mut benched = DoubletsUnitedVolatileBenched::setup(()); + b.iter(|| { + let mut fork = benched.fork(); + for _ in 0..*BACKGROUND_LINK_COUNT { + fork.create_point(); + } + for _ in 0..link_count { + fork.create_point(); + } + }); + }); + + group.bench_function("Doublets_Split_Volatile", |b| { + let mut benched = DoubletsSplitVolatileBenched::setup(()); + b.iter(|| { + let mut fork = benched.fork(); + for _ in 0..*BACKGROUND_LINK_COUNT { + fork.create_point(); + } + for _ in 0..link_count { + fork.create_point(); + } + }); + }); + + group.finish(); +} + +/// Run delete operations benchmark +fn bench_delete(c: &mut Criterion) { + let mut group = c.benchmark_group("delete"); + let link_count = *BENCHMARK_LINK_COUNT; + group.throughput(Throughput::Elements(link_count as u64)); + + group.bench_function("SQLite_Memory", |b| { + let mut benched = SqliteMemoryBenched::setup(()); + b.iter(|| { + let mut fork = benched.fork(); + // Create links to delete + let mut ids = Vec::with_capacity(link_count); + for _ in 0..link_count { + ids.push(fork.create_point()); + } + // Benchmark delete + for id in ids { + fork.delete(id); + } + }); + }); + + group.bench_function("Doublets_United_Volatile", |b| { + let mut benched = DoubletsUnitedVolatileBenched::setup(()); + b.iter(|| { + let mut fork = benched.fork(); + let mut ids = Vec::with_capacity(link_count); + for _ in 0..link_count { + ids.push(fork.create_point()); + } + for id in ids { + fork.delete(id); + } + }); + }); + + group.bench_function("Doublets_Split_Volatile", |b| { + let mut benched = DoubletsSplitVolatileBenched::setup(()); + b.iter(|| { + let mut fork = benched.fork(); + let mut ids = Vec::with_capacity(link_count); + for _ in 0..link_count { + ids.push(fork.create_point()); + } + for id in ids { + fork.delete(id); + } + }); + }); + + group.finish(); +} + +/// Run update operations benchmark +fn bench_update(c: &mut Criterion) { + let mut group = c.benchmark_group("update"); + let link_count = *BENCHMARK_LINK_COUNT; + group.throughput(Throughput::Elements(link_count as u64)); + + group.bench_function("SQLite_Memory", |b| { + let mut benched = SqliteMemoryBenched::setup(()); + b.iter(|| { + let mut fork = benched.fork(); + // Create links to update + let mut ids = Vec::with_capacity(link_count); + for _ in 0..link_count { + ids.push(fork.create_point()); + } + // Benchmark update + for (i, id) in ids.iter().enumerate() { + fork.update(*id, (i + 1) as u64, (i + 2) as u64); + } + }); + }); + + group.bench_function("Doublets_United_Volatile", |b| { + let mut benched = DoubletsUnitedVolatileBenched::setup(()); + b.iter(|| { + let mut fork = benched.fork(); + let mut ids = Vec::with_capacity(link_count); + for _ in 0..link_count { + ids.push(fork.create_point()); + } + for (i, id) in ids.iter().enumerate() { + fork.update(*id, (i + 1) as u64, (i + 2) as u64); + } + }); + }); + + group.bench_function("Doublets_Split_Volatile", |b| { + let mut benched = DoubletsSplitVolatileBenched::setup(()); + b.iter(|| { + let mut fork = benched.fork(); + let mut ids = Vec::with_capacity(link_count); + for _ in 0..link_count { + ids.push(fork.create_point()); + } + for (i, id) in ids.iter().enumerate() { + fork.update(*id, (i + 1) as u64, (i + 2) as u64); + } + }); + }); + + group.finish(); +} + +/// Run query all benchmark +fn bench_query_all(c: &mut Criterion) { + let mut group = c.benchmark_group("query_all"); + let link_count = *BENCHMARK_LINK_COUNT; + group.throughput(Throughput::Elements(link_count as u64)); + + group.bench_function("SQLite_Memory", |b| { + let mut benched = SqliteMemoryBenched::setup(()); + b.iter(|| { + let mut fork = benched.fork(); + // Create links to query + for _ in 0..link_count { + fork.create_point(); + } + // Benchmark query + let _ = fork.query_all(); + }); + }); + + group.bench_function("Doublets_United_Volatile", |b| { + let mut benched = DoubletsUnitedVolatileBenched::setup(()); + b.iter(|| { + let mut fork = benched.fork(); + for _ in 0..link_count { + fork.create_point(); + } + let _ = fork.query_all(); + }); + }); + + group.bench_function("Doublets_Split_Volatile", |b| { + let mut benched = DoubletsSplitVolatileBenched::setup(()); + b.iter(|| { + let mut fork = benched.fork(); + for _ in 0..link_count { + fork.create_point(); + } + let _ = fork.query_all(); + }); + }); + + group.finish(); +} + +/// Run query by ID benchmark +fn bench_query_by_id(c: &mut Criterion) { + let mut group = c.benchmark_group("query_by_id"); + let link_count = *BENCHMARK_LINK_COUNT; + group.throughput(Throughput::Elements(link_count as u64)); + + group.bench_function("SQLite_Memory", |b| { + let mut benched = SqliteMemoryBenched::setup(()); + b.iter(|| { + let mut fork = benched.fork(); + // Create links + let mut ids = Vec::with_capacity(link_count); + for _ in 0..link_count { + ids.push(fork.create_point()); + } + // Benchmark query + for id in &ids { + let _ = fork.query_by_id(*id); + } + }); + }); + + group.bench_function("Doublets_United_Volatile", |b| { + let mut benched = DoubletsUnitedVolatileBenched::setup(()); + b.iter(|| { + let mut fork = benched.fork(); + let mut ids = Vec::with_capacity(link_count); + for _ in 0..link_count { + ids.push(fork.create_point()); + } + for id in &ids { + let _ = fork.query_by_id(*id); + } + }); + }); + + group.bench_function("Doublets_Split_Volatile", |b| { + let mut benched = DoubletsSplitVolatileBenched::setup(()); + b.iter(|| { + let mut fork = benched.fork(); + let mut ids = Vec::with_capacity(link_count); + for _ in 0..link_count { + ids.push(fork.create_point()); + } + for id in &ids { + let _ = fork.query_by_id(*id); + } + }); + }); + + group.finish(); +} + +/// Run query by source benchmark +fn bench_query_by_source(c: &mut Criterion) { + let mut group = c.benchmark_group("query_by_source"); + let link_count = *BENCHMARK_LINK_COUNT; + group.throughput(Throughput::Elements(link_count as u64)); + + group.bench_function("SQLite_Memory", |b| { + let mut benched = SqliteMemoryBenched::setup(()); + b.iter(|| { + let mut fork = benched.fork(); + // Create links with various sources + for i in 0..link_count { + let id = fork.create_point(); + fork.update(id, (i % 10 + 1) as u64, id); + } + // Benchmark query + for i in 1..=10 { + let _ = fork.query_by_source(i as u64); + } + }); + }); + + group.bench_function("Doublets_United_Volatile", |b| { + let mut benched = DoubletsUnitedVolatileBenched::setup(()); + b.iter(|| { + let mut fork = benched.fork(); + for i in 0..link_count { + let id = fork.create_point(); + fork.update(id, (i % 10 + 1) as u64, id); + } + for i in 1..=10 { + let _ = fork.query_by_source(i as u64); + } + }); + }); + + group.bench_function("Doublets_Split_Volatile", |b| { + let mut benched = DoubletsSplitVolatileBenched::setup(()); + b.iter(|| { + let mut fork = benched.fork(); + for i in 0..link_count { + let id = fork.create_point(); + fork.update(id, (i % 10 + 1) as u64, id); + } + for i in 1..=10 { + let _ = fork.query_by_source(i as u64); + } + }); + }); + + group.finish(); +} + +/// Run query by target benchmark +fn bench_query_by_target(c: &mut Criterion) { + let mut group = c.benchmark_group("query_by_target"); + let link_count = *BENCHMARK_LINK_COUNT; + group.throughput(Throughput::Elements(link_count as u64)); + + group.bench_function("SQLite_Memory", |b| { + let mut benched = SqliteMemoryBenched::setup(()); + b.iter(|| { + let mut fork = benched.fork(); + // Create links with various targets + for i in 0..link_count { + let id = fork.create_point(); + fork.update(id, id, (i % 10 + 1) as u64); + } + // Benchmark query + for i in 1..=10 { + let _ = fork.query_by_target(i as u64); + } + }); + }); + + group.bench_function("Doublets_United_Volatile", |b| { + let mut benched = DoubletsUnitedVolatileBenched::setup(()); + b.iter(|| { + let mut fork = benched.fork(); + for i in 0..link_count { + let id = fork.create_point(); + fork.update(id, id, (i % 10 + 1) as u64); + } + for i in 1..=10 { + let _ = fork.query_by_target(i as u64); + } + }); + }); + + group.bench_function("Doublets_Split_Volatile", |b| { + let mut benched = DoubletsSplitVolatileBenched::setup(()); + b.iter(|| { + let mut fork = benched.fork(); + for i in 0..link_count { + let id = fork.create_point(); + fork.update(id, id, (i % 10 + 1) as u64); + } + for i in 1..=10 { + let _ = fork.query_by_target(i as u64); + } + }); + }); + + group.finish(); +} + +/// Configure criterion for faster CI runs +fn configure_criterion() -> Criterion { + Criterion::default() + .sample_size(10) + .measurement_time(Duration::from_secs(1)) + .warm_up_time(Duration::from_millis(500)) +} + +criterion_group! { + name = benches; + config = configure_criterion(); + targets = bench_create, + bench_delete, + bench_update, + bench_query_all, + bench_query_by_id, + bench_query_by_source, + bench_query_by_target +} + +criterion_main!(benches); diff --git a/rust/rust-toolchain.toml b/rust/rust-toolchain.toml new file mode 100644 index 0000000..33c06c9 --- /dev/null +++ b/rust/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "nightly-2022-08-22" +components = ["rustfmt", "clippy"] diff --git a/rust/rustfmt.toml b/rust/rustfmt.toml new file mode 100644 index 0000000..79f8a99 --- /dev/null +++ b/rust/rustfmt.toml @@ -0,0 +1,2 @@ +edition = "2021" +tab_spaces = 4 diff --git a/rust/src/benched/doublets_benched.rs b/rust/src/benched/doublets_benched.rs new file mode 100644 index 0000000..a1613f2 --- /dev/null +++ b/rust/src/benched/doublets_benched.rs @@ -0,0 +1,81 @@ +//! Benched implementations for Doublets + +use crate::{Benched, Fork, Links}; +use crate::doublets_impl::{ + create_split_volatile, create_united_volatile, DoubletsLinks, DoubletsSplitVolatile, + DoubletsUnitedVolatile, +}; + +/// Benched implementation for Doublets United (unit) store with volatile storage +pub struct DoubletsUnitedVolatileBenched { + links: DoubletsLinks, +} + +impl Benched for DoubletsUnitedVolatileBenched { + type Builder = (); + + fn setup(_builder: Self::Builder) -> Self { + Self { + links: create_united_volatile(), + } + } + + fn fork(&mut self) -> Fork { + Fork::new(self) + } + + unsafe fn unfork(&mut self) { + self.links.delete_all(); + } +} + +impl std::ops::Deref for DoubletsUnitedVolatileBenched { + type Target = DoubletsLinks; + + fn deref(&self) -> &Self::Target { + &self.links + } +} + +impl std::ops::DerefMut for DoubletsUnitedVolatileBenched { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.links + } +} + +/// Benched implementation for Doublets Split store with volatile storage +pub struct DoubletsSplitVolatileBenched { + links: DoubletsLinks, +} + +impl Benched for DoubletsSplitVolatileBenched { + type Builder = (); + + fn setup(_builder: Self::Builder) -> Self { + Self { + links: create_split_volatile(), + } + } + + fn fork(&mut self) -> Fork { + Fork::new(self) + } + + unsafe fn unfork(&mut self) { + self.links.delete_all(); + } +} + +impl std::ops::Deref for DoubletsSplitVolatileBenched { + type Target = DoubletsLinks; + + fn deref(&self) -> &Self::Target { + &self.links + } +} + +impl std::ops::DerefMut for DoubletsSplitVolatileBenched { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.links + } +} diff --git a/rust/src/benched/mod.rs b/rust/src/benched/mod.rs new file mode 100644 index 0000000..3e5be02 --- /dev/null +++ b/rust/src/benched/mod.rs @@ -0,0 +1,27 @@ +//! Benched trait and implementations for SQLite and Doublets + +mod doublets_benched; +mod sqlite_benched; + +pub use doublets_benched::*; +pub use sqlite_benched::*; + +use crate::Fork; + +/// Trait for types that can be benchmarked with setup/teardown lifecycle +pub trait Benched: Sized { + /// The builder type used to construct this benched type + type Builder; + + /// Setup the benchmark environment + fn setup(builder: Self::Builder) -> Self; + + /// Create a fork for an isolated benchmark iteration + fn fork(&mut self) -> Fork; + + /// Clean up after a benchmark iteration + /// + /// # Safety + /// This should only be called by Fork's Drop implementation + unsafe fn unfork(&mut self); +} diff --git a/rust/src/benched/sqlite_benched.rs b/rust/src/benched/sqlite_benched.rs new file mode 100644 index 0000000..bc9caa6 --- /dev/null +++ b/rust/src/benched/sqlite_benched.rs @@ -0,0 +1,41 @@ +//! Benched implementation for SQLite + +use crate::{Benched, Fork}; +use crate::sqlite_impl::SqliteLinks; + +/// Benched implementation for SQLite with in-memory database +pub struct SqliteMemoryBenched { + links: SqliteLinks, +} + +impl Benched for SqliteMemoryBenched { + type Builder = (); + + fn setup(_builder: Self::Builder) -> Self { + Self { + links: SqliteLinks::new_memory(), + } + } + + fn fork(&mut self) -> Fork { + Fork::new(self) + } + + unsafe fn unfork(&mut self) { + self.links.reset(); + } +} + +impl std::ops::Deref for SqliteMemoryBenched { + type Target = SqliteLinks; + + fn deref(&self) -> &Self::Target { + &self.links + } +} + +impl std::ops::DerefMut for SqliteMemoryBenched { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.links + } +} diff --git a/rust/src/doublets_impl.rs b/rust/src/doublets_impl.rs new file mode 100644 index 0000000..9cb2760 --- /dev/null +++ b/rust/src/doublets_impl.rs @@ -0,0 +1,186 @@ +//! Doublets storage implementation for links + +use crate::{Link, Links}; +use doublets::{ + mem::Alloc, + split::{self, DataPart, IndexPart}, + unit::{self, LinkPart}, + Doublets, DoubletsExt, +}; +use std::alloc::Global; + +/// Type alias for Doublets united (unit) store with volatile (in-memory) storage. +/// Each link is stored as a contiguous unit containing (id, source, target). +pub type DoubletsUnitedVolatile = unit::Store, Global>>; + +/// Type alias for Doublets split store with volatile (in-memory) storage. +/// Separates data and index into different memory regions for better cache efficiency. +pub type DoubletsSplitVolatile = + split::Store, Global>, Alloc, Global>>; + +/// Wrapper to adapt doublets::Doublets to our Links trait +pub struct DoubletsLinks { + store: S, +} + +impl DoubletsLinks { + pub fn new(store: S) -> Self { + Self { store } + } + + pub fn into_inner(self) -> S { + self.store + } +} + +impl + DoubletsExt> Links for DoubletsLinks { + fn create(&mut self, source: u64, target: u64) -> u64 { + self.store + .create_by([source as usize, target as usize]) + .expect("Failed to create link") as u64 + } + + fn create_point(&mut self) -> u64 { + self.store.create_point().expect("Failed to create point") as u64 + } + + fn update(&mut self, id: u64, source: u64, target: u64) { + self.store + .update(id as usize, source as usize, target as usize) + .expect("Failed to update link"); + } + + fn delete(&mut self, id: u64) { + self.store + .delete(id as usize) + .expect("Failed to delete link"); + } + + fn delete_all(&mut self) { + let any = self.store.constants().any; + let ids: Vec = self + .store + .each_iter([any, any, any]) + .map(|link| link.index) + .collect(); + for id in ids { + let _ = self.store.delete(id); + } + } + + fn query_all(&self) -> Vec { + let any = self.store.constants().any; + self.store + .each_iter([any, any, any]) + .map(|link| Link::new(link.index as u64, link.source as u64, link.target as u64)) + .collect() + } + + fn query_by_id(&self, id: u64) -> Option { + self.store.get_link(id as usize).map(|link| { + Link::new(link.index as u64, link.source as u64, link.target as u64) + }) + } + + fn query_by_source(&self, source: u64) -> Vec { + let any = self.store.constants().any; + self.store + .each_iter([any, source as usize, any]) + .map(|link| Link::new(link.index as u64, link.source as u64, link.target as u64)) + .collect() + } + + fn query_by_target(&self, target: u64) -> Vec { + let any = self.store.constants().any; + self.store + .each_iter([any, any, target as usize]) + .map(|link| Link::new(link.index as u64, link.source as u64, link.target as u64)) + .collect() + } + + fn query_by_source_target(&self, source: u64, target: u64) -> Vec { + let any = self.store.constants().any; + self.store + .each_iter([any, source as usize, target as usize]) + .map(|link| Link::new(link.index as u64, link.source as u64, link.target as u64)) + .collect() + } + + fn count(&self) -> usize { + self.store.count() + } +} + +/// Create a new in-memory doublets united store +pub fn create_united_volatile() -> DoubletsLinks { + let mem = Alloc::new(Global); + let store = DoubletsUnitedVolatile::new(mem).expect("Failed to create doublets store"); + DoubletsLinks::new(store) +} + +/// Create a new in-memory doublets split store +pub fn create_split_volatile() -> DoubletsLinks { + let data_mem = Alloc::new(Global); + let index_mem = Alloc::new(Global); + let store = + DoubletsSplitVolatile::new(data_mem, index_mem).expect("Failed to create doublets store"); + DoubletsLinks::new(store) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_create_and_query_united() { + let mut db = create_united_volatile(); + let id = db.create_point(); + assert_eq!(id, 1); + + let link = db.query_by_id(id).unwrap(); + assert_eq!(link.source, id); + assert_eq!(link.target, id); + } + + #[test] + fn test_create_and_query_split() { + let mut db = create_split_volatile(); + let id = db.create_point(); + assert_eq!(id, 1); + + let link = db.query_by_id(id).unwrap(); + assert_eq!(link.source, id); + assert_eq!(link.target, id); + } + + #[test] + fn test_update() { + let mut db = create_united_volatile(); + let id = db.create(1, 2); + db.update(id, 3, 4); + + let link = db.query_by_id(id).unwrap(); + assert_eq!(link.source, 3); + assert_eq!(link.target, 4); + } + + #[test] + fn test_delete() { + let mut db = create_united_volatile(); + let id = db.create_point(); + db.delete(id); + assert!(db.query_by_id(id).is_none()); + } + + #[test] + fn test_query_by_source() { + let mut db = create_united_volatile(); + let id1 = db.create_point(); + let id2 = db.create_point(); + db.update(id1, id1, id2); + db.update(id2, id1, id1); + + let links = db.query_by_source(id1); + assert_eq!(links.len(), 2); + } +} diff --git a/rust/src/exclusive.rs b/rust/src/exclusive.rs new file mode 100644 index 0000000..727f428 --- /dev/null +++ b/rust/src/exclusive.rs @@ -0,0 +1,46 @@ +//! Thread-safe exclusive access wrapper + +use std::cell::UnsafeCell; +use std::ops::{Deref, DerefMut}; + +/// A wrapper that provides mutable access through an immutable reference. +/// This is useful for benchmark scenarios where we need interior mutability. +pub struct Exclusive { + inner: UnsafeCell, +} + +impl Exclusive { + /// Creates a new Exclusive wrapper + pub fn new(value: T) -> Self { + Self { + inner: UnsafeCell::new(value), + } + } + + /// Gets a mutable reference to the inner value + /// + /// # Safety + /// The caller must ensure that no other references exist to the inner value. + #[allow(clippy::mut_from_ref)] + pub fn get_mut(&self) -> &mut T { + unsafe { &mut *self.inner.get() } + } +} + +impl Deref for Exclusive { + type Target = T; + + fn deref(&self) -> &Self::Target { + unsafe { &*self.inner.get() } + } +} + +impl DerefMut for Exclusive { + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { &mut *self.inner.get() } + } +} + +// SAFETY: We ensure exclusive access through the API design +unsafe impl Send for Exclusive {} +unsafe impl Sync for Exclusive {} diff --git a/rust/src/fork.rs b/rust/src/fork.rs new file mode 100644 index 0000000..ba14355 --- /dev/null +++ b/rust/src/fork.rs @@ -0,0 +1,40 @@ +//! Fork mechanism for isolated benchmark runs + +use crate::Benched; +use std::ops::{Deref, DerefMut}; + +/// A fork that provides an isolated environment for benchmark iterations. +/// When dropped, it calls unfork() to clean up the state. +pub struct Fork<'a, B: Benched> { + benched: &'a mut B, +} + +impl<'a, B: Benched> Fork<'a, B> { + /// Creates a new Fork from a Benched implementation + pub fn new(benched: &'a mut B) -> Self { + Self { benched } + } +} + +impl<'a, B: Benched> Deref for Fork<'a, B> { + type Target = B; + + fn deref(&self) -> &Self::Target { + self.benched + } +} + +impl<'a, B: Benched> DerefMut for Fork<'a, B> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.benched + } +} + +impl<'a, B: Benched> Drop for Fork<'a, B> { + fn drop(&mut self) { + // SAFETY: We ensure the benched is properly cleaned up + unsafe { + self.benched.unfork(); + } + } +} diff --git a/rust/src/lib.rs b/rust/src/lib.rs new file mode 100644 index 0000000..04f91f4 --- /dev/null +++ b/rust/src/lib.rs @@ -0,0 +1,99 @@ +//! SQLite vs Doublets benchmark library +//! +//! This library provides implementations for benchmarking SQLite and Doublets +//! storage systems on basic CRUD operations with links. + +#![feature(allocator_api)] + +pub mod benched; +pub mod doublets_impl; +pub mod exclusive; +pub mod fork; +pub mod sqlite_impl; + +pub use benched::Benched; +pub use exclusive::Exclusive; +pub use fork::Fork; + +use once_cell::sync::Lazy; +use std::env; + +/// Number of links to use for benchmarking +pub static BENCHMARK_LINK_COUNT: Lazy = Lazy::new(|| { + env::var("BENCHMARK_LINK_COUNT") + .ok() + .and_then(|s| s.parse().ok()) + .unwrap_or(1000) +}); + +/// Number of background links to create before benchmarking +pub static BACKGROUND_LINK_COUNT: Lazy = Lazy::new(|| { + env::var("BACKGROUND_LINK_COUNT") + .ok() + .and_then(|s| s.parse().ok()) + .unwrap_or(3000) +}); + +/// A link structure representing a doublet (source -> target relationship) +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Link { + pub id: u64, + pub source: u64, + pub target: u64, +} + +impl Link { + pub fn new(id: u64, source: u64, target: u64) -> Self { + Self { id, source, target } + } +} + +/// Trait for database operations on links +pub trait Links { + /// Create a new link and return its ID + fn create(&mut self, source: u64, target: u64) -> u64; + + /// Create a point link (self-referencing link) + fn create_point(&mut self) -> u64 { + let id = self.create(0, 0); + self.update(id, id, id); + id + } + + /// Update an existing link + fn update(&mut self, id: u64, source: u64, target: u64); + + /// Delete a link by ID + fn delete(&mut self, id: u64); + + /// Delete all links + fn delete_all(&mut self); + + /// Query all links + fn query_all(&self) -> Vec; + + /// Query a link by ID + fn query_by_id(&self, id: u64) -> Option; + + /// Query links by source + fn query_by_source(&self, source: u64) -> Vec; + + /// Query links by target + fn query_by_target(&self, target: u64) -> Vec; + + /// Query links by source and target + fn query_by_source_target(&self, source: u64, target: u64) -> Vec; + + /// Count all links + fn count(&self) -> usize; +} + +/// Macro for running benchmarks with proper setup and teardown +#[macro_export] +macro_rules! bench { + ($name:expr, $benched:expr, $op:expr) => {{ + use $crate::Benched; + let mut fork = $benched.fork(); + $op(&mut *fork); + }}; +} diff --git a/rust/src/sqlite_impl.rs b/rust/src/sqlite_impl.rs new file mode 100644 index 0000000..1f0d92b --- /dev/null +++ b/rust/src/sqlite_impl.rs @@ -0,0 +1,297 @@ +//! SQLite implementation for links storage + +use crate::{Link, Links}; +use rusqlite::{params, Connection}; +use std::path::Path; + +/// SQLite-based links storage +pub struct SqliteLinks { + conn: Connection, + next_id: u64, +} + +impl SqliteLinks { + /// Create a new SQLite links storage with in-memory database + pub fn new_memory() -> Self { + let conn = Connection::open_in_memory().expect("Failed to open in-memory SQLite database"); + Self::init(conn) + } + + /// Create a new SQLite links storage with file-based database + pub fn new_file>(path: P) -> Self { + let conn = Connection::open(path).expect("Failed to open SQLite database file"); + Self::init(conn) + } + + fn init(conn: Connection) -> Self { + conn.execute( + "CREATE TABLE IF NOT EXISTS links ( + id INTEGER PRIMARY KEY, + source INTEGER NOT NULL, + target INTEGER NOT NULL + )", + [], + ) + .expect("Failed to create links table"); + + // Create indexes for efficient queries + conn.execute( + "CREATE INDEX IF NOT EXISTS idx_source ON links(source)", + [], + ) + .expect("Failed to create source index"); + + conn.execute( + "CREATE INDEX IF NOT EXISTS idx_target ON links(target)", + [], + ) + .expect("Failed to create target index"); + + conn.execute( + "CREATE INDEX IF NOT EXISTS idx_source_target ON links(source, target)", + [], + ) + .expect("Failed to create source_target index"); + + // Get the max ID to continue from + let next_id: u64 = conn + .query_row("SELECT COALESCE(MAX(id), 0) + 1 FROM links", [], |row| { + row.get(0) + }) + .unwrap_or(1); + + Self { conn, next_id } + } + + /// Drop all tables and recreate them + pub fn reset(&mut self) { + self.conn + .execute("DROP TABLE IF EXISTS links", []) + .expect("Failed to drop links table"); + self.conn + .execute( + "CREATE TABLE links ( + id INTEGER PRIMARY KEY, + source INTEGER NOT NULL, + target INTEGER NOT NULL + )", + [], + ) + .expect("Failed to create links table"); + + self.conn + .execute("CREATE INDEX idx_source ON links(source)", []) + .expect("Failed to create source index"); + + self.conn + .execute("CREATE INDEX idx_target ON links(target)", []) + .expect("Failed to create target index"); + + self.conn + .execute( + "CREATE INDEX idx_source_target ON links(source, target)", + [], + ) + .expect("Failed to create source_target index"); + + self.next_id = 1; + } +} + +impl Links for SqliteLinks { + fn create(&mut self, source: u64, target: u64) -> u64 { + let id = self.next_id; + self.conn + .execute( + "INSERT INTO links (id, source, target) VALUES (?1, ?2, ?3)", + params![id as i64, source as i64, target as i64], + ) + .expect("Failed to insert link"); + self.next_id += 1; + id + } + + fn update(&mut self, id: u64, source: u64, target: u64) { + self.conn + .execute( + "UPDATE links SET source = ?1, target = ?2 WHERE id = ?3", + params![source as i64, target as i64, id as i64], + ) + .expect("Failed to update link"); + } + + fn delete(&mut self, id: u64) { + self.conn + .execute("DELETE FROM links WHERE id = ?1", params![id as i64]) + .expect("Failed to delete link"); + } + + fn delete_all(&mut self) { + self.conn + .execute("DELETE FROM links", []) + .expect("Failed to delete all links"); + self.next_id = 1; + } + + fn query_all(&self) -> Vec { + let mut stmt = self + .conn + .prepare("SELECT id, source, target FROM links") + .expect("Failed to prepare query"); + + stmt.query_map([], |row| { + Ok(Link { + id: row.get::<_, i64>(0)? as u64, + source: row.get::<_, i64>(1)? as u64, + target: row.get::<_, i64>(2)? as u64, + }) + }) + .expect("Failed to query links") + .filter_map(|r| r.ok()) + .collect() + } + + fn query_by_id(&self, id: u64) -> Option { + self.conn + .query_row( + "SELECT id, source, target FROM links WHERE id = ?1", + params![id as i64], + |row| { + Ok(Link { + id: row.get::<_, i64>(0)? as u64, + source: row.get::<_, i64>(1)? as u64, + target: row.get::<_, i64>(2)? as u64, + }) + }, + ) + .ok() + } + + fn query_by_source(&self, source: u64) -> Vec { + let mut stmt = self + .conn + .prepare("SELECT id, source, target FROM links WHERE source = ?1") + .expect("Failed to prepare query"); + + stmt.query_map(params![source as i64], |row| { + Ok(Link { + id: row.get::<_, i64>(0)? as u64, + source: row.get::<_, i64>(1)? as u64, + target: row.get::<_, i64>(2)? as u64, + }) + }) + .expect("Failed to query links") + .filter_map(|r| r.ok()) + .collect() + } + + fn query_by_target(&self, target: u64) -> Vec { + let mut stmt = self + .conn + .prepare("SELECT id, source, target FROM links WHERE target = ?1") + .expect("Failed to prepare query"); + + stmt.query_map(params![target as i64], |row| { + Ok(Link { + id: row.get::<_, i64>(0)? as u64, + source: row.get::<_, i64>(1)? as u64, + target: row.get::<_, i64>(2)? as u64, + }) + }) + .expect("Failed to query links") + .filter_map(|r| r.ok()) + .collect() + } + + fn query_by_source_target(&self, source: u64, target: u64) -> Vec { + let mut stmt = self + .conn + .prepare("SELECT id, source, target FROM links WHERE source = ?1 AND target = ?2") + .expect("Failed to prepare query"); + + stmt.query_map(params![source as i64, target as i64], |row| { + Ok(Link { + id: row.get::<_, i64>(0)? as u64, + source: row.get::<_, i64>(1)? as u64, + target: row.get::<_, i64>(2)? as u64, + }) + }) + .expect("Failed to query links") + .filter_map(|r| r.ok()) + .collect() + } + + fn count(&self) -> usize { + self.conn + .query_row("SELECT COUNT(*) FROM links", [], |row| row.get::<_, i64>(0)) + .unwrap_or(0) as usize + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_create_and_query() { + let mut db = SqliteLinks::new_memory(); + let id = db.create(1, 2); + assert_eq!(id, 1); + + let link = db.query_by_id(id).unwrap(); + assert_eq!(link.source, 1); + assert_eq!(link.target, 2); + } + + #[test] + fn test_create_point() { + let mut db = SqliteLinks::new_memory(); + let id = db.create_point(); + assert_eq!(id, 1); + + let link = db.query_by_id(id).unwrap(); + assert_eq!(link.source, id); + assert_eq!(link.target, id); + } + + #[test] + fn test_update() { + let mut db = SqliteLinks::new_memory(); + let id = db.create(1, 2); + db.update(id, 3, 4); + + let link = db.query_by_id(id).unwrap(); + assert_eq!(link.source, 3); + assert_eq!(link.target, 4); + } + + #[test] + fn test_delete() { + let mut db = SqliteLinks::new_memory(); + let id = db.create(1, 2); + db.delete(id); + assert!(db.query_by_id(id).is_none()); + } + + #[test] + fn test_query_by_source() { + let mut db = SqliteLinks::new_memory(); + db.create(1, 2); + db.create(1, 3); + db.create(2, 3); + + let links = db.query_by_source(1); + assert_eq!(links.len(), 2); + } + + #[test] + fn test_query_by_target() { + let mut db = SqliteLinks::new_memory(); + db.create(1, 3); + db.create(2, 3); + db.create(2, 4); + + let links = db.query_by_target(3); + assert_eq!(links.len(), 2); + } +}