diff --git a/.github/workflows/bench-pages.yml b/.github/workflows/bench-pages.yml index a995632..33a50dd 100644 --- a/.github/workflows/bench-pages.yml +++ b/.github/workflows/bench-pages.yml @@ -19,6 +19,6 @@ jobs: uses: jcfangc/ci/.github/workflows/rust-benchmark-pages.yml@v0 with: rust_toolchain: stable - run_command: cargo bench -- --color never + run_command: cargo bench --features characterization -- --color never pages_artifact_path: target/divan-pages divan_output_path: target/divan-pages/divan.txt diff --git a/.github/workflows/gate.yml b/.github/workflows/gate.yml index e856da8..4ca20fe 100644 --- a/.github/workflows/gate.yml +++ b/.github/workflows/gate.yml @@ -62,7 +62,3 @@ jobs: - name: Test run: cargo test --locked - - - name: Test benchmark tooling - if: matrix.name == 'linux-x64-baseline' - run: python3 -m unittest discover -s scripts/tests -v diff --git a/Cargo.lock b/Cargo.lock index 2f9184a..35d8034 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,12 +11,86 @@ dependencies = [ "memchr", ] +[[package]] +name = "aliasable" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "ambassador" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd3d4da7fddbc6567cbd7b5c30364f91313701b1857ab5b925eb3acf06162eed" +dependencies = [ + "itertools 0.10.5", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + [[package]] name = "anstyle" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + [[package]] name = "anyhow" version = "1.0.102" @@ -32,6 +106,30 @@ dependencies = [ "num-traits", ] +[[package]] +name = "arbitrary-chunks" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ad8689a486416c401ea15715a4694de30054248ec627edbf31f49cb64ee4086" + +[[package]] +name = "arrayvec" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" + +[[package]] +name = "atomic" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" + +[[package]] +name = "atomic-primitive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d0f69ca9522465c166bd988f9e0d9840f6ad141a4b66a55e94b601f14c289c2" + [[package]] name = "autocfg" version = "1.5.1" @@ -54,8 +152,12 @@ dependencies = [ "bit-string-derive", "bitvec_simd", "codspeed-divan-compat", + "compressed-intvec", + "grit-bitvec", "int-intervals", "proptest", + "sux", + "value-traits", "witnessed", ] @@ -66,7 +168,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -75,6 +177,12 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.13.0" @@ -91,11 +199,47 @@ dependencies = [ "wide", ] +[[package]] +name = "block-pseudorand" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2097358495d244a0643746f4d13eedba4608137008cf9dec54e53a3b700115a6" +dependencies = [ + "chiapos-chacha8", + "nanorand", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + [[package]] name = "bytemuck" version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0e56a716f1e132ff6bf4bdac1c944a3fcdc1cae65f70a4a2a1ac3b401d2d1f" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" @@ -119,6 +263,53 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chacha20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" +dependencies = [ + "cfg-if", + "cpufeatures", + "rand_core 0.10.1", +] + +[[package]] +name = "chiapos-chacha8" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33f8be573a85f6c2bc1b8e43834c07e32f95e489b914bf856c0549c3c269cd0a" +dependencies = [ + "rayon", +] + +[[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.6.1" @@ -183,11 +374,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f70e4ddd6beedefeb48f59d5f85fc21365a66e7976408c3d39f6cbbc4f03e08c" dependencies = [ "divan-macros", - "itertools", + "itertools 0.14.0", "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -205,6 +396,12 @@ dependencies = [ "regex-lite", ] +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + [[package]] name = "colored" version = "2.2.0" @@ -216,190 +413,869 @@ dependencies = [ ] [[package]] -name = "condtype" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf0a07a401f374238ab8e2f11a104d2851bf9ce711ec69804834de8af45c7af" - -[[package]] -name = "divan-macros" -version = "0.1.17" +name = "common_traits" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dc51d98e636f5e3b0759a39257458b22619cac7e96d932da6eeb052891bb67c" +checksum = "65d0a1296e8d359cb197a8f8289f3d3f77cdb67f1a83d0aeb0820a5b7aea4058" dependencies = [ - "proc-macro2", - "quote", - "syn", + "anyhow", + "half", + "impl-tools", ] [[package]] -name = "either" -version = "1.16.0" +name = "compressed-intvec" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" +checksum = "6e37a40af84e3b33a97ec92652d98803e6194db7c1502423bff7d2c9f082c548" +dependencies = [ + "atomic", + "common_traits", + "dsi-bitstream", + "mem_dbg", + "num-traits", + "parking_lot", +] [[package]] -name = "equivalent" -version = "1.0.2" +name = "condtype" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +checksum = "baf0a07a401f374238ab8e2f11a104d2851bf9ce711ec69804834de8af45c7af" [[package]] -name = "errno" -version = "0.3.14" +name = "cpufeatures" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" dependencies = [ "libc", - "windows-sys 0.61.2", ] [[package]] -name = "fastrand" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" - -[[package]] -name = "find-msvc-tools" -version = "0.1.9" +name = "criterion" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "is-terminal", + "itertools 0.10.5", + "num-traits", + "once_cell", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] [[package]] -name = "fnv" -version = "1.0.7" +name = "criterion-plot" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools 0.10.5", +] [[package]] -name = "getrandom" -version = "0.2.17" +name = "crossbeam-channel" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +checksum = "98b0cc327b5bc766e7fda9c9260cc0fa81b43a8e240440422dff70788e3f9ef1" dependencies = [ - "cfg-if", - "libc", - "wasi", + "crossbeam-utils", ] [[package]] -name = "getrandom" -version = "0.3.4" +name = "crossbeam-deque" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +checksum = "622f3fc73690be383c7214310406f28a90e6edeadc3cea882f9d71e495b9711a" dependencies = [ - "cfg-if", - "libc", - "r-efi 5.3.0", - "wasip2", + "crossbeam-epoch", + "crossbeam-utils", ] [[package]] -name = "getrandom" -version = "0.4.3" +name = "crossbeam-epoch" +version = "0.9.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d" dependencies = [ - "cfg-if", - "libc", - "r-efi 6.0.0", + "crossbeam-utils", ] [[package]] -name = "glob" -version = "0.3.3" +name = "crossbeam-utils" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" [[package]] -name = "hashbrown" -version = "0.17.1" +name = "crunchy" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] -name = "indexmap" -version = "2.14.0" +name = "darling" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" dependencies = [ - "equivalent", - "hashbrown", + "darling_core", + "darling_macro", ] [[package]] -name = "int-intervals" -version = "0.1.2" +name = "darling_core" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae229a6c2c8562cc2bee9365152c5787dd6ec7f42404fbd3563f2b437c4094" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.117", +] [[package]] -name = "itertools" -version = "0.14.0" +name = "darling_macro" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ - "either", + "darling_core", + "quote", + "syn 2.0.117", ] [[package]] -name = "itoa" -version = "1.0.18" +name = "defmt" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] [[package]] -name = "lazy_static" -version = "1.5.0" +name = "defmt-macros" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8" +dependencies = [ + "defmt-parser", + "proc-macro2", + "quote", + "syn 2.0.117", +] [[package]] -name = "libc" -version = "0.2.186" +name = "defmt-parser" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror", +] [[package]] -name = "linux-raw-sys" -version = "0.12.1" +name = "derivative" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] [[package]] -name = "memchr" -version = "2.8.1" +name = "derive_setters" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" +checksum = "b7e6f6fa1f03c14ae082120b84b3c7fbd7b8588d924cf2d7c3daf9afd49df8b9" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.117", +] [[package]] -name = "nix" -version = "0.31.3" +name = "divan-macros" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" +checksum = "8dc51d98e636f5e3b0759a39257458b22619cac7e96d932da6eeb052891bb67c" dependencies = [ - "bitflags", - "cfg-if", - "cfg_aliases", - "libc", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "num-traits" -version = "0.2.19" +name = "dsi-bitstream" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +checksum = "72b1a854525fc27b5ed458f038740e56613e5b9d0c6af67d7b9dc1062157afb7" dependencies = [ - "autocfg", + "mem_dbg", + "num-primitive", + "rand 0.10.2", + "serde", ] [[package]] -name = "once_cell" -version = "1.21.4" +name = "dsi-progress-logger" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc2f98e7418e41b0138729f1d1ca4f0ecc105e98639ca6ef736acf081e47db45" +dependencies = [ + "log", + "num-format", + "pluralizer", + "sysinfo", +] + +[[package]] +name = "either" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" + +[[package]] +name = "env_filter" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.1", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "grit-bitvec" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf90e0aabb241f986f4aebc776ac86e5f02fe6609e88ae3c0f3b1106085a470" + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "hermit-abi" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "impl-tools" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae314a99afb5821e2fda288387546d4a04aace674551e854e6216b892ec3208" +dependencies = [ + "autocfg", + "impl-tools-lib", + "proc-macro-error2", + "syn 2.0.117", +] + +[[package]] +name = "impl-tools-lib" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab699036df31c1f7d3561bfa6e9cb9bc3bb0fd2e2cd9bf121c31cb961d049ddf" +dependencies = [ + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", +] + +[[package]] +name = "int-intervals" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae229a6c2c8562cc2bee9365152c5787dd6ec7f42404fbd3563f2b437c4094" + +[[package]] +name = "is-terminal" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jiff" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668b7183bd07af9a4885f5c35b0cc5c83c4607a913c16b7e17291832910d2dcc" +dependencies = [ + "defmt", + "jiff-core", + "jiff-static", + "jiff-tzdb-platform", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", + "windows-link 0.2.1", +] + +[[package]] +name = "jiff-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7feca88439efe53da3754500c1851dedf3cb36c524dd5cf8225cc0794de95d09" +dependencies = [ + "defmt", +] + +[[package]] +name = "jiff-static" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a69dcb3a21cfb32ce1cd056169337ca284af0766dd766e7878819b251a49204" +dependencies = [ + "jiff-core", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "jiff-tzdb" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "142bd39932ad231f10513df9ab62661fead8719872150b7ad02a2df79f4e141e" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +dependencies = [ + "jiff-tzdb", +] + +[[package]] +name = "js-sys" +version = "0.3.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "lambert_w" +version = "1.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5f0846ee4f0299ca4c5b9ca06ff55cf88b3430a763bf591474cc734479c9b24" +dependencies = [ + "num-complex", + "num-traits", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lender" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aeed4888de4544bf3ba2e111326978dbcc37f57bf2c7d6a6a78ed6c01f77ef93" +dependencies = [ + "aliasable", + "fallible-iterator", + "lender-derive", + "maybe-dangling", + "stable_try_trait_v2", +] + +[[package]] +name = "lender-derive" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d074a297c82222d442171bad4f392fef93d35fb31e24a115f605a0c907ce0af9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" + +[[package]] +name = "maybe-dangling" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59dbb09ed53f8e4f314e353dc6c1853ae5b4c480a668a422657804a544ea9f65" + +[[package]] +name = "mem_dbg" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b48a1086c746f4ee6ca5cb0acf856a14709bc4d2d20e03db150a12ddf2269e6d" +dependencies = [ + "bitflags 2.13.0", + "hashbrown 0.16.1", + "mem_dbg-derive", +] + +[[package]] +name = "mem_dbg-derive" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb910efe8da52f13da727170e352e50a1764579a6fb1065d00d9556da19c79ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "memchr" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" + +[[package]] +name = "nanorand" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "729eb334247daa1803e0a094d0a5c55711b85571179f5ec6e53eccfdf7008958" + +[[package]] +name = "nix" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" +dependencies = [ + "bitflags 2.13.0", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "ntapi" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" +dependencies = [ + "winapi", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-format" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" +dependencies = [ + "arrayvec", + "itoa", +] + +[[package]] +name = "num-primitive" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0178502a58a2514f927965f80216c0ea2533b5364061544ea8e09b79343d4183" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.13.0", +] + +[[package]] +name = "objc2-io-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15" +dependencies = [ + "libc", + "objc2-core-foundation", +] + +[[package]] +name = "once_cell" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link 0.2.1", +] + +[[package]] +name = "partition" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "947f833aaa585cf12b8ec7c0476c98784c49f33b861376ffc84ed92adebf2aba" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[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 = "pluralizer" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b3eba432a00a1f6c16f39147847a870e94e2e9b992759b503e330efec778cbe" +dependencies = [ + "once_cell", + "regex", +] + +[[package]] +name = "portable-atomic" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" + +[[package]] +name = "portable-atomic-util" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10ab3eb7f3becc3a1cbc4f2c6f20267996cfc1a6467a873763411b136a122715" +dependencies = [ + "portable-atomic", +] + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -418,6 +1294,27 @@ dependencies = [ "toml_edit", ] +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -435,9 +1332,9 @@ checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" dependencies = [ "bit-set", "bit-vec", - "bitflags", + "bitflags 2.13.0", "num-traits", - "rand", + "rand 0.9.4", "rand_chacha", "rand_xorshift", "regex-syntax", @@ -474,41 +1371,102 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] -name = "rand" -version = "0.9.4" +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha", + "rand_core 0.9.5", +] + +[[package]] +name = "rand" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +dependencies = [ + "chacha20", + "getrandom 0.4.3", + "rand_core 0.10.1", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rand_xorshift" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +dependencies = [ + "rand_core 0.9.5", +] + +[[package]] +name = "rayon" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" dependencies = [ - "rand_chacha", - "rand_core", + "either", + "rayon-core", ] [[package]] -name = "rand_chacha" -version = "0.9.0" +name = "rayon-core" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" dependencies = [ - "ppv-lite86", - "rand_core", + "crossbeam-deque", + "crossbeam-utils", ] [[package]] -name = "rand_core" -version = "0.9.5" +name = "rdst" +version = "0.20.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +checksum = "6e7970b4e577b76a96d5e56b5f6662b66d1a4e1f5bb026ee118fc31b373c2752" dependencies = [ - "getrandom 0.3.4", + "arbitrary-chunks", + "block-pseudorand", + "criterion", + "partition", + "rayon", + "tikv-jemallocator", + "voracious_radix_sort", ] [[package]] -name = "rand_xorshift" -version = "0.4.0" +name = "redox_syscall" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "rand_core", + "bitflags 2.13.0", ] [[package]] @@ -552,13 +1510,19 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags", + "bitflags 2.13.0", "errno", "libc", "linux-raw-sys", "windows-sys 0.61.2", ] +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + [[package]] name = "rusty-fork" version = "0.3.1" @@ -580,6 +1544,21 @@ dependencies = [ "bytemuck", ] +[[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 = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "serde" version = "1.0.228" @@ -607,7 +1586,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -629,6 +1608,12 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + [[package]] name = "smallvec" version = "1.15.1" @@ -638,6 +1623,12 @@ dependencies = [ "serde", ] +[[package]] +name = "stable_try_trait_v2" +version = "1.75.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c4e48411f4db8ccca0470bfb67e3bb821af4227d455aa147917d8d109be0d13" + [[package]] name = "statrs" version = "0.18.0" @@ -648,6 +1639,72 @@ dependencies = [ "num-traits", ] +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "succinctly" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efed1dfd86bb1bafeba986d5cb77f0f38924438b833c5cc3977a2afbd7e26536" +dependencies = [ + "bytemuck", + "indexmap", + "libm", +] + +[[package]] +name = "sux" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c7bb0732ef419ea39063a3f345cd5feea30765842354d52d709e28fb04cceec" +dependencies = [ + "ambassador", + "anyhow", + "arbitrary-chunks", + "arrayvec", + "atomic-primitive", + "bitflags 2.13.0", + "crossbeam-channel", + "derivative", + "derive_setters", + "dsi-progress-logger", + "env_logger", + "fallible-iterator", + "impl-tools", + "itertools 0.14.0", + "jiff", + "lambert_w", + "lender", + "libc", + "log", + "mem_dbg", + "num-primitive", + "rand 0.10.2", + "rdst", + "succinctly", + "sync-cell-slice", + "tempfile", + "thiserror", + "value-traits", + "xxhash-rust", + "zerocopy", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.117" @@ -659,6 +1716,37 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync-cell-slice" +version = "0.9.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ff90d38b459e4e44379b38cb749f35b394d8cc5c614402d72fbebf58e966e7c" + +[[package]] +name = "sysinfo" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "252800745060e7b9ffb7b2badbd8b31cfa4aa2e61af879d0a3bf2a317c20217d" +dependencies = [ + "libc", + "memchr", + "ntapi", + "objc2-core-foundation", + "objc2-io-kit", + "windows", +] + [[package]] name = "tempfile" version = "3.27.0" @@ -682,6 +1770,56 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "thiserror" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "tikv-jemalloc-sys" +version = "0.5.4+5.3.0-patched" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9402443cb8fd499b6f327e40565234ff34dbda27460c5b47db0db77443dd85d1" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "tikv-jemallocator" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "965fe0c26be5c56c94e38ba547249074803efd52adfb66de62107d95aab3eaca" +dependencies = [ + "libc", + "tikv-jemalloc-sys", +] + +[[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 = "toml_datetime" version = "1.1.1+spec-1.1.0" @@ -724,6 +1862,41 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "value-traits" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17e73c7053d8fa8e9c3c6b16c32d079ed5642a7156514820486a9c4e109cf48d" +dependencies = [ + "value-traits-derive", +] + +[[package]] +name = "value-traits-derive" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d301d1ee4b3eced3e73aa5740a303c7e068f1d4450c5dae4c8cf6bfa266954f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "voracious_radix_sort" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446e7ffcb6c27a71d05af7e51ef2ee5b71c48424b122a832f2439651e1914899" +dependencies = [ + "rayon", +] + [[package]] name = "wait-timeout" version = "0.2.1" @@ -733,6 +1906,16 @@ dependencies = [ "libc", ] +[[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.1+wasi-snapshot-preview1" @@ -748,6 +1931,61 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "wasm-bindgen" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 3.0.5", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fbddc4a036f00ec4f18c83445bd3115cb306a91da554919a099d9222fe4a7f8" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "wide" version = "0.7.33" @@ -758,12 +1996,145 @@ dependencies = [ "safe_arch", ] +[[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.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-link 0.1.3", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.1.3", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core", + "windows-link 0.1.3", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + [[package]] name = "windows-sys" version = "0.59.0" @@ -779,7 +2150,7 @@ version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -798,6 +2169,15 @@ dependencies = [ "windows_x86_64_msvc", ] +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -867,6 +2247,12 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60dfef5521792043abeb0b4328f39f4f17652a512d634e02bd9374ad214ec5c5" +[[package]] +name = "xxhash-rust" +version = "0.8.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aee1b19627c7c60102ab80d3a9cbe18de90bfe03bfa6c3715447681f0e8c8af6" + [[package]] name = "zerocopy" version = "0.8.52" @@ -884,7 +2270,7 @@ checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index ad4560d..3150b2e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ repository = "https://github.com/jcfangc/bit-string" license = "MIT OR Apache-2.0" keywords = ["bitstring", "bits", "bitset", "sequence"] categories = ["data-structures", "no-std"] -exclude = ["/benches", "/scripts", "/src/**/tests_for_*", "/tests", ".github/"] +exclude = ["/benches", "/src/**/tests_for_*", "/tests", ".github/"] [workspace] members = ["derive"] @@ -16,6 +16,9 @@ members = ["derive"] [workspace.package] version = "0.6.0" +[features] +characterization = [] + [workspace.dependencies] bit-string-derive = { version = "0.6", path = "derive" } @@ -29,8 +32,12 @@ doctest = false [dev-dependencies] bitvec_simd = "0.20.5" +compressed-intvec = { version = "0.6.0", default-features = false } divan = { package = "codspeed-divan-compat", version = "4.7.0" } +grit-bitvec = "0.4.3" proptest = "1.11.0" +sux = { version = "0.14.0", default-features = false } +value-traits = "0.2.1" [[bench]] @@ -117,6 +124,30 @@ harness = false name = "editing_pop" harness = false +[[bench]] +name = "packed_construction" +harness = false + +[[bench]] +name = "packed_access" +harness = false + +[[bench]] +name = "packed_iteration" +harness = false + +[[bench]] +name = "packed_editing" +harness = false + +[[bench]] +name = "packed_competitors" +harness = false + +[[bench]] +name = "packed_native" +harness = false + [[bench]] name = "bit_ops_shl_assign" harness = false diff --git a/benches/packed_access.rs b/benches/packed_access.rs new file mode 100644 index 0000000..78d58ca --- /dev/null +++ b/benches/packed_access.rs @@ -0,0 +1,175 @@ +#[path = "packed_support/mod.rs"] +mod support; + +use divan::{Bencher, black_box}; +use support::{codes, indices, packed}; + +fn main() { + divan::main(); +} + +fn get_sequential_packed(bencher: Bencher, len: usize) { + let input = codes(BITS, len); + let value = packed::(&input); + bencher.bench(|| { + let value = black_box(&value); + let mut checksum = 0u8; + for index in 0..len { + checksum ^= value.get(index).unwrap().0; + } + black_box(checksum) + }); +} + +fn get_sequential_vec(bencher: Bencher, len: usize) { + let input = codes(BITS, len); + bencher.bench(|| { + let input = black_box(&input); + let mut checksum = 0u8; + for index in 0..len { + checksum ^= input[index]; + } + black_box(checksum) + }); +} + +fn get_sequential_packed_str(bencher: Bencher, len: usize) { + let input = codes(BITS, len); + let mut source = Vec::with_capacity(len + 1); + source.push(0); + source.extend_from_slice(&input); + let owner = packed::(&source); + let value = owner.as_packed_str().slice_from(1); + bencher.bench(|| { + let value = black_box(value); + let mut checksum = 0u8; + for index in 0..len { + checksum ^= value.get(index).unwrap().0; + } + black_box(checksum) + }); +} + +fn get_random_packed(bencher: Bencher, len: usize) { + let input = codes(BITS, len); + let value = packed::(&input); + let indexes = indices(len); + bencher.bench(|| { + let value = black_box(&value); + let indexes = black_box(&indexes); + let mut checksum = 0u8; + for &index in indexes { + checksum ^= value.get(index).unwrap().0; + } + black_box(checksum) + }); +} + +fn get_random_vec(bencher: Bencher, len: usize) { + let input = codes(BITS, len); + let indexes = indices(len); + bencher.bench(|| { + let input = black_box(&input); + let indexes = black_box(&indexes); + let mut checksum = 0u8; + for &index in indexes { + checksum ^= input[index]; + } + black_box(checksum) + }); +} + +fn get_random_packed_str(bencher: Bencher, len: usize) { + let input = codes(BITS, len); + let mut source = Vec::with_capacity(len + 1); + source.push(0); + source.extend_from_slice(&input); + let owner = packed::(&source); + let value = owner.as_packed_str().slice_from(1); + let indexes = indices(len); + bencher.bench(|| { + let value = black_box(value); + let indexes = black_box(&indexes); + let mut checksum = 0u8; + for &index in indexes { + checksum ^= value.get(index).unwrap().0; + } + black_box(checksum) + }); +} + +macro_rules! define_case { + ($case:ident, $bits:literal, $len:literal) => { + mod $case { + use super::*; + + #[divan::bench( + name = concat!( + "packed_access/get_sequential/", + stringify!($case), + "/ours_packed_string" + ) + )] + fn ours_packed_string(bencher: Bencher) { + super::get_sequential_packed::<$bits>(bencher, $len); + } + + #[divan::bench( + name = concat!( + "packed_access/get_sequential/", + stringify!($case), + "/ours_packed_str_unaligned" + ) + )] + fn ours_packed_str(bencher: Bencher) { + super::get_sequential_packed_str::<$bits>(bencher, $len); + } + + #[divan::bench( + name = concat!( + "packed_access/get_sequential/", + stringify!($case), + "/vec_u8" + ) + )] + fn vec_u8(bencher: Bencher) { + super::get_sequential_vec::<$bits>(bencher, $len); + } + + #[divan::bench( + name = concat!( + "packed_access/get_random_stream/", + stringify!($case), + "/ours_packed_string" + ) + )] + fn ours_packed_string_random(bencher: Bencher) { + super::get_random_packed::<$bits>(bencher, $len); + } + + #[divan::bench( + name = concat!( + "packed_access/get_random_stream/", + stringify!($case), + "/ours_packed_str_unaligned" + ) + )] + fn ours_packed_str_random(bencher: Bencher) { + super::get_random_packed_str::<$bits>(bencher, $len); + } + + #[divan::bench( + name = concat!( + "packed_access/get_random_stream/", + stringify!($case), + "/vec_u8" + ) + )] + fn vec_u8_random(bencher: Bencher) { + super::get_random_vec::<$bits>(bencher, $len); + } + } + }; +} + +crate::for_each_packed_bench_case!(define_case); diff --git a/benches/packed_competitors.rs b/benches/packed_competitors.rs new file mode 100644 index 0000000..deb2d95 --- /dev/null +++ b/benches/packed_competitors.rs @@ -0,0 +1,480 @@ +#[path = "packed_support/mod.rs"] +mod support; + +use compressed_intvec::fixed::UFixedVec; +use divan::{Bencher, black_box}; +use grit_bitvec::{TypedBitElem, TypedBitVec}; +use support::{ + aligned_index, aligned_index_for, codes, cross_word_index, cross_word_index_for, indices, + unaligned_index, unaligned_index_for, +}; +use sux::bits::BitFieldVec; +use value_traits::slices::{SliceByValue, SliceByValueMut}; + +fn main() { + divan::main(); +} + +fn construct_fixed(bencher: Bencher, len: usize) { + let input = codes(BITS, len); + bencher.bench(|| { + black_box( + UFixedVec::::from_iter_builder(input.iter().copied(), usize::from(BITS)) + .build() + .unwrap(), + ) + }); +} + +fn get_sequential_fixed(bencher: Bencher, len: usize) { + let value = fixed_vec(BITS, &codes(BITS, len)); + bencher.bench(|| { + let value = black_box(&value); + let mut checksum = 0u8; + for index in 0..len { + checksum ^= value.get(index).unwrap(); + } + black_box(checksum) + }); +} + +fn get_random_fixed(bencher: Bencher, len: usize) { + let value = fixed_vec(BITS, &codes(BITS, len)); + let indexes = indices(len); + bencher.bench(|| { + let value = black_box(&value); + let indexes = black_box(&indexes); + let mut checksum = 0u8; + for &index in indexes { + checksum ^= value.get(index).unwrap(); + } + black_box(checksum) + }); +} + +fn iter_fixed(bencher: Bencher, len: usize) { + let value = fixed_vec(BITS, &codes(BITS, len)); + bencher.bench(|| { + let value = black_box(&value); + let checksum = value.iter().fold(0u8, |checksum, code| checksum ^ code); + black_box(checksum) + }); +} + +fn set_fixed(bencher: Bencher, len: usize) { + let input = codes(BITS, len); + let index = aligned_index::(len); + let replacement = input[index] ^ 1; + let value = fixed_vec(BITS, &input); + bencher.with_inputs(|| value.clone()).bench_refs(|value| { + value.set(index, replacement); + black_box(&*value); + }); +} + +fn push_fixed(bencher: Bencher, len: usize) { + let value = fixed_vec(BITS, &codes(BITS, len)); + bencher.with_inputs(|| value.clone()).bench_refs(|value| { + value.push(0); + black_box(&*value); + }); +} + +fn pop_fixed(bencher: Bencher, len: usize) { + let value = fixed_vec(BITS, &codes(BITS, len)); + bencher.with_inputs(|| value.clone()).bench_refs(|value| { + black_box(value.pop()); + black_box(&*value); + }); +} + +fn extend_fixed(bencher: Bencher, len: usize) { + let value = fixed_vec(BITS, &codes(BITS, len)); + let extension = codes(BITS, len / 4); + bencher.with_inputs(|| value.clone()).bench_refs(|value| { + value.extend_from_slice(&extension); + black_box(&*value); + }); +} + +fn insert_fixed(bencher: Bencher, len: usize) { + let value = fixed_vec(BITS, &codes(BITS, len)); + bencher.with_inputs(|| value.clone()).bench_refs(|value| { + value.insert(unaligned_index::(len), 0); + black_box(&*value); + }); +} + +fn remove_fixed(bencher: Bencher, len: usize) { + let value = fixed_vec(BITS, &codes(BITS, len)); + bencher.with_inputs(|| value.clone()).bench_refs(|value| { + black_box(value.remove(cross_word_index::(len))); + black_box(&*value); + }); +} + +fn construct_sux(bencher: Bencher, len: usize) { + let input = codes(BITS, len); + bencher.bench(|| { + let mut value = BitFieldVec::>::with_capacity(usize::from(BITS), len); + value.extend(input.iter().map(|&code| usize::from(code))); + black_box(value) + }); +} + +fn get_sequential_sux(bencher: Bencher, len: usize) { + let value = sux_vec(BITS, &codes(BITS, len)); + bencher.bench(|| { + let value = black_box(&value); + let mut checksum = 0u8; + for index in 0..len { + checksum ^= value.index_value(index) as u8; + } + black_box(checksum) + }); +} + +fn get_random_sux(bencher: Bencher, len: usize) { + let value = sux_vec(BITS, &codes(BITS, len)); + let indexes = indices(len); + bencher.bench(|| { + let value = black_box(&value); + let indexes = black_box(&indexes); + let mut checksum = 0u8; + for &index in indexes { + checksum ^= value.index_value(index) as u8; + } + black_box(checksum) + }); +} + +fn iter_sux(bencher: Bencher, len: usize) { + let value = sux_vec(BITS, &codes(BITS, len)); + bencher.bench(|| { + let value = black_box(&value); + let checksum = value + .iter() + .fold(0u8, |checksum, code| checksum ^ code as u8); + black_box(checksum) + }); +} + +fn set_sux(bencher: Bencher, len: usize) { + let input = codes(BITS, len); + let index = aligned_index::(len); + let replacement = input[index] ^ 1; + let value = sux_vec(BITS, &input); + bencher.with_inputs(|| value.clone()).bench_refs(|value| { + value.set_value(index, usize::from(replacement)); + black_box(&*value); + }); +} + +fn push_sux(bencher: Bencher, len: usize) { + let value = sux_vec(BITS, &codes(BITS, len)); + bencher.with_inputs(|| value.clone()).bench_refs(|value| { + value.push(0); + black_box(&*value); + }); +} + +fn pop_sux(bencher: Bencher, len: usize) { + let value = sux_vec(BITS, &codes(BITS, len)); + bencher.with_inputs(|| value.clone()).bench_refs(|value| { + black_box(value.pop()); + black_box(&*value); + }); +} + +fn extend_sux(bencher: Bencher, len: usize) { + let value = sux_vec(BITS, &codes(BITS, len)); + let extension = codes(BITS, len / 4); + bencher.with_inputs(|| value.clone()).bench_refs(|value| { + value.extend(extension.iter().map(|&code| usize::from(code))); + black_box(&*value); + }); +} + +fn construct_grit(bencher: Bencher, len: usize) +where + T: TypedBitElem + 'static, +{ + let input = codes(grit_width::(), len); + bencher.bench(|| black_box(grit_vec::(&input))); +} + +fn get_sequential_grit(bencher: Bencher, len: usize) +where + T: TypedBitElem + 'static, +{ + let input = codes(grit_width::(), len); + let value = grit_vec::(&input); + bencher.bench_local(|| { + let value = black_box(&value); + let mut checksum = 0u8; + for index in 0..len { + checksum ^= value.get(index).unwrap(); + } + black_box(checksum) + }); +} + +fn get_random_grit(bencher: Bencher, len: usize) +where + T: TypedBitElem + 'static, +{ + let input = codes(grit_width::(), len); + let value = grit_vec::(&input); + let indexes = indices(len); + bencher.bench_local(|| { + let value = black_box(&value); + let indexes = black_box(&indexes); + let mut checksum = 0u8; + for &index in indexes { + checksum ^= value.get(index).unwrap(); + } + black_box(checksum) + }); +} + +fn iter_grit(bencher: Bencher, len: usize) +where + T: TypedBitElem + 'static, +{ + let input = codes(grit_width::(), len); + bencher + .with_inputs(|| grit_vec::(&input)) + .bench_local_values(|value| { + let checksum = value + .into_iter() + .fold(0u8, |checksum, code| checksum ^ code); + black_box(checksum) + }); +} + +fn set_grit(bencher: Bencher, len: usize) +where + T: TypedBitElem + 'static, +{ + let bits = grit_width::(); + let width = usize::from(bits); + let input = codes(bits, len); + let index = aligned_index_for(width, len); + let replacement = input[index] ^ 1; + bencher + .with_inputs(|| grit_vec::(&input)) + .bench_local_refs(|value| { + let _ = black_box(value.set(index, replacement)); + black_box(&*value); + }); +} + +fn push_grit(bencher: Bencher, len: usize) +where + T: TypedBitElem + 'static, +{ + let input = codes(grit_width::(), len); + bencher + .with_inputs(|| grit_vec::(&input)) + .bench_local_refs(|value| { + let _ = black_box(value.push(0)); + black_box(&*value); + }); +} + +fn pop_grit(bencher: Bencher, len: usize) +where + T: TypedBitElem + 'static, +{ + let input = codes(grit_width::(), len); + bencher + .with_inputs(|| grit_vec::(&input)) + .bench_local_refs(|value| { + let _ = black_box(value.pop()); + black_box(&*value); + }); +} + +fn insert_grit(bencher: Bencher, len: usize) +where + T: TypedBitElem + 'static, +{ + let input = codes(grit_width::(), len); + bencher + .with_inputs(|| grit_vec::(&input)) + .bench_local_refs(|value| { + let _ = + black_box(value.insert(unaligned_index_for(grit_width::() as usize, len), 0)); + black_box(&*value); + }); +} + +fn remove_grit(bencher: Bencher, len: usize) +where + T: TypedBitElem + 'static, +{ + let input = codes(grit_width::(), len); + bencher + .with_inputs(|| grit_vec::(&input)) + .bench_local_refs(|value| { + let _ = black_box(value.remove(cross_word_index_for(grit_width::() as usize, len))); + black_box(&*value); + }); +} + +fn append_grit(bencher: Bencher, len: usize) +where + T: TypedBitElem + 'static, +{ + let input = codes(grit_width::(), len); + let extension = codes(grit_width::(), len / 4); + bencher + .with_inputs(|| grit_vec::(&input)) + .bench_local_refs(|value| { + let _ = black_box(value.append_iter(extension.iter().copied())); + black_box(&*value); + }); +} + +macro_rules! define_packed_case { + ($case:ident, $bits:literal, $len:literal) => { + mod $case { + use super::*; + + macro_rules! benchmark { + ($scenario:literal, $helper:ident, $legend:literal) => { + #[divan::bench( + name = concat!( + "packed_", + $scenario, + "/", + stringify!($case), + "/", + $legend + ) + )] + fn $helper(bencher: Bencher) { + super::$helper::<$bits>(bencher, $len); + } + }; + } + + benchmark!( + "construction/from_chars", + construct_fixed, + "compressed_intvec" + ); + benchmark!( + "access/get_sequential", + get_sequential_fixed, + "compressed_intvec" + ); + benchmark!("access/get_random_stream", get_random_fixed, "compressed_intvec"); + benchmark!("iteration/iterate", iter_fixed, "compressed_intvec"); + benchmark!("editing/set_position/aligned", set_fixed, "compressed_intvec"); + benchmark!("editing/push_boundary_probe", push_fixed, "compressed_intvec"); + benchmark!("editing/pop", pop_fixed, "compressed_intvec"); + benchmark!("editing/extend_bulk_25pct", extend_fixed, "compressed_intvec"); + benchmark!("editing/insert_position/unaligned", insert_fixed, "compressed_intvec"); + benchmark!("editing/remove_position/word_boundary_or_fallback", remove_fixed, "compressed_intvec"); + + benchmark!("construction/from_chars", construct_sux, "sux"); + benchmark!("access/get_sequential", get_sequential_sux, "sux"); + benchmark!("access/get_random_stream", get_random_sux, "sux"); + benchmark!("iteration/iterate", iter_sux, "sux"); + benchmark!("editing/set_position/aligned", set_sux, "sux"); + benchmark!("editing/push_boundary_probe", push_sux, "sux"); + benchmark!("editing/pop", pop_sux, "sux"); + benchmark!("editing/extend_bulk_25pct", extend_sux, "sux"); + } + }; +} + +crate::for_each_packed_bench_case!(define_packed_case); + +macro_rules! define_grit_case { + ($case:ident, $bits:literal, $len:literal, $type:ty) => { + mod $case { + use super::*; + + macro_rules! benchmark { + ($scenario:literal, $helper:ident) => { + #[divan::bench( + name = concat!( + "packed_", + $scenario, + "/", + stringify!($case), + "/grit_bitvec" + ) + )] + fn $helper(bencher: Bencher) { + crate::$helper::<$type>(bencher, $len); + } + }; + } + + benchmark!("construction/from_chars", construct_grit); + benchmark!("access/get_sequential", get_sequential_grit); + benchmark!("access/get_random_stream", get_random_grit); + benchmark!("iteration/iterate", iter_grit); + benchmark!("editing/set_position/aligned", set_grit); + benchmark!("editing/push_boundary_probe", push_grit); + benchmark!("editing/pop", pop_grit); + benchmark!("editing/insert_position/unaligned", insert_grit); + benchmark!("editing/remove_position/word_boundary_or_fallback", remove_grit); + benchmark!("editing/extend_bulk_25pct", append_grit); + } + }; +} + +mod grit_cases { + use super::*; + crate::for_each_grit_bench_case!(define_grit_case); +} + +fn fixed_vec(bits: u8, input: &[u8]) -> UFixedVec { + UFixedVec::from_iter_builder(input.iter().copied(), usize::from(bits)) + .build() + .unwrap() +} + +fn sux_vec(bits: u8, input: &[u8]) -> BitFieldVec> { + let mut value = BitFieldVec::with_capacity(usize::from(bits), input.len()); + value.extend(input.iter().map(|&code| usize::from(code))); + value +} + +fn grit_vec(input: &[u8]) -> TypedBitVec +where + T: TypedBitElem, +{ + let mut value = TypedBitVec::with_capacity(input.len()); + value.append_iter(input.iter().copied()).unwrap(); + value +} + +fn grit_width() -> u8 +where + T: TypedBitElem + 'static, +{ + use std::any::TypeId; + + if TypeId::of::() == TypeId::of::() { + 1 + } else if TypeId::of::() == TypeId::of::() { + 2 + } else if TypeId::of::() == TypeId::of::() { + 3 + } else if TypeId::of::() == TypeId::of::() { + 4 + } else if TypeId::of::() == TypeId::of::() { + 5 + } else if TypeId::of::() == TypeId::of::() { + 6 + } else if TypeId::of::() == TypeId::of::() { + 7 + } else { + unreachable!("unsupported grit-bitvec benchmark type") + } +} diff --git a/benches/packed_construction.rs b/benches/packed_construction.rs new file mode 100644 index 0000000..1ae5696 --- /dev/null +++ b/benches/packed_construction.rs @@ -0,0 +1,51 @@ +#[path = "packed_support/mod.rs"] +mod support; + +use divan::{Bencher, black_box}; +use support::{codes, packed}; + +fn main() { + divan::main(); +} + +fn construct_packed(bencher: Bencher, len: usize) { + let input = codes(BITS, len); + bencher.bench(|| black_box(packed::(&input))); +} + +fn construct_vec(bencher: Bencher, len: usize) { + let input = codes(BITS, len); + bencher.bench(|| black_box(input.iter().copied().collect::>())); +} + +macro_rules! define_case { + ($case:ident, $bits:literal, $len:literal) => { + mod $case { + use super::*; + + #[divan::bench( + name = concat!( + "packed_construction/from_chars/", + stringify!($case), + "/ours_packed_string" + ) + )] + fn ours_packed_string(bencher: Bencher) { + super::construct_packed::<$bits>(bencher, $len); + } + + #[divan::bench( + name = concat!( + "packed_construction/from_chars/", + stringify!($case), + "/vec_u8" + ) + )] + fn vec_u8(bencher: Bencher) { + super::construct_vec::<$bits>(bencher, $len); + } + } + }; +} + +crate::for_each_packed_bench_case!(define_case); diff --git a/benches/packed_editing.rs b/benches/packed_editing.rs new file mode 100644 index 0000000..6d739c6 --- /dev/null +++ b/benches/packed_editing.rs @@ -0,0 +1,200 @@ +#[path = "packed_support/mod.rs"] +mod support; + +use divan::{Bencher, black_box}; +use support::{Code, aligned_index, codes, cross_word_index, packed, unaligned_index}; + +fn main() { + divan::main(); +} + +fn set_middle_packed(bencher: Bencher, len: usize) { + let input_codes = codes(BITS, len); + let index = aligned_index::(len); + let replacement = Code(input_codes[index] ^ 1); + let input = packed::(&input_codes); + bencher.with_inputs(|| input.clone()).bench_refs(|value| { + let previous = value.set(index, replacement); + black_box(&*value); + black_box(previous) + }); +} + +fn set_middle_vec(bencher: Bencher, len: usize) { + let input = codes(BITS, len); + let index = aligned_index::(len); + let replacement = input[index] ^ 1; + bencher.with_inputs(|| input.clone()).bench_refs(|value| { + let old = std::mem::replace(&mut value[index], replacement); + black_box(&*value); + black_box(old) + }); +} + +fn push_packed(bencher: Bencher, len: usize) { + let input = packed::(&codes(BITS, len)); + bencher.with_inputs(|| input.clone()).bench_refs(|value| { + value.push(Code(0)); + black_box(&*value); + black_box(value.char_len()) + }); +} + +fn push_amortized_packed(bencher: Bencher, len: usize) { + let input = packed::(&codes(BITS, len - 1)); + bencher.with_inputs(|| input.clone()).bench_refs(|value| { + value.push(Code(0)); + black_box(&*value); + black_box(value.char_len()) + }); +} + +fn push_vec(bencher: Bencher, len: usize) { + let input = codes(BITS, len); + bencher.with_inputs(|| input.clone()).bench_refs(|value| { + value.push(0); + black_box(&*value); + black_box(value.len()) + }); +} + +fn push_amortized_vec(bencher: Bencher, len: usize) { + let input = codes(BITS, len - 1); + bencher + .with_inputs(|| { + let mut value = input.clone(); + value.reserve(1); + value + }) + .bench_refs(|value| { + value.push(0); + black_box(&*value); + black_box(value.len()) + }); +} + +fn pop_packed(bencher: Bencher, len: usize) { + let input = packed::(&codes(BITS, len)); + bencher.with_inputs(|| input.clone()).bench_refs(|value| { + let popped = value.pop(); + black_box(&*value); + black_box(popped) + }); +} + +fn pop_vec(bencher: Bencher, len: usize) { + let input = codes(BITS, len); + bencher.with_inputs(|| input.clone()).bench_refs(|value| { + let popped = value.pop(); + black_box(&*value); + black_box(popped) + }); +} + +fn insert_middle_packed(bencher: Bencher, len: usize) { + let input = packed::(&codes(BITS, len)); + let index = unaligned_index::(len); + bencher.with_inputs(|| input.clone()).bench_refs(|value| { + value.insert(index, Code(0)); + black_box(&*value); + black_box(value.char_len()) + }); +} + +fn insert_middle_vec(bencher: Bencher, len: usize) { + let input = codes(BITS, len); + let index = unaligned_index::(len); + bencher.with_inputs(|| input.clone()).bench_refs(|value| { + value.insert(index, 0); + black_box(&*value); + black_box(value.len()) + }); +} + +fn remove_middle_packed(bencher: Bencher, len: usize) { + let input = packed::(&codes(BITS, len)); + let index = cross_word_index::(len); + bencher.with_inputs(|| input.clone()).bench_refs(|value| { + let removed = value.remove(index); + black_box(&*value); + black_box(removed) + }); +} + +fn remove_middle_vec(bencher: Bencher, len: usize) { + let input = codes(BITS, len); + let index = cross_word_index::(len); + bencher.with_inputs(|| input.clone()).bench_refs(|value| { + let removed = value.remove(index); + black_box(&*value); + black_box(removed) + }); +} + +fn extend_packed(bencher: Bencher, len: usize) { + let input = packed::(&codes(BITS, len)); + let extension = codes(BITS, len / 4); + bencher.with_inputs(|| input.clone()).bench_refs(|value| { + value.extend(extension.iter().copied().map(Code)); + black_box(&*value); + black_box(value.char_len()) + }); +} + +fn extend_vec(bencher: Bencher, len: usize) { + let input = codes(BITS, len); + let extension = codes(BITS, len / 4); + bencher.with_inputs(|| input.clone()).bench_refs(|value| { + value.extend(extension.iter().copied()); + black_box(&*value); + black_box(value.len()) + }); +} + +macro_rules! define_case { + ($case:ident, $bits:literal, $len:literal) => { + mod $case { + use super::*; + + macro_rules! pair { + ($scenario:literal, $packed:ident, $vec:ident) => { + #[divan::bench( + name = concat!( + "packed_editing/", + $scenario, + "/", + stringify!($case), + "/ours_packed_string" + ) + )] + fn $packed(bencher: Bencher) { + crate::$packed::<$bits>(bencher, $len); + } + + #[divan::bench( + name = concat!( + "packed_editing/", + $scenario, + "/", + stringify!($case), + "/vec_u8" + ) + )] + fn $vec(bencher: Bencher) { + crate::$vec::<$bits>(bencher, $len); + } + }; + } + + pair!("set_position/aligned", set_middle_packed, set_middle_vec); + pair!("push_boundary_probe", push_packed, push_vec); + pair!("push_amortized", push_amortized_packed, push_amortized_vec); + pair!("pop", pop_packed, pop_vec); + pair!("insert_position/unaligned", insert_middle_packed, insert_middle_vec); + pair!("remove_position/word_boundary_or_fallback", remove_middle_packed, remove_middle_vec); + pair!("extend_bulk_25pct", extend_packed, extend_vec); + } + }; +} + +crate::for_each_packed_bench_case!(define_case); diff --git a/benches/packed_iteration.rs b/benches/packed_iteration.rs new file mode 100644 index 0000000..ec918c3 --- /dev/null +++ b/benches/packed_iteration.rs @@ -0,0 +1,85 @@ +#[path = "packed_support/mod.rs"] +mod support; + +use divan::{Bencher, black_box}; +use support::{codes, packed}; + +fn main() { + divan::main(); +} + +fn iterate_packed(bencher: Bencher, len: usize) { + let input = codes(BITS, len); + let value = packed::(&input); + bencher.bench(|| { + let value = black_box(&value); + let checksum = value.iter().fold(0u8, |checksum, code| checksum ^ code.0); + black_box(checksum) + }); +} + +fn iterate_vec(bencher: Bencher, len: usize) { + let input = codes(BITS, len); + bencher.bench(|| { + let input = black_box(&input); + let checksum = input.iter().fold(0u8, |checksum, &code| checksum ^ code); + black_box(checksum) + }); +} + +fn iterate_packed_str(bencher: Bencher, len: usize) { + let input = codes(BITS, len); + let mut source = Vec::with_capacity(len + 1); + source.push(0); + source.extend_from_slice(&input); + let owner = packed::(&source); + let value = owner.as_packed_str().slice_from(1); + bencher.bench(|| { + let value = black_box(value); + let checksum = value.iter().fold(0u8, |checksum, code| checksum ^ code.0); + black_box(checksum) + }); +} + +macro_rules! define_case { + ($case:ident, $bits:literal, $len:literal) => { + mod $case { + use super::*; + + #[divan::bench( + name = concat!( + "packed_iteration/iterate/", + stringify!($case), + "/ours_packed_string" + ) + )] + fn ours_packed_string(bencher: Bencher) { + super::iterate_packed::<$bits>(bencher, $len); + } + + #[divan::bench( + name = concat!( + "packed_iteration/iterate/", + stringify!($case), + "/ours_packed_str_unaligned" + ) + )] + fn ours_packed_str(bencher: Bencher) { + super::iterate_packed_str::<$bits>(bencher, $len); + } + + #[divan::bench( + name = concat!( + "packed_iteration/iterate/", + stringify!($case), + "/vec_u8" + ) + )] + fn vec_u8(bencher: Bencher) { + super::iterate_vec::<$bits>(bencher, $len); + } + } + }; +} + +crate::for_each_packed_bench_case!(define_case); diff --git a/benches/packed_native.rs b/benches/packed_native.rs new file mode 100644 index 0000000..3a74d04 --- /dev/null +++ b/benches/packed_native.rs @@ -0,0 +1,488 @@ +#[path = "packed_support/mod.rs"] +mod support; + +use std::{ + collections::hash_map::DefaultHasher, + hash::{Hash, Hasher}, +}; + +use bit_string::PackedString; +use divan::{Bencher, black_box}; +use int_intervals::UsizeCO; +use support::{ + codes, cross_word_index, cross_word_index_for_offset, mix64, packed, unaligned_index, +}; + +const NEEDLE_LENGTH: usize = 8; + +fn main() { + divan::main(); +} + +fn find_packed_string(bencher: Bencher, len: usize) { + let (input, needle_input, start) = present_case::(len, false); + let value = packed::(&input); + let needle = packed::(&needle_input); + assert_eq!(value.find(&needle), Some(start)); + bencher.bench(|| black_box(value.find(&needle))); +} + +fn find_packed_str(bencher: Bencher, len: usize) { + let (input, needle_input, start) = present_case::(len, false); + let value_owner = offset_packed::(&input); + let needle_owner = packed::(&needle_input); + let value = value_owner.as_packed_str().slice_from(1); + let needle = needle_owner.as_packed_str(); + assert_eq!(value.find(needle), Some(start)); + bencher.bench(|| black_box(value.find(needle))); +} + +fn find_absent_packed_string(bencher: Bencher, len: usize) { + let (input, needle_input) = absent_case::(len); + let value = packed::(&input); + let needle = packed::(&needle_input); + assert_eq!(value.find(&needle), None); + bencher.bench(|| black_box(value.find(&needle))); +} + +fn find_absent_packed_str(bencher: Bencher, len: usize) { + let (input, needle_input) = absent_case::(len); + let value_owner = offset_packed::(&input); + let needle_owner = packed::(&needle_input); + let value = value_owner.as_packed_str().slice_from(1); + let needle = needle_owner.as_packed_str(); + assert_eq!(value.find(needle), None); + bencher.bench(|| black_box(value.find(needle))); +} + +fn rfind_packed_string(bencher: Bencher, len: usize) { + let (input, needle_input, start) = present_case::(len, true); + let value = packed::(&input); + let needle = packed::(&needle_input); + assert_eq!(value.rfind(&needle), Some(start)); + bencher.bench(|| black_box(value.rfind(&needle))); +} + +fn rfind_packed_str(bencher: Bencher, len: usize) { + let (input, needle_input, start) = present_case::(len, true); + let value_owner = offset_packed::(&input); + let needle_owner = packed::(&needle_input); + let value = value_owner.as_packed_str().slice_from(1); + let needle = needle_owner.as_packed_str(); + assert_eq!(value.rfind(needle), Some(start)); + bencher.bench(|| black_box(value.rfind(needle))); +} + +fn rfind_absent_packed_string(bencher: Bencher, len: usize) { + let (input, needle_input) = absent_case::(len); + let value = packed::(&input); + let needle = packed::(&needle_input); + assert_eq!(value.rfind(&needle), None); + bencher.bench(|| black_box(value.rfind(&needle))); +} + +fn rfind_absent_packed_str(bencher: Bencher, len: usize) { + let (input, needle_input) = absent_case::(len); + let value_owner = offset_packed::(&input); + let needle_owner = packed::(&needle_input); + let value = value_owner.as_packed_str().slice_from(1); + let needle = needle_owner.as_packed_str(); + assert_eq!(value.rfind(needle), None); + bencher.bench(|| black_box(value.rfind(needle))); +} + +fn contains_packed_string(bencher: Bencher, len: usize) { + let (input, needle_input, start) = present_case::(len, false); + let value = packed::(&input); + let needle = packed::(&needle_input); + assert_eq!(value.find(&needle), Some(start)); + assert!(value.contains(&needle)); + bencher.bench(|| black_box(value.contains(&needle))); +} + +fn contains_packed_str(bencher: Bencher, len: usize) { + let (input, needle_input, start) = present_case::(len, false); + let value_owner = offset_packed::(&input); + let needle_owner = packed::(&needle_input); + let value = value_owner.as_packed_str().slice_from(1); + let needle = needle_owner.as_packed_str(); + assert_eq!(value.find(needle), Some(start)); + assert!(value.contains(needle)); + bencher.bench(|| black_box(value.contains(needle))); +} + +fn contains_absent_packed_string(bencher: Bencher, len: usize) { + let (input, needle_input) = absent_case::(len); + let value = packed::(&input); + let needle = packed::(&needle_input); + assert!(!value.contains(&needle)); + bencher.bench(|| black_box(value.contains(&needle))); +} + +fn contains_absent_packed_str(bencher: Bencher, len: usize) { + let (input, needle_input) = absent_case::(len); + let value_owner = offset_packed::(&input); + let needle_owner = packed::(&needle_input); + let value = value_owner.as_packed_str().slice_from(1); + let needle = needle_owner.as_packed_str(); + assert!(!value.contains(needle)); + bencher.bench(|| black_box(value.contains(needle))); +} + +fn matches_at_packed_string(bencher: Bencher, len: usize) { + let input = codes(BITS, len); + let value = packed::(&input); + let start = unaligned_index::(len); + let needle_len = NEEDLE_LENGTH.min(len - start); + let needle = packed::(&input[start..start + needle_len]); + assert!(value.matches_at(start, &needle)); + bencher.bench(|| black_box(value.matches_at(start, &needle))); +} + +fn matches_at_packed_str(bencher: Bencher, len: usize) { + let input = codes(BITS, len); + let value_owner = offset_packed::(&input); + let start = unaligned_index::(len); + let needle_len = NEEDLE_LENGTH.min(len - start); + let needle_owner = packed::(&input[start..start + needle_len]); + let value = value_owner.as_packed_str().slice_from(1); + let needle = needle_owner.as_packed_str(); + assert!(value.matches_at(start, needle)); + bencher.bench(|| black_box(value.matches_at(start, needle))); +} + +fn starts_with_packed_string(bencher: Bencher, len: usize) { + let input = codes(BITS, len); + let value = packed::(&input); + let prefix = packed::(&input[..NEEDLE_LENGTH]); + bencher.bench(|| black_box(value.starts_with(&prefix))); +} + +fn starts_with_packed_str(bencher: Bencher, len: usize) { + let input = codes(BITS, len); + let value_owner = offset_packed::(&input); + let prefix_owner = packed::(&input[..NEEDLE_LENGTH]); + let value = value_owner.as_packed_str().slice_from(1); + let prefix = prefix_owner.as_packed_str(); + bencher.bench(|| black_box(value.starts_with(prefix))); +} + +fn ends_with_packed_string(bencher: Bencher, len: usize) { + let input = codes(BITS, len); + let value = packed::(&input); + let suffix = packed::(&input[len - NEEDLE_LENGTH..]); + bencher.bench(|| black_box(value.ends_with(&suffix))); +} + +fn ends_with_packed_str(bencher: Bencher, len: usize) { + let input = codes(BITS, len); + let value_owner = offset_packed::(&input); + let suffix_owner = packed::(&input[len - NEEDLE_LENGTH..]); + let value = value_owner.as_packed_str().slice_from(1); + let suffix = suffix_owner.as_packed_str(); + bencher.bench(|| black_box(value.ends_with(suffix))); +} + +fn strip_prefix_packed_string(bencher: Bencher, len: usize) { + let input = codes(BITS, len); + let value = packed::(&input); + let prefix = packed::(&input[..NEEDLE_LENGTH]); + bencher.bench(|| black_box(value.strip_prefix(&prefix))); +} + +fn strip_prefix_packed_str(bencher: Bencher, len: usize) { + let input = codes(BITS, len); + let value_owner = offset_packed::(&input); + let prefix_owner = packed::(&input[..NEEDLE_LENGTH]); + let value = value_owner.as_packed_str().slice_from(1); + let prefix = prefix_owner.as_packed_str(); + bencher.bench(|| black_box(value.strip_prefix(prefix))); +} + +fn strip_suffix_packed_string(bencher: Bencher, len: usize) { + let input = codes(BITS, len); + let value = packed::(&input); + let suffix = packed::(&input[len - NEEDLE_LENGTH..]); + bencher.bench(|| black_box(value.strip_suffix(&suffix))); +} + +fn strip_suffix_packed_str(bencher: Bencher, len: usize) { + let input = codes(BITS, len); + let value_owner = offset_packed::(&input); + let suffix_owner = packed::(&input[len - NEEDLE_LENGTH..]); + let value = value_owner.as_packed_str().slice_from(1); + let suffix = suffix_owner.as_packed_str(); + bencher.bench(|| black_box(value.strip_suffix(suffix))); +} + +fn cmp_packed_string(bencher: Bencher, len: usize) { + let left_codes = codes(BITS, len); + let mut right_codes = left_codes.clone(); + right_codes[cross_word_index::(len)] ^= 1; + let left = packed::(&left_codes); + let right = packed::(&right_codes); + bencher.bench(|| black_box(left.cmp(&right))); +} + +fn cmp_packed_str(bencher: Bencher, len: usize) { + let left_codes = codes(BITS, len); + let mut right_codes = left_codes.clone(); + right_codes[cross_word_index_for_offset(usize::from(BITS), len, usize::from(BITS))] ^= 1; + let left_owner = offset_packed::(&left_codes); + let right_owner = offset_packed::(&right_codes); + let left = left_owner.as_packed_str().slice_from(1); + let right = right_owner.as_packed_str().slice_from(1); + bencher.bench(|| black_box(left.cmp(&right))); +} + +fn hash_packed_string(bencher: Bencher, len: usize) { + let value = packed::(&codes(BITS, len)); + bencher.bench(|| { + let mut hasher = DefaultHasher::new(); + value.hash(&mut hasher); + black_box(hasher.finish()) + }); +} + +fn slice_packed_string(bencher: Bencher, len: usize) { + let value = packed::(&codes(BITS, len)); + bencher.bench(|| black_box(value.slice(interval(len / 4, len / 2)))); +} + +fn slice_packed_str(bencher: Bencher, len: usize) { + let owner = offset_packed::(&codes(BITS, len)); + let value = owner.as_packed_str().slice_from(1); + bencher.bench(|| black_box(value.slice(interval(len / 4, len / 2)))); +} + +fn to_packed_string(bencher: Bencher, len: usize) { + let owner = offset_packed::(&codes(BITS, len)); + let value = owner.as_packed_str().slice_from(1); + bencher.bench(|| black_box(value.to_packed_string())); +} + +fn replace_interval(bencher: Bencher, len: usize) { + let value = packed::(&codes(BITS, len)); + let replacement = packed::(&codes(BITS, len / 4)); + bencher.bench(|| black_box(value.replace_interval(interval(len / 4, len / 4), &replacement))); +} + +macro_rules! define_two_case_benchmarks { + ($module:ident, $scenario:literal, $string_fn:ident, $str_fn:ident) => { + mod $module { + macro_rules! define_case { + ($case:ident, $bits:literal, $len:literal) => { + mod $case { + #[divan::bench( + name = concat!( + $scenario, + "/", + stringify!($case), + "/ours_packed_string_aligned" + ) + )] + fn ours_packed_string(bencher: divan::Bencher) { + crate::$string_fn::<$bits>(bencher, $len); + } + + #[divan::bench( + name = concat!( + $scenario, + "/", + stringify!($case), + "/ours_packed_str_unaligned" + ) + )] + fn ours_packed_str(bencher: divan::Bencher) { + crate::$str_fn::<$bits>(bencher, $len); + } + } + }; + } + + crate::for_each_packed_bench_case!(define_case); + } + }; +} + +macro_rules! define_one_case_benchmark { + ($module:ident, $scenario:literal, $fn_name:ident, $legend:literal) => { + mod $module { + macro_rules! define_case { + ($case:ident, $bits:literal, $len:literal) => { + mod $case { + #[divan::bench( + name = concat!( + $scenario, + "/", + stringify!($case), + "/", + $legend + ) + )] + fn benchmark(bencher: divan::Bencher) { + crate::$fn_name::<$bits>(bencher, $len); + } + } + }; + } + + crate::for_each_packed_bench_case!(define_case); + } + }; +} + +define_two_case_benchmarks!( + find_cases, + "packed_matching/find_present_late", + find_packed_string, + find_packed_str +); +define_two_case_benchmarks!( + find_absent_cases, + "packed_matching/find_absent_early_reject", + find_absent_packed_string, + find_absent_packed_str +); +define_two_case_benchmarks!( + rfind_cases, + "packed_matching/rfind_present_scan_late", + rfind_packed_string, + rfind_packed_str +); +define_two_case_benchmarks!( + rfind_absent_cases, + "packed_matching/rfind_absent_early_reject", + rfind_absent_packed_string, + rfind_absent_packed_str +); +define_two_case_benchmarks!( + contains_cases, + "packed_matching/contains_present_late", + contains_packed_string, + contains_packed_str +); +define_two_case_benchmarks!( + contains_absent_cases, + "packed_matching/contains_absent_early_reject", + contains_absent_packed_string, + contains_absent_packed_str +); +define_two_case_benchmarks!( + matches_at_cases, + "packed_matching/matches_at/unaligned", + matches_at_packed_string, + matches_at_packed_str +); +define_two_case_benchmarks!( + starts_with_cases, + "packed_matching/starts_with", + starts_with_packed_string, + starts_with_packed_str +); +define_two_case_benchmarks!( + ends_with_cases, + "packed_matching/ends_with", + ends_with_packed_string, + ends_with_packed_str +); +define_two_case_benchmarks!( + strip_prefix_cases, + "packed_matching/strip_prefix", + strip_prefix_packed_string, + strip_prefix_packed_str +); +define_two_case_benchmarks!( + strip_suffix_cases, + "packed_matching/strip_suffix", + strip_suffix_packed_string, + strip_suffix_packed_str +); +define_two_case_benchmarks!( + cmp_cases, + "packed_ordering/cmp_difference/word_boundary_or_fallback", + cmp_packed_string, + cmp_packed_str +); +define_two_case_benchmarks!( + slice_cases, + "packed_slice/slice_middle/unaligned_view", + slice_packed_string, + slice_packed_str +); + +define_one_case_benchmark!( + hash_cases, + "packed_hash/hash", + hash_packed_string, + "ours_packed_string" +); +define_one_case_benchmark!( + conversion_cases, + "packed_conversion/to_packed_string", + to_packed_string, + "ours_packed_str" +); +define_one_case_benchmark!( + replacement_cases, + "packed_editing/replace_interval", + replace_interval, + "ours_packed_string" +); + +fn interval(start: usize, len: usize) -> UsizeCO { + UsizeCO::checked_from_start_len(start, len).unwrap() +} + +fn offset_packed(input: &[u8]) -> PackedString { + let mut source = Vec::with_capacity(input.len() + 1); + source.push(0); + source.extend_from_slice(input); + packed::(&source) +} + +fn search_needle_length(bits: u8, len: usize) -> usize { + let len = len.max(1); + let logarithm = (usize::BITS - (len - 1).leading_zeros()) as usize; + let needed_bits = logarithm + 10; + needed_bits.div_ceil(usize::from(bits)).clamp(1, len) +} + +fn present_case(len: usize, from_right: bool) -> (Vec, Vec, usize) { + let needle_len = search_needle_length(BITS, len); + let start = if from_right { 0 } else { len - needle_len }; + let mask = if BITS == 8 { + u8::MAX + } else { + ((1u16 << BITS) - 1) as u8 + }; + + for attempt in 0..1024u64 { + let needle: Vec<_> = (0..needle_len) + .map(|index| { + (mix64(index as u64 ^ attempt.wrapping_mul(0x9e37_79b9_7f4a_7c15)) as u8) & mask + }) + .collect(); + let mut input = codes(BITS, len); + input[start..start + needle_len].copy_from_slice(&needle); + let collision = if from_right { + (start + 1..=len - needle_len).any(|index| input[index..index + needle_len] == needle) + } else { + (0..start).any(|index| input[index..index + needle_len] == needle) + }; + if !collision { + return (input, needle, start); + } + } + panic!("could not create a directional unique search needle"); +} + +fn absent_case(len: usize) -> (Vec, Vec) { + let needle_len = search_needle_length(BITS, len); + let max_code = if BITS == 8 { + u8::MAX + } else { + ((1u16 << BITS) - 1) as u8 + }; + (vec![0; len], vec![max_code; needle_len]) +} diff --git a/benches/packed_support/mod.rs b/benches/packed_support/mod.rs new file mode 100644 index 0000000..e234fd2 --- /dev/null +++ b/benches/packed_support/mod.rs @@ -0,0 +1,245 @@ +#![allow(dead_code)] + +use bit_string::{PackedString, traits::PackedChar}; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub(crate) struct Code(pub(crate) u8); + +impl PackedChar for Code { + fn code(self) -> u8 { + self.0 + } + + fn from_code(code: u8) -> Option { + let limit = if BITS == 8 { 256 } else { 1 << BITS }; + (u16::from(code) < limit).then_some(Self(code)) + } +} + +#[macro_export] +macro_rules! for_each_packed_case { + ($macro:ident) => { + $macro!(bits_1_len_16, 1, 16); + $macro!(bits_1_len_64, 1, 64); + $macro!(bits_1_len_1024, 1, 1_024); + $macro!(bits_1_len_65536, 1, 65_536); + $macro!(bits_1_len_1048576, 1, 1_048_576); + $macro!(bits_2_len_16, 2, 16); + $macro!(bits_2_len_64, 2, 64); + $macro!(bits_2_len_1024, 2, 1_024); + $macro!(bits_2_len_65536, 2, 65_536); + $macro!(bits_2_len_1048576, 2, 1_048_576); + $macro!(bits_3_len_16, 3, 16); + $macro!(bits_3_len_64, 3, 64); + $macro!(bits_3_len_1024, 3, 1_024); + $macro!(bits_3_len_65536, 3, 65_536); + $macro!(bits_3_len_1048576, 3, 1_048_576); + $macro!(bits_4_len_16, 4, 16); + $macro!(bits_4_len_64, 4, 64); + $macro!(bits_4_len_1024, 4, 1_024); + $macro!(bits_4_len_65536, 4, 65_536); + $macro!(bits_4_len_1048576, 4, 1_048_576); + $macro!(bits_7_len_16, 7, 16); + $macro!(bits_7_len_64, 7, 64); + $macro!(bits_7_len_1024, 7, 1_024); + $macro!(bits_7_len_65536, 7, 65_536); + $macro!(bits_7_len_1048576, 7, 1_048_576); + $macro!(bits_8_len_16, 8, 16); + $macro!(bits_8_len_64, 8, 64); + $macro!(bits_8_len_1024, 8, 1_024); + $macro!(bits_8_len_65536, 8, 65_536); + $macro!(bits_8_len_1048576, 8, 1_048_576); + }; +} + +#[macro_export] +macro_rules! for_each_grit_case { + ($macro:ident) => { + $macro!(bits_1_len_16, 1, 16, grit_bitvec::u8_as_u1); + $macro!(bits_1_len_64, 1, 64, grit_bitvec::u8_as_u1); + $macro!(bits_1_len_1024, 1, 1_024, grit_bitvec::u8_as_u1); + $macro!(bits_1_len_65536, 1, 65_536, grit_bitvec::u8_as_u1); + $macro!(bits_1_len_1048576, 1, 1_048_576, grit_bitvec::u8_as_u1); + $macro!(bits_2_len_16, 2, 16, grit_bitvec::u8_as_u2); + $macro!(bits_2_len_64, 2, 64, grit_bitvec::u8_as_u2); + $macro!(bits_2_len_1024, 2, 1_024, grit_bitvec::u8_as_u2); + $macro!(bits_2_len_65536, 2, 65_536, grit_bitvec::u8_as_u2); + $macro!(bits_2_len_1048576, 2, 1_048_576, grit_bitvec::u8_as_u2); + $macro!(bits_3_len_16, 3, 16, grit_bitvec::u8_as_u3); + $macro!(bits_3_len_64, 3, 64, grit_bitvec::u8_as_u3); + $macro!(bits_3_len_1024, 3, 1_024, grit_bitvec::u8_as_u3); + $macro!(bits_3_len_65536, 3, 65_536, grit_bitvec::u8_as_u3); + $macro!(bits_3_len_1048576, 3, 1_048_576, grit_bitvec::u8_as_u3); + $macro!(bits_4_len_16, 4, 16, grit_bitvec::u8_as_u4); + $macro!(bits_4_len_64, 4, 64, grit_bitvec::u8_as_u4); + $macro!(bits_4_len_1024, 4, 1_024, grit_bitvec::u8_as_u4); + $macro!(bits_4_len_65536, 4, 65_536, grit_bitvec::u8_as_u4); + $macro!(bits_4_len_1048576, 4, 1_048_576, grit_bitvec::u8_as_u4); + $macro!(bits_7_len_16, 7, 16, grit_bitvec::u8_as_u7); + $macro!(bits_7_len_64, 7, 64, grit_bitvec::u8_as_u7); + $macro!(bits_7_len_1024, 7, 1_024, grit_bitvec::u8_as_u7); + $macro!(bits_7_len_65536, 7, 65_536, grit_bitvec::u8_as_u7); + $macro!(bits_7_len_1048576, 7, 1_048_576, grit_bitvec::u8_as_u7); + }; +} + +/// The small set of cases run by default in PR and CodSpeed benchmarks. +/// +/// Each width gets a tiny case and a large case. The full width × length +/// matrix remains available through the `characterization` feature below. +#[macro_export] +macro_rules! for_each_packed_regression_case { + ($macro:ident) => { + $macro!(bits_1_len_64, 1, 64); + $macro!(bits_1_len_1048576, 1, 1_048_576); + $macro!(bits_2_len_64, 2, 64); + $macro!(bits_2_len_1048576, 2, 1_048_576); + $macro!(bits_3_len_1024, 3, 1_024); + $macro!(bits_3_len_1048576, 3, 1_048_576); + $macro!(bits_4_len_64, 4, 64); + $macro!(bits_4_len_1048576, 4, 1_048_576); + $macro!(bits_7_len_1024, 7, 1_024); + $macro!(bits_7_len_1048576, 7, 1_048_576); + $macro!(bits_8_len_64, 8, 64); + $macro!(bits_8_len_1048576, 8, 1_048_576); + }; +} + +#[macro_export] +macro_rules! for_each_grit_regression_case { + ($macro:ident) => { + $macro!(bits_1_len_64, 1, 64, grit_bitvec::u8_as_u1); + $macro!(bits_1_len_1048576, 1, 1_048_576, grit_bitvec::u8_as_u1); + $macro!(bits_2_len_64, 2, 64, grit_bitvec::u8_as_u2); + $macro!(bits_2_len_1048576, 2, 1_048_576, grit_bitvec::u8_as_u2); + $macro!(bits_3_len_1024, 3, 1_024, grit_bitvec::u8_as_u3); + $macro!(bits_3_len_1048576, 3, 1_048_576, grit_bitvec::u8_as_u3); + $macro!(bits_4_len_64, 4, 64, grit_bitvec::u8_as_u4); + $macro!(bits_4_len_1048576, 4, 1_048_576, grit_bitvec::u8_as_u4); + $macro!(bits_7_len_1024, 7, 1_024, grit_bitvec::u8_as_u7); + $macro!(bits_7_len_1048576, 7, 1_048_576, grit_bitvec::u8_as_u7); + }; +} + +#[cfg(feature = "characterization")] +#[macro_export] +macro_rules! for_each_packed_bench_case { + ($macro:ident) => { + $crate::for_each_packed_case!($macro); + }; +} + +#[cfg(not(feature = "characterization"))] +#[macro_export] +macro_rules! for_each_packed_bench_case { + ($macro:ident) => { + $crate::for_each_packed_regression_case!($macro); + }; +} + +#[cfg(feature = "characterization")] +#[macro_export] +macro_rules! for_each_grit_bench_case { + ($macro:ident) => { + $crate::for_each_grit_case!($macro); + }; +} + +#[cfg(not(feature = "characterization"))] +#[macro_export] +macro_rules! for_each_grit_bench_case { + ($macro:ident) => { + $crate::for_each_grit_regression_case!($macro); + }; +} + +pub(crate) fn codes(bits: u8, len: usize) -> Vec { + let mask = if bits == 8 { + u8::MAX + } else { + ((1u16 << bits) - 1) as u8 + }; + + (0..len) + .map(|index| (mix64(index as u64) as u8) & mask) + .collect() +} + +pub(crate) fn packed(codes: &[u8]) -> PackedString { + PackedString::from_chars(codes.iter().copied().map(Code)) +} + +pub(crate) fn indices(len: usize) -> Vec { + (0..len.max(1)) + .map(|index| (mix64(index as u64 ^ 0x517c_c1b7_2722_0a95) as usize) % len) + .collect() +} + +pub(crate) fn aligned_index(len: usize) -> usize { + aligned_index_for(usize::from(BITS), len) +} + +pub(crate) fn aligned_index_for(bits: usize, len: usize) -> usize { + let period = 64 / gcd(bits, 64); + (len / 2 / period) * period +} + +pub(crate) fn unaligned_index(len: usize) -> usize { + unaligned_index_for(usize::from(BITS), len) +} + +pub(crate) fn unaligned_index_for(bits: usize, len: usize) -> usize { + let middle = len / 2; + let period = 64 / gcd(bits, 64); + for distance in 0..=len { + let left = middle.checked_sub(distance); + let right = middle.checked_add(distance); + for candidate in [right, left].into_iter().flatten() { + if candidate < len && candidate % period != 0 { + return candidate; + } + } + } + 0 +} + +/// Select a position whose packed character is close to the end of a word. +/// For widths that do not divide 64 (notably 3 and 7), this selects a true +/// cross-word character whenever the input is large enough. Other widths +/// fall back to the nearest useful non-aligned position. +pub(crate) fn cross_word_index(len: usize) -> usize { + cross_word_index_for_offset(usize::from(BITS), len, 0) +} + +pub(crate) fn cross_word_index_for(width: usize, len: usize) -> usize { + cross_word_index_for_offset(width, len, 0) +} + +pub(crate) fn cross_word_index_for_offset(width: usize, len: usize, offset: usize) -> usize { + let middle = len / 2; + for distance in 0..64 { + for candidate in [middle.saturating_sub(distance), middle + distance] { + if candidate < len { + let residue = (offset + candidate * width) % 64; + if residue > 64 - width { + return candidate; + } + } + } + } + unaligned_index_for(width, len) +} + +fn gcd(mut left: usize, mut right: usize) -> usize { + while right != 0 { + (left, right) = (right, left % right); + } + left +} + +pub(crate) fn mix64(mut value: u64) -> u64 { + value = value.wrapping_add(0x9e37_79b9_7f4a_7c15); + value = (value ^ (value >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9); + value = (value ^ (value >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb); + value ^ (value >> 31) +} diff --git a/pyproject.toml b/pyproject.toml deleted file mode 100644 index 9aa5d11..0000000 --- a/pyproject.toml +++ /dev/null @@ -1,8 +0,0 @@ -[project] -name = "bit-string-dev-tools" -version = "0.1.0" -requires-python = ">=3.11" -dependencies = [] - -[tool.uv] -package = false diff --git a/scripts/bench_config.py b/scripts/bench_config.py deleted file mode 100644 index 509c677..0000000 --- a/scripts/bench_config.py +++ /dev/null @@ -1,4 +0,0 @@ -"""Shared constants for reproducible benchmark runs.""" - -BASELINE_SHA = "c15fa1c254ea5b2bbabfe5f008d41f91e8297b89" -BENCH_RUSTFLAGS = "-C target-cpu=native" diff --git a/scripts/compare_bench.py b/scripts/compare_bench.py deleted file mode 100755 index 8cbdd75..0000000 --- a/scripts/compare_bench.py +++ /dev/null @@ -1,84 +0,0 @@ -#!/usr/bin/env python3 -"""Compare repeated Divan reports using the median of per-run medians.""" - -from __future__ import annotations - -import argparse -import statistics -import sys -from pathlib import Path - -from divan_parser import parse_report - - -def aggregate(paths: list[Path], expected_runs: int) -> dict[str, float]: - if len(paths) != expected_runs: - raise ValueError(f"expected {expected_runs} reports, got {len(paths)}") - reports = [parse_report(path) for path in paths] - expected_names = set(reports[0]) - for path, report in zip(paths[1:], reports[1:], strict=True): - if set(report) != expected_names: - missing = sorted(expected_names - set(report)) - extra = sorted(set(report) - expected_names) - raise ValueError(f"{path}: inconsistent benchmark set; missing={missing}, extra={extra}") - return {name: statistics.median(report[name].median_ns for report in reports) for name in expected_names} - - -def fmt_ns(value: float) -> str: - if value >= 1_000_000_000: - return f"{value / 1_000_000_000:.3f} s" - if value >= 1_000_000: - return f"{value / 1_000_000:.3f} ms" - if value >= 1_000: - return f"{value / 1_000:.3f} µs" - if value < 1: - return f"{value * 1_000:.3f} ps" - return f"{value:.3f} ns" - - -def main() -> int: - parser = argparse.ArgumentParser() - parser.add_argument("baseline", type=Path, nargs="+", help="baseline Divan text reports") - parser.add_argument("--current", type=Path, nargs="+", required=True, help="current Divan text reports") - parser.add_argument("--expected-runs", type=int, default=5) - parser.add_argument("--warning-threshold", type=float, default=5.0) - args = parser.parse_args() - try: - baseline = aggregate(args.baseline, args.expected_runs) - current = aggregate(args.current, args.expected_runs) - except ValueError as error: - print(f"ERROR: {error}", file=sys.stderr) - return 2 - only_baseline = sorted(baseline.keys() - current.keys()) - only_current = sorted(current.keys() - baseline.keys()) - if only_baseline or only_current: - for name in only_baseline: - print(f"ERROR: missing in current: {name}", file=sys.stderr) - for name in only_current: - print(f"ERROR: missing in baseline: {name}", file=sys.stderr) - return 2 - improved = unchanged = regressed = warnings = 0 - print(f"{'benchmark':<50} {'baseline':>12} {'current':>12} {'delta':>10} status") - print("-" * 96) - for name in sorted(baseline): - before, after = baseline[name], current[name] - delta = (after - before) / before * 100 - improved += delta < 0 - unchanged += delta == 0 - regressed += delta > 0 - status = "WARNING" if delta > args.warning_threshold else "OK" - if status == "WARNING": - warnings += 1 - print(f"WARNING: {name} regressed by {delta:+.2f}%", file=sys.stderr) - print(f"{name:<50.50} {fmt_ns(before):>12} {fmt_ns(after):>12} {delta:>+9.2f}% {status}") - print("\nSUMMARY") - print(f"matched benchmarks : {len(baseline)}") - print(f"improved : {improved}") - print(f"unchanged : {unchanged}") - print(f"regressed : {regressed}") - print(f"warnings : {warnings}") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/divan_parser.py b/scripts/divan_parser.py deleted file mode 100644 index 2915991..0000000 --- a/scripts/divan_parser.py +++ /dev/null @@ -1,54 +0,0 @@ -"""Strict parser for Divan's text table format.""" - -from __future__ import annotations - -import re -from dataclasses import dataclass -from pathlib import Path - -TIME = re.compile(r"(?P[0-9]+(?:\.[0-9]+)?)\s*(?Pps|ns|µs|us|ms|s)\b") -ROW = re.compile(r"^[│\s]*[├╰]─\s*(?P.+)$") -COUNTS = re.compile(r"│\s*(?P[0-9]+)\s*│\s*(?P[0-9]+)\s*$") -UNITS_TO_NS = {"ps": 1e-3, "ns": 1.0, "µs": 1_000.0, "us": 1_000.0, "ms": 1_000_000.0, "s": 1_000_000_000.0} - - -@dataclass(frozen=True) -class DivanResult: - fastest_ns: float - slowest_ns: float - median_ns: float - mean_ns: float - samples: int - iters: int - - -def _time_ns(match: re.Match[str]) -> float: - return float(match.group("value")) * UNITS_TO_NS[match.group("unit")] - - -def parse_report(path: Path) -> dict[str, DivanResult]: - result: dict[str, DivanResult] = {} - pending: str | None = None - for raw in path.read_text(encoding="utf-8").splitlines(): - row = ROW.match(raw) - if row: - body = row.group("body") - first_time = TIME.search(body) - end = first_time.start() if first_time else body.find("Timer precision:") - pending = body[: end if end >= 0 else None].rstrip() - if pending is None: - continue - times = list(TIME.finditer(raw)) - if len(times) < 4: - continue - counts = COUNTS.search(raw) - if counts is None: - raise ValueError(f"{path}: benchmark row for {pending!r} has no samples/iters") - if pending in result: - raise ValueError(f"{path}: duplicate benchmark name {pending!r}") - values = [_time_ns(match) for match in times[:4]] - result[pending] = DivanResult(*values, int(counts.group("samples")), int(counts.group("iters"))) - pending = None - if not result: - raise ValueError(f"{path}: no Divan benchmark rows found") - return result diff --git a/scripts/run_bench_comparison.py b/scripts/run_bench_comparison.py deleted file mode 100755 index 3c65635..0000000 --- a/scripts/run_bench_comparison.py +++ /dev/null @@ -1,63 +0,0 @@ -#!/usr/bin/env python3 -"""Run interleaved Divan benchmarks for a fixed baseline and this worktree.""" - -from __future__ import annotations - -import argparse -import os -import subprocess -import sys -from pathlib import Path - -from bench_config import BASELINE_SHA, BENCH_RUSTFLAGS - -ORDER = ("baseline", "current", "current", "baseline", "baseline", "current", "current", "baseline", "baseline", "current") - - -def run(command: list[str], *, cwd: Path, env: dict[str, str] | None = None, output: Path | None = None) -> None: - completed = subprocess.run(command, cwd=cwd, env=env, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, check=False) - if output is not None: - output.write_text(completed.stdout, encoding="utf-8") - if completed.returncode: - sys.stdout.write(completed.stdout) - raise SystemExit(f"command failed ({completed.returncode}): {' '.join(command)}") - - -def main() -> int: - parser = argparse.ArgumentParser() - parser.add_argument("--runs", type=int, default=5, help="runs per revision; default is 5") - parser.add_argument("--warning-threshold", type=float, default=5.0) - parser.add_argument("--baseline", default=BASELINE_SHA) - args = parser.parse_args() - if args.runs != 5: - raise SystemExit("this runner currently fixes --runs at 5 to preserve the documented interleaving") - - root = Path(__file__).resolve().parents[1] - output_dir = root / "target" / "bench-comparison" - baseline_dir = output_dir / "baseline-worktree" - output_dir.mkdir(parents=True, exist_ok=True) - if not baseline_dir.exists(): - run(["git", "worktree", "add", "--detach", str(baseline_dir), args.baseline], cwd=root) - else: - run(["git", "-C", str(baseline_dir), "checkout", "--detach", args.baseline], cwd=root) - - env = os.environ.copy() - env["RUSTFLAGS"] = BENCH_RUSTFLAGS - command = ["cargo", "bench", "--", "--color", "never"] - counts = {"baseline": 0, "current": 0} - for revision in ORDER: - counts[revision] += 1 - report = output_dir / f"{revision}-{counts[revision]}.txt" - print(f"[{revision} {counts[revision]}/5] {' '.join(command)}") - run(command, cwd=baseline_dir if revision == "baseline" else root, env=env, output=report) - - compare = [sys.executable, str(root / "scripts" / "compare_bench.py")] - compare += [str(output_dir / f"baseline-{i}.txt") for i in range(1, 6)] - compare += ["--current"] + [str(output_dir / f"current-{i}.txt") for i in range(1, 6)] - compare += ["--expected-runs", str(args.runs)] - compare += ["--warning-threshold", str(args.warning_threshold)] - return subprocess.run(compare, cwd=root, check=False).returncode - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/tests/test_bench.py b/scripts/tests/test_bench.py deleted file mode 100644 index 7b091c9..0000000 --- a/scripts/tests/test_bench.py +++ /dev/null @@ -1,68 +0,0 @@ -from __future__ import annotations - -import contextlib -import io -import sys -import tempfile -import unittest -from pathlib import Path -from unittest import mock - -sys.path.insert(0, str(Path(__file__).resolve().parents[1])) - -import compare_bench -from divan_parser import parse_report - -ROW = "├─ bench 500 ps │ 1 ns │ 750 ps │ 800 ps │ 100 │ 1000\n" - - -class DivanParserTests(unittest.TestCase): - def write(self, directory: str, name: str, content: str = ROW) -> Path: - path = Path(directory) / name - path.write_text(content) - return path - - def test_picoseconds_are_converted_to_ns(self) -> None: - with tempfile.TemporaryDirectory() as directory: - result = parse_report(self.write(directory, "report.txt"))["bench"] - self.assertEqual(result.median_ns, 0.75) - self.assertEqual(result.samples, 100) - self.assertEqual(result.iters, 1000) - - def test_duplicate_benchmark_is_rejected(self) -> None: - with tempfile.TemporaryDirectory() as directory, self.assertRaisesRegex(ValueError, "duplicate"): - parse_report(self.write(directory, "report.txt", ROW + ROW)) - - def test_expected_run_count_is_enforced(self) -> None: - with tempfile.TemporaryDirectory() as directory: - paths = [self.write(directory, f"report-{index}.txt") for index in range(4)] - with self.assertRaisesRegex(ValueError, "expected 5 reports"): - compare_bench.aggregate(paths, 5) - - def test_missing_benchmark_in_one_run_is_rejected(self) -> None: - with tempfile.TemporaryDirectory() as directory: - paths = [self.write(directory, f"report-{index}.txt") for index in range(5)] - paths[-1].write_text(ROW.replace("bench", "different")) - with self.assertRaisesRegex(ValueError, "inconsistent benchmark set"): - compare_bench.aggregate(paths, 5) - - def test_missing_between_revisions_fails(self) -> None: - with tempfile.TemporaryDirectory() as directory: - baseline = [self.write(directory, f"baseline-{index}.txt") for index in range(5)] - current = [self.write(directory, f"current-{index}.txt", ROW.replace("bench", "other")) for index in range(5)] - argv = [ - "compare_bench.py", - *(str(path) for path in baseline), - "--current", - *(str(path) for path in current), - ] - with ( - mock.patch.object(sys, "argv", argv), - contextlib.redirect_stdout(io.StringIO()), - contextlib.redirect_stderr(io.StringIO()), - ): - self.assertNotEqual(compare_bench.main(), 0) - - -if __name__ == "__main__": - unittest.main() diff --git a/uv.lock b/uv.lock deleted file mode 100644 index 2d9e4f8..0000000 --- a/uv.lock +++ /dev/null @@ -1,8 +0,0 @@ -version = 1 -revision = 3 -requires-python = ">=3.11" - -[[package]] -name = "bit-string-dev-tools" -version = "0.1.0" -source = { virtual = "." }