├── .github ├── codecov.yml ├── dependabot.yml └── workflows │ ├── check.yml │ ├── nostd.yml │ ├── safety.yml │ ├── scheduled.yml │ └── test.yml ├── .gitignore ├── .travis.yml ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── README.tpl ├── bench.dat ├── benches └── search_comparison.rs ├── plots ├── gather.py ├── generate-plot.sh ├── plot.gnuplot └── plot.svg └── src └── lib.rs /.github/codecov.yml: -------------------------------------------------------------------------------- 1 | # ref: https://docs.codecov.com/docs/codecovyml-reference 2 | coverage: 3 | # Hold ourselves to a high bar 4 | range: 85..100 5 | round: down 6 | precision: 1 7 | status: 8 | # ref: https://docs.codecov.com/docs/commit-status 9 | project: 10 | default: 11 | # Avoid false negatives 12 | threshold: 1% 13 | 14 | # Test files aren't important for coverage 15 | ignore: 16 | - "tests" 17 | 18 | # Make comments less noisy 19 | comment: 20 | layout: "files" 21 | require_changes: yes 22 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | schedule: 6 | interval: daily 7 | - package-ecosystem: cargo 8 | directory: / 9 | schedule: 10 | interval: daily 11 | ignore: 12 | - dependency-name: "*" 13 | # patch and minor updates don't matter for libraries 14 | # remove this ignore rule if your package has binaries 15 | update-types: 16 | - "version-update:semver-patch" 17 | - "version-update:semver-minor" 18 | -------------------------------------------------------------------------------- /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | permissions: 2 | contents: read 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | # Spend CI time only on latest ref: https://github.com/jonhoo/rust-ci-conf/pull/5 8 | concurrency: 9 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 10 | cancel-in-progress: true 11 | name: check 12 | jobs: 13 | fmt: 14 | runs-on: ubuntu-latest 15 | name: stable / fmt 16 | steps: 17 | - uses: actions/checkout@v4 18 | with: 19 | submodules: true 20 | - name: Install stable 21 | uses: dtolnay/rust-toolchain@stable 22 | with: 23 | components: rustfmt 24 | - name: cargo fmt --check 25 | run: cargo fmt --check 26 | clippy: 27 | runs-on: ubuntu-latest 28 | name: ${{ matrix.toolchain }} / clippy 29 | permissions: 30 | contents: read 31 | checks: write 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | toolchain: [stable, beta] 36 | steps: 37 | - uses: actions/checkout@v4 38 | with: 39 | submodules: true 40 | - name: Install ${{ matrix.toolchain }} 41 | uses: dtolnay/rust-toolchain@master 42 | with: 43 | toolchain: ${{ matrix.toolchain }} 44 | components: clippy 45 | - name: cargo clippy 46 | uses: actions-rs/clippy-check@v1 47 | with: 48 | token: ${{ secrets.GITHUB_TOKEN }} 49 | doc: 50 | runs-on: ubuntu-latest 51 | name: nightly / doc 52 | steps: 53 | - uses: actions/checkout@v4 54 | with: 55 | submodules: true 56 | - name: Install nightly 57 | uses: dtolnay/rust-toolchain@nightly 58 | - name: cargo doc 59 | run: cargo doc --no-deps --all-features 60 | env: 61 | RUSTDOCFLAGS: --cfg docsrs 62 | hack: 63 | runs-on: ubuntu-latest 64 | name: ubuntu / stable / features 65 | steps: 66 | - uses: actions/checkout@v4 67 | with: 68 | submodules: true 69 | - name: Install stable 70 | uses: dtolnay/rust-toolchain@stable 71 | - name: cargo install cargo-hack 72 | uses: taiki-e/install-action@cargo-hack 73 | # intentionally no target specifier; see https://github.com/jonhoo/rust-ci-conf/pull/4 74 | - name: cargo hack 75 | run: cargo hack --feature-powerset --exclude-features nightly check 76 | msrv: 77 | runs-on: ubuntu-latest 78 | # we use a matrix here just because env can't be used in job names 79 | # https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability 80 | strategy: 81 | matrix: 82 | msrv: ["1.60.0"] # plotters 0.3.5 83 | name: ubuntu / ${{ matrix.msrv }} 84 | steps: 85 | - uses: actions/checkout@v4 86 | with: 87 | submodules: true 88 | - name: Install ${{ matrix.msrv }} 89 | uses: dtolnay/rust-toolchain@master 90 | with: 91 | toolchain: ${{ matrix.msrv }} 92 | - name: cargo +${{ matrix.msrv }} check 93 | run: cargo check 94 | -------------------------------------------------------------------------------- /.github/workflows/nostd.yml: -------------------------------------------------------------------------------- 1 | permissions: 2 | contents: read 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | # Spend CI time only on latest ref: https://github.com/jonhoo/rust-ci-conf/pull/5 8 | concurrency: 9 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 10 | cancel-in-progress: true 11 | name: no-std 12 | jobs: 13 | nostd: 14 | runs-on: ubuntu-latest 15 | name: ${{ matrix.target }} 16 | strategy: 17 | matrix: 18 | target: [thumbv7m-none-eabi, aarch64-unknown-none] 19 | steps: 20 | - uses: actions/checkout@v4 21 | with: 22 | submodules: true 23 | - name: Install stable 24 | uses: dtolnay/rust-toolchain@stable 25 | - name: rustup target add ${{ matrix.target }} 26 | run: rustup target add ${{ matrix.target }} 27 | - name: cargo check 28 | run: cargo check --target ${{ matrix.target }} --no-default-features 29 | -------------------------------------------------------------------------------- /.github/workflows/safety.yml: -------------------------------------------------------------------------------- 1 | permissions: 2 | contents: read 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | # Spend CI time only on latest ref: https://github.com/jonhoo/rust-ci-conf/pull/5 8 | concurrency: 9 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 10 | cancel-in-progress: true 11 | name: safety 12 | jobs: 13 | miri: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | with: 18 | submodules: true 19 | - run: | 20 | echo "NIGHTLY=nightly-$(curl -s https://rust-lang.github.io/rustup-components-history/x86_64-unknown-linux-gnu/miri)" >> $GITHUB_ENV 21 | - name: Install ${{ env.NIGHTLY }} 22 | uses: dtolnay/rust-toolchain@master 23 | with: 24 | toolchain: ${{ env.NIGHTLY }} 25 | components: miri 26 | - name: cargo miri test 27 | run: cargo miri test 28 | env: 29 | MIRIFLAGS: "" 30 | -------------------------------------------------------------------------------- /.github/workflows/scheduled.yml: -------------------------------------------------------------------------------- 1 | permissions: 2 | contents: read 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | schedule: 8 | - cron: '7 7 * * *' 9 | # Spend CI time only on latest ref: https://github.com/jonhoo/rust-ci-conf/pull/5 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 12 | cancel-in-progress: true 13 | name: rolling 14 | jobs: 15 | # https://twitter.com/mycoliza/status/1571295690063753218 16 | nightly: 17 | runs-on: ubuntu-latest 18 | name: ubuntu / nightly 19 | steps: 20 | - uses: actions/checkout@v4 21 | with: 22 | submodules: true 23 | - name: Install nightly 24 | uses: dtolnay/rust-toolchain@nightly 25 | - name: cargo generate-lockfile 26 | if: hashFiles('Cargo.lock') == '' 27 | run: cargo generate-lockfile 28 | - name: cargo test --locked 29 | run: cargo test --locked --all-features --all-targets 30 | # https://twitter.com/alcuadrado/status/1571291687837732873 31 | update: 32 | runs-on: ubuntu-latest 33 | name: ubuntu / beta / updated 34 | # There's no point running this if no Cargo.lock was checked in in the 35 | # first place, since we'd just redo what happened in the regular test job. 36 | # Unfortunately, hashFiles only works in if on steps, so we reepeat it. 37 | # if: hashFiles('Cargo.lock') != '' 38 | steps: 39 | - uses: actions/checkout@v4 40 | with: 41 | submodules: true 42 | - name: Install beta 43 | if: hashFiles('Cargo.lock') != '' 44 | uses: dtolnay/rust-toolchain@beta 45 | - name: cargo update 46 | if: hashFiles('Cargo.lock') != '' 47 | run: cargo update 48 | - name: cargo test 49 | if: hashFiles('Cargo.lock') != '' 50 | run: cargo test --locked --all-targets 51 | env: 52 | RUSTFLAGS: -D deprecated 53 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | permissions: 2 | contents: read 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | # Spend CI time only on latest ref: https://github.com/jonhoo/rust-ci-conf/pull/5 8 | concurrency: 9 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 10 | cancel-in-progress: true 11 | name: test 12 | jobs: 13 | required: 14 | runs-on: ubuntu-latest 15 | name: ubuntu / ${{ matrix.toolchain }} 16 | strategy: 17 | matrix: 18 | toolchain: [stable, beta] 19 | steps: 20 | - uses: actions/checkout@v4 21 | with: 22 | submodules: true 23 | - name: Install ${{ matrix.toolchain }} 24 | uses: dtolnay/rust-toolchain@master 25 | with: 26 | toolchain: ${{ matrix.toolchain }} 27 | - name: cargo generate-lockfile 28 | if: hashFiles('Cargo.lock') == '' 29 | run: cargo generate-lockfile 30 | # https://twitter.com/jonhoo/status/1571290371124260865 31 | - name: cargo test --locked 32 | run: cargo test --locked --all-targets 33 | # https://github.com/rust-lang/cargo/issues/6669 34 | - name: cargo test --doc 35 | run: cargo test --locked --doc 36 | minimal: 37 | runs-on: ubuntu-latest 38 | name: ubuntu / stable / minimal-versions 39 | steps: 40 | - uses: actions/checkout@v4 41 | with: 42 | submodules: true 43 | - name: Install stable 44 | uses: dtolnay/rust-toolchain@stable 45 | - name: Install nightly for -Zminimal-versions 46 | uses: dtolnay/rust-toolchain@nightly 47 | - name: rustup default stable 48 | run: rustup default stable 49 | - name: cargo update -Zminimal-versions 50 | run: cargo +nightly update -Zminimal-versions 51 | - name: cargo test 52 | run: cargo test --locked --all-targets 53 | os-check: 54 | runs-on: ${{ matrix.os }} 55 | name: ${{ matrix.os }} / stable 56 | strategy: 57 | fail-fast: false 58 | matrix: 59 | os: [macos-latest, windows-latest] 60 | steps: 61 | # if your project needs OpenSSL, uncommment this to fix Windows builds. 62 | # it's commented out by default as tthe install command takes 5-10m. 63 | # - run: echo "VCPKG_ROOT=$env:VCPKG_INSTALLATION_ROOT" | Out-File -FilePath $env:GITHUB_ENV -Append 64 | # if: runner.os == 'Windows' 65 | # - run: vcpkg install openssl:x64-windows-static-md 66 | # if: runner.os == 'Windows' 67 | - uses: actions/checkout@v4 68 | with: 69 | submodules: true 70 | - name: Install stable 71 | uses: dtolnay/rust-toolchain@stable 72 | - name: cargo generate-lockfile 73 | if: hashFiles('Cargo.lock') == '' 74 | run: cargo generate-lockfile 75 | - name: cargo test 76 | run: cargo test --locked --all-targets 77 | coverage: 78 | runs-on: ubuntu-latest 79 | name: ubuntu / stable / coverage 80 | steps: 81 | - uses: actions/checkout@v4 82 | with: 83 | submodules: true 84 | - name: Install stable 85 | uses: dtolnay/rust-toolchain@stable 86 | with: 87 | components: llvm-tools-preview 88 | - name: cargo install cargo-llvm-cov 89 | uses: taiki-e/install-action@cargo-llvm-cov 90 | - name: cargo generate-lockfile 91 | if: hashFiles('Cargo.lock') == '' 92 | run: cargo generate-lockfile 93 | - name: cargo llvm-cov 94 | run: cargo llvm-cov --locked --lcov --output-path lcov.info 95 | - name: Upload to codecov.io 96 | uses: codecov/codecov-action@v3 97 | with: 98 | fail_ci_if_error: true 99 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | **/*.rs.bk 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: rust 3 | rust: 4 | - stable 5 | - beta 6 | - nightly 7 | cache: cargo 8 | matrix: 9 | allow_failures: 10 | - rust: nightly 11 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.0.4" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "anes" 16 | version = "0.1.6" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" 19 | 20 | [[package]] 21 | name = "anstyle" 22 | version = "1.0.2" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "15c4c2c83f81532e5845a733998b6971faca23490340a418e9b72a3ec9de12ea" 25 | 26 | [[package]] 27 | name = "autocfg" 28 | version = "1.1.0" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 31 | 32 | [[package]] 33 | name = "bitflags" 34 | version = "2.4.0" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" 37 | 38 | [[package]] 39 | name = "bumpalo" 40 | version = "3.13.0" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" 43 | 44 | [[package]] 45 | name = "cast" 46 | version = "0.3.0" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" 49 | 50 | [[package]] 51 | name = "cc" 52 | version = "1.0.83" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" 55 | dependencies = [ 56 | "libc", 57 | ] 58 | 59 | [[package]] 60 | name = "cfg-if" 61 | version = "1.0.0" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 64 | 65 | [[package]] 66 | name = "ciborium" 67 | version = "0.2.1" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926" 70 | dependencies = [ 71 | "ciborium-io", 72 | "ciborium-ll", 73 | "serde", 74 | ] 75 | 76 | [[package]] 77 | name = "ciborium-io" 78 | version = "0.2.1" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656" 81 | 82 | [[package]] 83 | name = "ciborium-ll" 84 | version = "0.2.1" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" 87 | dependencies = [ 88 | "ciborium-io", 89 | "half", 90 | ] 91 | 92 | [[package]] 93 | name = "clap" 94 | version = "4.4.0" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "1d5f1946157a96594eb2d2c10eb7ad9a2b27518cb3000209dec700c35df9197d" 97 | dependencies = [ 98 | "clap_builder", 99 | ] 100 | 101 | [[package]] 102 | name = "clap_builder" 103 | version = "4.4.0" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "78116e32a042dd73c2901f0dc30790d20ff3447f3e3472fad359e8c3d282bcd6" 106 | dependencies = [ 107 | "anstyle", 108 | "clap_lex", 109 | ] 110 | 111 | [[package]] 112 | name = "clap_lex" 113 | version = "0.5.1" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" 116 | 117 | [[package]] 118 | name = "criterion" 119 | version = "0.5.1" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" 122 | dependencies = [ 123 | "anes", 124 | "cast", 125 | "ciborium", 126 | "clap", 127 | "criterion-plot", 128 | "is-terminal", 129 | "itertools", 130 | "num-traits", 131 | "once_cell", 132 | "oorandom", 133 | "plotters", 134 | "rayon", 135 | "regex", 136 | "serde", 137 | "serde_derive", 138 | "serde_json", 139 | "tinytemplate", 140 | "walkdir", 141 | ] 142 | 143 | [[package]] 144 | name = "criterion-plot" 145 | version = "0.5.0" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" 148 | dependencies = [ 149 | "cast", 150 | "itertools", 151 | ] 152 | 153 | [[package]] 154 | name = "crossbeam-channel" 155 | version = "0.5.8" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" 158 | dependencies = [ 159 | "cfg-if", 160 | "crossbeam-utils", 161 | ] 162 | 163 | [[package]] 164 | name = "crossbeam-deque" 165 | version = "0.8.3" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" 168 | dependencies = [ 169 | "cfg-if", 170 | "crossbeam-epoch", 171 | "crossbeam-utils", 172 | ] 173 | 174 | [[package]] 175 | name = "crossbeam-epoch" 176 | version = "0.9.15" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" 179 | dependencies = [ 180 | "autocfg", 181 | "cfg-if", 182 | "crossbeam-utils", 183 | "memoffset", 184 | "scopeguard", 185 | ] 186 | 187 | [[package]] 188 | name = "crossbeam-utils" 189 | version = "0.8.16" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" 192 | dependencies = [ 193 | "cfg-if", 194 | ] 195 | 196 | [[package]] 197 | name = "either" 198 | version = "1.9.0" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" 201 | 202 | [[package]] 203 | name = "errno" 204 | version = "0.3.2" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" 207 | dependencies = [ 208 | "errno-dragonfly", 209 | "libc", 210 | "windows-sys", 211 | ] 212 | 213 | [[package]] 214 | name = "errno-dragonfly" 215 | version = "0.1.2" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" 218 | dependencies = [ 219 | "cc", 220 | "libc", 221 | ] 222 | 223 | [[package]] 224 | name = "half" 225 | version = "1.8.2" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" 228 | 229 | [[package]] 230 | name = "hermit-abi" 231 | version = "0.3.2" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" 234 | 235 | [[package]] 236 | name = "is-terminal" 237 | version = "0.4.9" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" 240 | dependencies = [ 241 | "hermit-abi", 242 | "rustix", 243 | "windows-sys", 244 | ] 245 | 246 | [[package]] 247 | name = "itertools" 248 | version = "0.10.5" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" 251 | dependencies = [ 252 | "either", 253 | ] 254 | 255 | [[package]] 256 | name = "itoa" 257 | version = "1.0.9" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" 260 | 261 | [[package]] 262 | name = "js-sys" 263 | version = "0.3.64" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" 266 | dependencies = [ 267 | "wasm-bindgen", 268 | ] 269 | 270 | [[package]] 271 | name = "libc" 272 | version = "0.2.147" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" 275 | 276 | [[package]] 277 | name = "linux-raw-sys" 278 | version = "0.4.5" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" 281 | 282 | [[package]] 283 | name = "log" 284 | version = "0.4.20" 285 | source = "registry+https://github.com/rust-lang/crates.io-index" 286 | checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" 287 | 288 | [[package]] 289 | name = "memchr" 290 | version = "2.5.0" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 293 | 294 | [[package]] 295 | name = "memoffset" 296 | version = "0.9.0" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" 299 | dependencies = [ 300 | "autocfg", 301 | ] 302 | 303 | [[package]] 304 | name = "num-traits" 305 | version = "0.2.16" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" 308 | dependencies = [ 309 | "autocfg", 310 | ] 311 | 312 | [[package]] 313 | name = "num_cpus" 314 | version = "1.16.0" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" 317 | dependencies = [ 318 | "hermit-abi", 319 | "libc", 320 | ] 321 | 322 | [[package]] 323 | name = "once_cell" 324 | version = "1.18.0" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" 327 | 328 | [[package]] 329 | name = "oorandom" 330 | version = "11.1.3" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" 333 | 334 | [[package]] 335 | name = "ordsearch" 336 | version = "0.2.7" 337 | dependencies = [ 338 | "criterion", 339 | "num-traits", 340 | "regex", 341 | "serde", 342 | ] 343 | 344 | [[package]] 345 | name = "plotters" 346 | version = "0.3.5" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" 349 | dependencies = [ 350 | "num-traits", 351 | "plotters-backend", 352 | "plotters-svg", 353 | "wasm-bindgen", 354 | "web-sys", 355 | ] 356 | 357 | [[package]] 358 | name = "plotters-backend" 359 | version = "0.3.5" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" 362 | 363 | [[package]] 364 | name = "plotters-svg" 365 | version = "0.3.5" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" 368 | dependencies = [ 369 | "plotters-backend", 370 | ] 371 | 372 | [[package]] 373 | name = "proc-macro2" 374 | version = "1.0.66" 375 | source = "registry+https://github.com/rust-lang/crates.io-index" 376 | checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" 377 | dependencies = [ 378 | "unicode-ident", 379 | ] 380 | 381 | [[package]] 382 | name = "quote" 383 | version = "1.0.33" 384 | source = "registry+https://github.com/rust-lang/crates.io-index" 385 | checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" 386 | dependencies = [ 387 | "proc-macro2", 388 | ] 389 | 390 | [[package]] 391 | name = "rayon" 392 | version = "1.7.0" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" 395 | dependencies = [ 396 | "either", 397 | "rayon-core", 398 | ] 399 | 400 | [[package]] 401 | name = "rayon-core" 402 | version = "1.11.0" 403 | source = "registry+https://github.com/rust-lang/crates.io-index" 404 | checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" 405 | dependencies = [ 406 | "crossbeam-channel", 407 | "crossbeam-deque", 408 | "crossbeam-utils", 409 | "num_cpus", 410 | ] 411 | 412 | [[package]] 413 | name = "regex" 414 | version = "1.9.3" 415 | source = "registry+https://github.com/rust-lang/crates.io-index" 416 | checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" 417 | dependencies = [ 418 | "aho-corasick", 419 | "memchr", 420 | "regex-automata", 421 | "regex-syntax", 422 | ] 423 | 424 | [[package]] 425 | name = "regex-automata" 426 | version = "0.3.6" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" 429 | dependencies = [ 430 | "aho-corasick", 431 | "memchr", 432 | "regex-syntax", 433 | ] 434 | 435 | [[package]] 436 | name = "regex-syntax" 437 | version = "0.7.4" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" 440 | 441 | [[package]] 442 | name = "rustix" 443 | version = "0.38.9" 444 | source = "registry+https://github.com/rust-lang/crates.io-index" 445 | checksum = "9bfe0f2582b4931a45d1fa608f8a8722e8b3c7ac54dd6d5f3b3212791fedef49" 446 | dependencies = [ 447 | "bitflags", 448 | "errno", 449 | "libc", 450 | "linux-raw-sys", 451 | "windows-sys", 452 | ] 453 | 454 | [[package]] 455 | name = "ryu" 456 | version = "1.0.15" 457 | source = "registry+https://github.com/rust-lang/crates.io-index" 458 | checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" 459 | 460 | [[package]] 461 | name = "same-file" 462 | version = "1.0.6" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 465 | dependencies = [ 466 | "winapi-util", 467 | ] 468 | 469 | [[package]] 470 | name = "scopeguard" 471 | version = "1.2.0" 472 | source = "registry+https://github.com/rust-lang/crates.io-index" 473 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 474 | 475 | [[package]] 476 | name = "serde" 477 | version = "1.0.185" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "be9b6f69f1dfd54c3b568ffa45c310d6973a5e5148fd40cf515acaf38cf5bc31" 480 | dependencies = [ 481 | "serde_derive", 482 | ] 483 | 484 | [[package]] 485 | name = "serde_derive" 486 | version = "1.0.185" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | checksum = "dc59dfdcbad1437773485e0367fea4b090a2e0a16d9ffc46af47764536a298ec" 489 | dependencies = [ 490 | "proc-macro2", 491 | "quote", 492 | "syn", 493 | ] 494 | 495 | [[package]] 496 | name = "serde_json" 497 | version = "1.0.105" 498 | source = "registry+https://github.com/rust-lang/crates.io-index" 499 | checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" 500 | dependencies = [ 501 | "itoa", 502 | "ryu", 503 | "serde", 504 | ] 505 | 506 | [[package]] 507 | name = "syn" 508 | version = "2.0.29" 509 | source = "registry+https://github.com/rust-lang/crates.io-index" 510 | checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" 511 | dependencies = [ 512 | "proc-macro2", 513 | "quote", 514 | "unicode-ident", 515 | ] 516 | 517 | [[package]] 518 | name = "tinytemplate" 519 | version = "1.2.1" 520 | source = "registry+https://github.com/rust-lang/crates.io-index" 521 | checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" 522 | dependencies = [ 523 | "serde", 524 | "serde_json", 525 | ] 526 | 527 | [[package]] 528 | name = "unicode-ident" 529 | version = "1.0.11" 530 | source = "registry+https://github.com/rust-lang/crates.io-index" 531 | checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" 532 | 533 | [[package]] 534 | name = "walkdir" 535 | version = "2.3.3" 536 | source = "registry+https://github.com/rust-lang/crates.io-index" 537 | checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" 538 | dependencies = [ 539 | "same-file", 540 | "winapi-util", 541 | ] 542 | 543 | [[package]] 544 | name = "wasm-bindgen" 545 | version = "0.2.87" 546 | source = "registry+https://github.com/rust-lang/crates.io-index" 547 | checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" 548 | dependencies = [ 549 | "cfg-if", 550 | "wasm-bindgen-macro", 551 | ] 552 | 553 | [[package]] 554 | name = "wasm-bindgen-backend" 555 | version = "0.2.87" 556 | source = "registry+https://github.com/rust-lang/crates.io-index" 557 | checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" 558 | dependencies = [ 559 | "bumpalo", 560 | "log", 561 | "once_cell", 562 | "proc-macro2", 563 | "quote", 564 | "syn", 565 | "wasm-bindgen-shared", 566 | ] 567 | 568 | [[package]] 569 | name = "wasm-bindgen-macro" 570 | version = "0.2.87" 571 | source = "registry+https://github.com/rust-lang/crates.io-index" 572 | checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" 573 | dependencies = [ 574 | "quote", 575 | "wasm-bindgen-macro-support", 576 | ] 577 | 578 | [[package]] 579 | name = "wasm-bindgen-macro-support" 580 | version = "0.2.87" 581 | source = "registry+https://github.com/rust-lang/crates.io-index" 582 | checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" 583 | dependencies = [ 584 | "proc-macro2", 585 | "quote", 586 | "syn", 587 | "wasm-bindgen-backend", 588 | "wasm-bindgen-shared", 589 | ] 590 | 591 | [[package]] 592 | name = "wasm-bindgen-shared" 593 | version = "0.2.87" 594 | source = "registry+https://github.com/rust-lang/crates.io-index" 595 | checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" 596 | 597 | [[package]] 598 | name = "web-sys" 599 | version = "0.3.64" 600 | source = "registry+https://github.com/rust-lang/crates.io-index" 601 | checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" 602 | dependencies = [ 603 | "js-sys", 604 | "wasm-bindgen", 605 | ] 606 | 607 | [[package]] 608 | name = "winapi" 609 | version = "0.3.9" 610 | source = "registry+https://github.com/rust-lang/crates.io-index" 611 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 612 | dependencies = [ 613 | "winapi-i686-pc-windows-gnu", 614 | "winapi-x86_64-pc-windows-gnu", 615 | ] 616 | 617 | [[package]] 618 | name = "winapi-i686-pc-windows-gnu" 619 | version = "0.4.0" 620 | source = "registry+https://github.com/rust-lang/crates.io-index" 621 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 622 | 623 | [[package]] 624 | name = "winapi-util" 625 | version = "0.1.5" 626 | source = "registry+https://github.com/rust-lang/crates.io-index" 627 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 628 | dependencies = [ 629 | "winapi", 630 | ] 631 | 632 | [[package]] 633 | name = "winapi-x86_64-pc-windows-gnu" 634 | version = "0.4.0" 635 | source = "registry+https://github.com/rust-lang/crates.io-index" 636 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 637 | 638 | [[package]] 639 | name = "windows-sys" 640 | version = "0.48.0" 641 | source = "registry+https://github.com/rust-lang/crates.io-index" 642 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 643 | dependencies = [ 644 | "windows-targets", 645 | ] 646 | 647 | [[package]] 648 | name = "windows-targets" 649 | version = "0.48.5" 650 | source = "registry+https://github.com/rust-lang/crates.io-index" 651 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 652 | dependencies = [ 653 | "windows_aarch64_gnullvm", 654 | "windows_aarch64_msvc", 655 | "windows_i686_gnu", 656 | "windows_i686_msvc", 657 | "windows_x86_64_gnu", 658 | "windows_x86_64_gnullvm", 659 | "windows_x86_64_msvc", 660 | ] 661 | 662 | [[package]] 663 | name = "windows_aarch64_gnullvm" 664 | version = "0.48.5" 665 | source = "registry+https://github.com/rust-lang/crates.io-index" 666 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 667 | 668 | [[package]] 669 | name = "windows_aarch64_msvc" 670 | version = "0.48.5" 671 | source = "registry+https://github.com/rust-lang/crates.io-index" 672 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 673 | 674 | [[package]] 675 | name = "windows_i686_gnu" 676 | version = "0.48.5" 677 | source = "registry+https://github.com/rust-lang/crates.io-index" 678 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 679 | 680 | [[package]] 681 | name = "windows_i686_msvc" 682 | version = "0.48.5" 683 | source = "registry+https://github.com/rust-lang/crates.io-index" 684 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 685 | 686 | [[package]] 687 | name = "windows_x86_64_gnu" 688 | version = "0.48.5" 689 | source = "registry+https://github.com/rust-lang/crates.io-index" 690 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 691 | 692 | [[package]] 693 | name = "windows_x86_64_gnullvm" 694 | version = "0.48.5" 695 | source = "registry+https://github.com/rust-lang/crates.io-index" 696 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 697 | 698 | [[package]] 699 | name = "windows_x86_64_msvc" 700 | version = "0.48.5" 701 | source = "registry+https://github.com/rust-lang/crates.io-index" 702 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 703 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ordsearch" 3 | version = "0.2.7" 4 | 5 | description = "A data structure for efficient lower-bound lookups" 6 | readme = "README.md" 7 | 8 | authors = ["Jon Gjengset "] 9 | 10 | documentation = "https://docs.rs/ordsearch" 11 | homepage = "https://github.com/jonhoo/ordsearch" 12 | repository = "https://github.com/jonhoo/ordsearch.git" 13 | 14 | keywords = ["data-structures", "search", "performance", "Eytzinger"] 15 | categories = ["data-structures", "memory-management"] 16 | 17 | license = "MIT/Apache-2.0" 18 | 19 | [features] 20 | default = [] 21 | nightly = [] 22 | 23 | [dev-dependencies] 24 | criterion = { version = "0.5", features = ["html_reports"] } 25 | num-traits = "0.2.15" 26 | 27 | [target.'cfg(any())'.dependencies] 28 | serde = { version = "1.0.100", optional = true } 29 | regex = { version = "1.6.0", optional = true } 30 | 31 | [[bench]] 32 | name = "search_comparison" 33 | harness = false 34 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Jon Gjengset 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ordsearch 2 | 3 | [![Crates.io](https://img.shields.io/crates/v/ordsearch.svg)](https://crates.io/crates/ordsearch) 4 | [![Documentation](https://docs.rs/ordsearch/badge.svg)](https://docs.rs/ordsearch/) 5 | [![Build Status](https://travis-ci.org/jonhoo/ordsearch.svg?branch=master)](https://travis-ci.org/jonhoo/ordsearch) 6 | 7 | This crate provides a data structure for approximate lookups in ordered collections. 8 | 9 | More concretely, given a set `A` of `n` values, and a query value `x`, this library provides an 10 | efficient mechanism for finding the smallest value in `A` that is greater than or equal to `x`. 11 | In particular, this library caters to the important case where there are many such queries to 12 | the same array, `A`. 13 | 14 | This library is constructed from the best solution identified in [Array Layouts for 15 | Comparison-Based Searching](https://arxiv.org/abs/1509.05053) by Paul-Virak Khuong and Pat 16 | Morin. For more information, see the paper, [their 17 | website](http://cglab.ca/~morin/misc/arraylayout-v2/), and the [C++ implementation 18 | repository](https://github.com/patmorin/arraylayout). 19 | 20 | ## Current implementation 21 | 22 | At the time of writing, this implementation uses a branch-free search over an 23 | Eytzinger-arranged array with masked prefetching based on the [C++ 24 | implementation](https://github.com/patmorin/arraylayout/blob/3f20174a2a0ab52c6f37f2ea87d087307f19b5ee/src/eytzinger_array.h#L253) 25 | written by the authors of the aforementioned paper. This is the recommended algorithm from the 26 | paper, and what the authors suggested in 27 | https://github.com/patmorin/arraylayout/issues/3#issuecomment-338472755. 28 | 29 | Note that prefetching is *only* enabled with the (non-default) `nightly` feature due to 30 | https://github.com/aweinstock314/prefetch/issues/1. Suggestions for workarounds welcome. 31 | 32 | ## Performance 33 | 34 | The included benchmarks can be run with 35 | 36 | ```console,ignore 37 | $ cargo +nightly bench --features nightly 38 | ``` 39 | 40 | This will benchmark both construction and search with different number of values, and 41 | differently sized values -- look for the line that aligns closest with your data. The general 42 | trend is that `ordsearch` is faster when `n` is smaller and `T` is larger as long as you 43 | compile with 44 | [`target-cpu=native`](https://github.com/jonhoo/ordsearch/issues/2#issuecomment-390441137) and 45 | [`lto=thin`](https://github.com/jonhoo/ordsearch/issues/2#issuecomment-390446671). The 46 | performance gain seems to be best on Intel processors, and is smaller since the (relatively) 47 | recent improvement to [SliceExt::binary_search 48 | performance](https://github.com/rust-lang/rust/pull/45333). 49 | 50 | Below are summarized results from an Intel(R) Core(TM) i7-1068NG7 CPU @ 2.30GHz CPU run with: 51 | 52 | ```console 53 | $ rustc +nightly --version 54 | rustc 1.75.0-nightly (e0d7ed1f4 2023-10-01) 55 | $ env CARGO_INCREMENTAL=0 RUSTFLAGS='-C target-cpu=native' cargo +nightly bench --features nightly 56 | ``` 57 | 58 | ![](./plots/plot.svg) 59 | 60 | ## Future work 61 | 62 | - [ ] Implement aligned operation: https://github.com/patmorin/arraylayout/blob/3f20174a2a0ab52c6f37f2ea87d087307f19b5ee/src/eytzinger_array.h#L204 63 | - [ ] Implement deep prefetching for large `T`: https://github.com/patmorin/arraylayout/blob/3f20174a2a0ab52c6f37f2ea87d087307f19b5ee/src/eytzinger_array.h#L128 64 | -------------------------------------------------------------------------------- /README.tpl: -------------------------------------------------------------------------------- 1 | # {{crate}} 2 | 3 | [![Crates.io](https://img.shields.io/crates/v/ordsearch.svg)](https://crates.io/crates/ordsearch) 4 | [![Documentation](https://docs.rs/ordsearch/badge.svg)](https://docs.rs/ordsearch/) 5 | [![Build Status](https://travis-ci.org/jonhoo/ordsearch.svg?branch=master)](https://travis-ci.org/jonhoo/ordsearch) 6 | 7 | {{readme}} 8 | -------------------------------------------------------------------------------- /bench.dat: -------------------------------------------------------------------------------- 1 | 2 | running 94 tests 3 | test tests::complete_approximate ... ignored 4 | test tests::complete_exact ... ignored 5 | test tests::unbalanced_approximate ... ignored 6 | test tests::unbalanced_exact ... ignored 7 | test b::btreeset::construction::u32::l1 ... bench: 214,301 ns/iter (+/- 2,231) 8 | test b::btreeset::construction::u32::l1_dup ... bench: 115,925 ns/iter (+/- 3,713) 9 | test b::btreeset::construction::u32::l2 ... bench: 2,709,092 ns/iter (+/- 15,499) 10 | test b::btreeset::construction::u32::l2_dup ... bench: 1,637,096 ns/iter (+/- 14,920) 11 | test b::btreeset::construction::u8::l1 ... bench: 155,869 ns/iter (+/- 2,090) 12 | test b::btreeset::construction::u8::l1_dup ... bench: 108,579 ns/iter (+/- 697) 13 | test b::btreeset::construction::u8::l2 ... bench: 1,544,782 ns/iter (+/- 18,142) 14 | test b::btreeset::construction::u8::l2_dup ... bench: 1,079,372 ns/iter (+/- 12,949) 15 | test b::btreeset::construction::usize::l1 ... bench: 217,538 ns/iter (+/- 1,242) 16 | test b::btreeset::construction::usize::l1_dup ... bench: 118,103 ns/iter (+/- 1,129) 17 | test b::btreeset::construction::usize::l2 ... bench: 2,751,103 ns/iter (+/- 27,108) 18 | test b::btreeset::construction::usize::l2_dup ... bench: 1,678,106 ns/iter (+/- 16,066) 19 | test b::btreeset::search::u32::l1 ... bench: 294 ns/iter (+/- 2) 20 | test b::btreeset::search::u32::l1_dup ... bench: 169 ns/iter (+/- 1) 21 | test b::btreeset::search::u32::l2 ... bench: 364 ns/iter (+/- 3) 22 | test b::btreeset::search::u32::l2_dup ... bench: 239 ns/iter (+/- 2) 23 | test b::btreeset::search::u32::l3 ... bench: 723 ns/iter (+/- 20) 24 | test b::btreeset::search::u32::l3_dup ... bench: 454 ns/iter (+/- 6) 25 | test b::btreeset::search::u8::l1 ... bench: 222 ns/iter (+/- 2) 26 | test b::btreeset::search::u8::l1_dup ... bench: 155 ns/iter (+/- 2) 27 | test b::btreeset::search::u8::l2 ... bench: 222 ns/iter (+/- 3) 28 | test b::btreeset::search::u8::l2_dup ... bench: 155 ns/iter (+/- 2) 29 | test b::btreeset::search::u8::l3 ... bench: 222 ns/iter (+/- 2) 30 | test b::btreeset::search::u8::l3_dup ... bench: 155 ns/iter (+/- 1) 31 | test b::btreeset::search::usize::l1 ... bench: 298 ns/iter (+/- 3) 32 | test b::btreeset::search::usize::l1_dup ... bench: 168 ns/iter (+/- 1) 33 | test b::btreeset::search::usize::l2 ... bench: 368 ns/iter (+/- 4) 34 | test b::btreeset::search::usize::l2_dup ... bench: 242 ns/iter (+/- 3) 35 | test b::btreeset::search::usize::l3 ... bench: 780 ns/iter (+/- 7) 36 | test b::btreeset::search::usize::l3_dup ... bench: 495 ns/iter (+/- 5) 37 | test b::sorted_vec::construction::u32::l1 ... bench: 34,528 ns/iter (+/- 285) 38 | test b::sorted_vec::construction::u32::l1_dup ... bench: 26,319 ns/iter (+/- 194) 39 | test b::sorted_vec::construction::u32::l2 ... bench: 405,267 ns/iter (+/- 3,697) 40 | test b::sorted_vec::construction::u32::l2_dup ... bench: 322,402 ns/iter (+/- 3,280) 41 | test b::sorted_vec::construction::u8::l1 ... bench: 33,579 ns/iter (+/- 441) 42 | test b::sorted_vec::construction::u8::l1_dup ... bench: 20,878 ns/iter (+/- 433) 43 | test b::sorted_vec::construction::u8::l2 ... bench: 277,256 ns/iter (+/- 4,084) 44 | test b::sorted_vec::construction::u8::l2_dup ... bench: 192,552 ns/iter (+/- 3,768) 45 | test b::sorted_vec::construction::usize::l1 ... bench: 34,491 ns/iter (+/- 280) 46 | test b::sorted_vec::construction::usize::l1_dup ... bench: 26,294 ns/iter (+/- 202) 47 | test b::sorted_vec::construction::usize::l2 ... bench: 404,635 ns/iter (+/- 4,279) 48 | test b::sorted_vec::construction::usize::l2_dup ... bench: 322,429 ns/iter (+/- 2,588) 49 | test b::sorted_vec::search::u32::l1 ... bench: 51 ns/iter (+/- 0) 50 | test b::sorted_vec::search::u32::l1_dup ... bench: 42 ns/iter (+/- 0) 51 | test b::sorted_vec::search::u32::l2 ... bench: 67 ns/iter (+/- 0) 52 | test b::sorted_vec::search::u32::l2_dup ... bench: 66 ns/iter (+/- 0) 53 | test b::sorted_vec::search::u32::l3 ... bench: 118 ns/iter (+/- 0) 54 | test b::sorted_vec::search::u32::l3_dup ... bench: 119 ns/iter (+/- 0) 55 | test b::sorted_vec::search::u8::l1 ... bench: 47 ns/iter (+/- 0) 56 | test b::sorted_vec::search::u8::l1_dup ... bench: 36 ns/iter (+/- 0) 57 | test b::sorted_vec::search::u8::l2 ... bench: 56 ns/iter (+/- 0) 58 | test b::sorted_vec::search::u8::l2_dup ... bench: 45 ns/iter (+/- 0) 59 | test b::sorted_vec::search::u8::l3 ... bench: 68 ns/iter (+/- 0) 60 | test b::sorted_vec::search::u8::l3_dup ... bench: 52 ns/iter (+/- 0) 61 | test b::sorted_vec::search::usize::l1 ... bench: 51 ns/iter (+/- 0) 62 | test b::sorted_vec::search::usize::l1_dup ... bench: 42 ns/iter (+/- 0) 63 | test b::sorted_vec::search::usize::l2 ... bench: 68 ns/iter (+/- 0) 64 | test b::sorted_vec::search::usize::l2_dup ... bench: 67 ns/iter (+/- 0) 65 | test b::sorted_vec::search::usize::l3 ... bench: 139 ns/iter (+/- 1) 66 | test b::sorted_vec::search::usize::l3_dup ... bench: 139 ns/iter (+/- 2) 67 | test b::this::construction::u32::l1 ... bench: 41,147 ns/iter (+/- 459) 68 | test b::this::construction::u32::l1_dup ... bench: 32,942 ns/iter (+/- 372) 69 | test b::this::construction::u32::l2 ... bench: 477,717 ns/iter (+/- 7,902) 70 | test b::this::construction::u32::l2_dup ... bench: 391,750 ns/iter (+/- 5,826) 71 | test b::this::construction::u8::l1 ... bench: 40,102 ns/iter (+/- 603) 72 | test b::this::construction::u8::l1_dup ... bench: 27,409 ns/iter (+/- 137) 73 | test b::this::construction::u8::l2 ... bench: 332,638 ns/iter (+/- 3,651) 74 | test b::this::construction::u8::l2_dup ... bench: 252,488 ns/iter (+/- 3,683) 75 | test b::this::construction::usize::l1 ... bench: 40,867 ns/iter (+/- 300) 76 | test b::this::construction::usize::l1_dup ... bench: 32,654 ns/iter (+/- 338) 77 | test b::this::construction::usize::l2 ... bench: 478,249 ns/iter (+/- 28,625) 78 | test b::this::construction::usize::l2_dup ... bench: 407,511 ns/iter (+/- 27,254) 79 | test b::this::search::u32::l1 ... bench: 103 ns/iter (+/- 0) 80 | test b::this::search::u32::l1_dup ... bench: 90 ns/iter (+/- 0) 81 | test b::this::search::u32::l2 ... bench: 150 ns/iter (+/- 6) 82 | test b::this::search::u32::l2_dup ... bench: 146 ns/iter (+/- 1) 83 | test b::this::search::u32::l3 ... bench: 352 ns/iter (+/- 7) 84 | test b::this::search::u32::l3_dup ... bench: 352 ns/iter (+/- 3) 85 | test b::this::search::u8::l1 ... bench: 97 ns/iter (+/- 0) 86 | test b::this::search::u8::l1_dup ... bench: 85 ns/iter (+/- 0) 87 | test b::this::search::u8::l2 ... bench: 149 ns/iter (+/- 0) 88 | test b::this::search::u8::l2_dup ... bench: 141 ns/iter (+/- 1) 89 | test b::this::search::u8::l3 ... bench: 224 ns/iter (+/- 2) 90 | test b::this::search::u8::l3_dup ... bench: 197 ns/iter (+/- 3) 91 | test b::this::search::usize::l1 ... bench: 105 ns/iter (+/- 1) 92 | test b::this::search::usize::l1_dup ... bench: 91 ns/iter (+/- 0) 93 | test b::this::search::usize::l2 ... bench: 153 ns/iter (+/- 1) 94 | test b::this::search::usize::l2_dup ... bench: 148 ns/iter (+/- 2) 95 | test b::this::search::usize::l3 ... bench: 463 ns/iter (+/- 4) 96 | test b::this::search::usize::l3_dup ... bench: 467 ns/iter (+/- 6) 97 | 98 | test result: ok. 0 passed; 0 failed; 4 ignored; 90 measured; 0 filtered out 99 | 100 | -------------------------------------------------------------------------------- /benches/search_comparison.rs: -------------------------------------------------------------------------------- 1 | extern crate criterion; 2 | extern crate num_traits; 3 | extern crate ordsearch; 4 | 5 | use criterion::{ 6 | criterion_group, criterion_main, measurement::WallTime, AxisScale, BatchSize, BenchmarkGroup, 7 | BenchmarkId, Criterion, PlotConfiguration, 8 | }; 9 | use ordsearch::OrderedCollection; 10 | use std::{ 11 | any::type_name, 12 | collections::BTreeSet, 13 | convert::TryFrom, 14 | iter, 15 | sync::atomic::{AtomicUsize, Ordering}, 16 | time::Duration, 17 | }; 18 | 19 | const WARM_UP_TIME: Duration = Duration::from_millis(500); 20 | const MEASUREMENT_TIME: Duration = Duration::from_millis(1000); 21 | const DUPLICATION_FACTOR: usize = 16; 22 | 23 | criterion_main!(benches); 24 | 25 | criterion_group!( 26 | benches, 27 | benchmarks_for::, 28 | benchmarks_for::, 29 | benchmarks_for::, 30 | benchmarks_for::, 31 | benchmarks_for::, 32 | ); 33 | 34 | fn benchmarks_for(c: &mut Criterion) 35 | where 36 | T: TryFrom + Ord + Copy, 37 | { 38 | let plot_config = PlotConfiguration::default().summary_scale(AxisScale::Logarithmic); 39 | 40 | let sizes = [ 41 | 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4069, 8192, 16384, 32768, 65536, 1048576, 42 | 10485760, 43 | ]; 44 | 45 | { 46 | let groupname = format!("Search {}", type_name::()); 47 | let mut group = c.benchmark_group(groupname); 48 | group 49 | .warm_up_time(WARM_UP_TIME) 50 | .measurement_time(MEASUREMENT_TIME) 51 | .plot_config(plot_config.clone()); 52 | 53 | for i in sizes { 54 | search_bench_case::( 55 | "sorted_vec", 56 | make_sorted_vec, 57 | search_sorted_vec, 58 | &mut group, 59 | i, 60 | false, 61 | ); 62 | search_bench_case::( 63 | "btreeset", 64 | make_btreeset, 65 | search_btreeset, 66 | &mut group, 67 | i, 68 | false, 69 | ); 70 | search_bench_case::( 71 | "ordsearch", 72 | make_this, 73 | search_this, 74 | &mut group, 75 | i, 76 | false, 77 | ); 78 | } 79 | group.finish(); 80 | } 81 | 82 | { 83 | let groupname = format!("Search (with duplicates) {}", type_name::()); 84 | let mut group = c.benchmark_group(groupname); 85 | group 86 | .warm_up_time(WARM_UP_TIME) 87 | .measurement_time(MEASUREMENT_TIME) 88 | .plot_config(plot_config.clone()); 89 | 90 | for i in sizes { 91 | search_bench_case::( 92 | "sorted_vec", 93 | make_sorted_vec, 94 | search_sorted_vec, 95 | &mut group, 96 | i, 97 | true, 98 | ); 99 | search_bench_case::( 100 | "btreeset", 101 | make_btreeset, 102 | search_btreeset, 103 | &mut group, 104 | i, 105 | true, 106 | ); 107 | search_bench_case::( 108 | "ordsearch", 109 | make_this, 110 | search_this, 111 | &mut group, 112 | i, 113 | true, 114 | ); 115 | } 116 | group.finish(); 117 | } 118 | 119 | { 120 | let groupname = format!("Construction {}", type_name::()); 121 | let mut group = c.benchmark_group(groupname); 122 | group 123 | .warm_up_time(WARM_UP_TIME) 124 | .measurement_time(MEASUREMENT_TIME) 125 | .plot_config(plot_config.clone()); 126 | 127 | for i in sizes { 128 | construction_bench_case::( 129 | "sorted_vec", 130 | make_sorted_vec, 131 | &mut group, 132 | i, 133 | false, 134 | ); 135 | construction_bench_case::("btreeset", make_btreeset, &mut group, i, false); 136 | construction_bench_case::("ordsearch", make_this, &mut group, i, false); 137 | } 138 | group.finish(); 139 | } 140 | 141 | { 142 | let groupname = format!("Construction (with duplicates) {}", type_name::()); 143 | let mut group = c.benchmark_group(groupname); 144 | group 145 | .warm_up_time(WARM_UP_TIME) 146 | .measurement_time(MEASUREMENT_TIME) 147 | .plot_config(plot_config); 148 | 149 | for i in sizes { 150 | construction_bench_case::( 151 | "sorted_vec", 152 | make_sorted_vec, 153 | &mut group, 154 | i, 155 | true, 156 | ); 157 | construction_bench_case::("btreeset", make_btreeset, &mut group, i, true); 158 | construction_bench_case::("ordsearch", make_this, &mut group, i, true); 159 | } 160 | group.finish(); 161 | } 162 | } 163 | 164 | fn search_bench_case( 165 | name: &str, 166 | setup_fun: impl Fn(Vec) -> Coll, 167 | search_fun: impl Fn(&Coll, T) -> Option<&T>, 168 | group: &mut BenchmarkGroup, 169 | size: usize, 170 | duplicates: bool, 171 | ) where 172 | T: TryFrom + Ord + Copy, 173 | { 174 | group.bench_with_input(BenchmarkId::new(name, size), &size, |b, &size| { 175 | // increasing sequence of even numbers, bounded by MAX 176 | let iter = (0usize..) 177 | // Generate only even numbers to provide a ~50% hit ratio in the benchmark 178 | .map(|i| (i * 2) % MAX) 179 | .map(|i| T::try_from(i).ok().unwrap()); 180 | 181 | let v: Vec = if duplicates { 182 | iter.flat_map(|i| iter::repeat(i).take(DUPLICATION_FACTOR)) 183 | .take(size) 184 | .collect() 185 | } else { 186 | iter.take(size).collect() 187 | }; 188 | 189 | // to generate ~50% hit ratio generated number should be in the range 0..(size * 2) because 190 | // test payload contains only even numbers: [0, 2, ..., 2 * size] 191 | let mut r = pseudorandom_iter::(MAX.min(size * 2)); 192 | let c = setup_fun(v); 193 | b.iter(|| search_fun(&c, r.next().unwrap())) 194 | }); 195 | } 196 | 197 | fn construction_bench_case( 198 | name: &str, 199 | setup_fun: impl Fn(Vec) -> Coll, 200 | group: &mut BenchmarkGroup, 201 | size: usize, 202 | duplicates: bool, 203 | ) where 204 | T: TryFrom + Ord + Copy, 205 | { 206 | group.bench_with_input(BenchmarkId::new(name, size), &size, |b, &size| { 207 | let v: Vec = if duplicates { 208 | pseudorandom_iter(MAX) 209 | .flat_map(|i| iter::repeat(i).take(DUPLICATION_FACTOR)) 210 | .take(size) 211 | .collect() 212 | } else { 213 | pseudorandom_iter(MAX).take(size).collect() 214 | }; 215 | 216 | b.iter_batched(|| v.clone(), &setup_fun, BatchSize::SmallInput); 217 | }); 218 | } 219 | 220 | fn make_this(mut v: Vec) -> OrderedCollection { 221 | v.sort_unstable(); 222 | OrderedCollection::from_sorted_iter(v) 223 | } 224 | 225 | fn search_this(c: &OrderedCollection, x: T) -> Option<&T> { 226 | c.find_gte(x) 227 | } 228 | 229 | fn make_btreeset(v: Vec) -> BTreeSet { 230 | use std::iter::FromIterator; 231 | BTreeSet::from_iter(v) 232 | } 233 | 234 | fn search_btreeset(c: &BTreeSet, x: T) -> Option<&T> { 235 | use std::collections::Bound; 236 | c.range((Bound::Included(x), Bound::Unbounded)).next() 237 | } 238 | 239 | fn make_sorted_vec(mut v: Vec) -> Vec { 240 | v.sort_unstable(); 241 | v 242 | } 243 | 244 | #[allow(clippy::ptr_arg)] 245 | fn search_sorted_vec(c: &Vec, x: T) -> Option<&T> { 246 | c.binary_search(&x).ok().map(|i| &c[i]) 247 | } 248 | 249 | fn pseudorandom_iter(max: usize) -> impl Iterator 250 | where 251 | T: TryFrom, 252 | { 253 | static SEED: AtomicUsize = AtomicUsize::new(0); 254 | let mut seed = SEED.fetch_add(1, Ordering::SeqCst); 255 | 256 | iter::from_fn(move || { 257 | // LCG constants from https://en.wikipedia.org/wiki/Numerical_Recipes. 258 | seed = seed.wrapping_mul(1664525).wrapping_add(1013904223); 259 | 260 | // High 32 bits have much higher period 261 | let value = (seed >> 32) % max; 262 | Some(T::try_from(value).ok().unwrap()) 263 | }) 264 | } 265 | -------------------------------------------------------------------------------- /plots/gather.py: -------------------------------------------------------------------------------- 1 | import json 2 | import csv 3 | 4 | TYPES = ['u8', 'u16', 'u32', 'u64'] 5 | SIZES = [8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4069, 8192, 16384, 32768, 65536] 6 | COLLECTIONS = ['ordsearch', 'sorted_vec', 'btreeset'] 7 | 8 | 9 | def main(): 10 | for collection in COLLECTIONS: 11 | data = read_collection_stat(collection) 12 | with open(f"target/{collection}.csv", "w") as f: 13 | writer = csv.DictWriter(f, data[0].keys()) 14 | writer.writeheader() 15 | writer.writerows(data) 16 | 17 | 18 | def read_collection_stat(collection: str) -> list[dict[str, int]]: 19 | result = [] 20 | for size in SIZES: 21 | row = {'size': size} 22 | for type_name in TYPES: 23 | file_name = f"target/criterion/Search {type_name}/{collection}/{size}/new/estimates.json" 24 | with open(file_name) as f: 25 | j = json.loads(f.read()) 26 | row[type_name] = j['mean']['point_estimate'] 27 | result.append(row) 28 | return result 29 | 30 | 31 | if __name__ == "__main__": 32 | main() 33 | -------------------------------------------------------------------------------- /plots/generate-plot.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This scripts generate plot.svg in the current directory. 4 | # 5 | # To use it properly gnuplot should be installed on the system 6 | # 1. run benchmarks 7 | # $ cargo +nightly bench --bench=search_comparison --features=nightly "^Search" 8 | # 2. run this script 9 | # $ ./plots/generate-plot.sh 10 | 11 | RELATIVE_PATH=$(dirname "$0") 12 | 13 | # Running all commands in the current directory 14 | cd "$RELATIVE_PATH" 15 | gnuplot ./plot.gnuplot -------------------------------------------------------------------------------- /plots/plot.gnuplot: -------------------------------------------------------------------------------- 1 | set term svg enhanced size 800,600 lw 1.2 background rgb 'white' 2 | set output 'plot.svg' 3 | set grid 4 | set key bottom right 5 | set multiplot layout 2,2 6 | set style line 1 lc 'dark-green' lw 1.5 pt 4 ps 0.7 7 | set style line 2 lc 'dark-red' lw 1.5 pt 6 ps 0.7 8 | set style line 3 lc 'dark-yellow' lw 1.5 pt 8 ps 0.7 9 | set size 0.5,0.5 10 | 11 | set datafile separator ',' 12 | 13 | set ylabel "Execution time (ns.)" 14 | 15 | unset xlabel 16 | set logscale x 2 17 | set xrange [8/2:65536*2] 18 | input_path = "../target" 19 | 20 | set origin 0,0.5 21 | set title "{/:Bold u8}" 22 | plot \ 23 | input_path.'/ordsearch.csv' skip 1 using 1:2 with lp title 'ordsearch' ls 1, \ 24 | input_path.'/sorted_vec.csv' skip 1 using 1:2 with lp title 'binary search' ls 2, \ 25 | input_path.'/btreeset.csv' skip 1 using 1:2 with lp title 'BTree' ls 3 26 | 27 | unset ylabel 28 | unset key 29 | 30 | set origin 0.5,0.5 31 | set title "{/:Bold u16}" 32 | plot \ 33 | input_path.'/ordsearch.csv' skip 1 using 1:3 with lp title 'ordsearch' ls 1, \ 34 | input_path.'/sorted_vec.csv' skip 1 using 1:3 with lp title 'binary search' ls 2, \ 35 | input_path.'/btreeset.csv' skip 1 using 1:3 with lp title 'BTree' ls 3 36 | 37 | set xlabel "Array size" 38 | set ylabel "Execution time (ns.)" 39 | 40 | set origin 0,0 41 | set title "{/:Bold u32}" 42 | plot \ 43 | input_path.'/ordsearch.csv' skip 1 using 1:4 with lp title 'ordsearch' ls 1, \ 44 | input_path.'/sorted_vec.csv' skip 1 using 1:4 with lp title 'binary search' ls 2, \ 45 | input_path.'/btreeset.csv' skip 1 using 1:4 with lp title 'BTree' ls 3 46 | 47 | unset ylabel 48 | 49 | set origin 0.5,0 50 | set title "{/:Bold u64}" 51 | plot \ 52 | input_path.'/ordsearch.csv' skip 1 using 1:5 with lp title 'ordsearch' ls 1, \ 53 | input_path.'/sorted_vec.csv' skip 1 using 1:5 with lp title 'binary search' ls 2, \ 54 | input_path.'/btreeset.csv' skip 1 using 1:5 with lp title 'BTree' ls 3 -------------------------------------------------------------------------------- /plots/plot.svg: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | Gnuplot 10 | Produced by GNUPLOT 5.4 patchlevel 10 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 5 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 10 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 15 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 20 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 25 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 30 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 35 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 40 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 45 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 50 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 4 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 16 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 64 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 256 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 1024 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 4096 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 16384 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 65536 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | Execution time (ns.) 285 | 286 | 287 | 288 | 289 | ordsearch 290 | 291 | 292 | 293 | 294 | ordsearch 295 | 296 | 297 | 298 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | binary search 318 | 319 | 320 | binary search 321 | 322 | 323 | 324 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | BTree 344 | 345 | 346 | BTree 347 | 348 | 349 | 350 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | u8 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 0 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 10 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 20 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 30 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 40 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 50 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 60 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 70 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 80 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 90 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 100 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 4 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 16 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 64 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 256 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 1024 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 4096 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 16384 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 65536 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | ordsearch 638 | 639 | 640 | 641 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | binary search 659 | 660 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | BTree 678 | 679 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | u16 707 | 708 | 709 | 710 | 711 | 712 | 713 | 714 | 715 | 716 | 717 | 718 | 719 | 720 | 721 | 0 722 | 723 | 724 | 725 | 726 | 727 | 728 | 729 | 730 | 731 | 732 | 733 | 734 | 10 735 | 736 | 737 | 738 | 739 | 740 | 741 | 742 | 743 | 744 | 745 | 746 | 747 | 20 748 | 749 | 750 | 751 | 752 | 753 | 754 | 755 | 756 | 757 | 758 | 759 | 760 | 30 761 | 762 | 763 | 764 | 765 | 766 | 767 | 768 | 769 | 770 | 771 | 772 | 773 | 40 774 | 775 | 776 | 777 | 778 | 779 | 780 | 781 | 782 | 783 | 784 | 785 | 786 | 50 787 | 788 | 789 | 790 | 791 | 792 | 793 | 794 | 795 | 796 | 797 | 798 | 799 | 60 800 | 801 | 802 | 803 | 804 | 805 | 806 | 807 | 808 | 809 | 810 | 811 | 812 | 70 813 | 814 | 815 | 816 | 817 | 818 | 819 | 820 | 821 | 822 | 823 | 824 | 825 | 80 826 | 827 | 828 | 829 | 830 | 831 | 832 | 833 | 834 | 835 | 836 | 837 | 838 | 90 839 | 840 | 841 | 842 | 843 | 844 | 845 | 846 | 847 | 848 | 849 | 850 | 851 | 100 852 | 853 | 854 | 855 | 856 | 857 | 858 | 859 | 860 | 861 | 862 | 863 | 864 | 110 865 | 866 | 867 | 868 | 869 | 870 | 871 | 872 | 873 | 874 | 875 | 876 | 877 | 4 878 | 879 | 880 | 881 | 882 | 883 | 884 | 885 | 886 | 887 | 888 | 889 | 890 | 16 891 | 892 | 893 | 894 | 895 | 896 | 897 | 898 | 899 | 900 | 901 | 902 | 903 | 64 904 | 905 | 906 | 907 | 908 | 909 | 910 | 911 | 912 | 913 | 914 | 915 | 916 | 256 917 | 918 | 919 | 920 | 921 | 922 | 923 | 924 | 925 | 926 | 927 | 928 | 929 | 1024 930 | 931 | 932 | 933 | 934 | 935 | 936 | 937 | 938 | 939 | 940 | 941 | 942 | 4096 943 | 944 | 945 | 946 | 947 | 948 | 949 | 950 | 951 | 952 | 953 | 954 | 955 | 16384 956 | 957 | 958 | 959 | 960 | 961 | 962 | 963 | 964 | 965 | 966 | 967 | 968 | 65536 969 | 970 | 971 | 972 | 973 | 974 | 975 | 976 | 977 | Execution time (ns.) 978 | 979 | 980 | 981 | 982 | Array size 983 | 984 | 985 | 986 | 987 | ordsearch 988 | 989 | 990 | 991 | 993 | 994 | 995 | 996 | 997 | 998 | 999 | 1000 | 1001 | 1002 | 1003 | 1004 | 1005 | 1006 | 1007 | 1008 | binary search 1009 | 1010 | 1012 | 1013 | 1014 | 1015 | 1016 | 1017 | 1018 | 1019 | 1020 | 1021 | 1022 | 1023 | 1024 | 1025 | 1026 | 1027 | BTree 1028 | 1029 | 1031 | 1032 | 1033 | 1034 | 1035 | 1036 | 1037 | 1038 | 1039 | 1040 | 1041 | 1042 | 1043 | 1044 | 1045 | 1046 | 1047 | 1048 | 1049 | 1050 | 1051 | 1052 | 1053 | 1054 | 1055 | 1056 | u32 1057 | 1058 | 1059 | 1060 | 1061 | 1062 | 1063 | 1064 | 1065 | 1066 | 1067 | 1068 | 1069 | 1070 | 1071 | 0 1072 | 1073 | 1074 | 1075 | 1076 | 1077 | 1078 | 1079 | 1080 | 1081 | 1082 | 1083 | 1084 | 20 1085 | 1086 | 1087 | 1088 | 1089 | 1090 | 1091 | 1092 | 1093 | 1094 | 1095 | 1096 | 1097 | 40 1098 | 1099 | 1100 | 1101 | 1102 | 1103 | 1104 | 1105 | 1106 | 1107 | 1108 | 1109 | 1110 | 60 1111 | 1112 | 1113 | 1114 | 1115 | 1116 | 1117 | 1118 | 1119 | 1120 | 1121 | 1122 | 1123 | 80 1124 | 1125 | 1126 | 1127 | 1128 | 1129 | 1130 | 1131 | 1132 | 1133 | 1134 | 1135 | 1136 | 100 1137 | 1138 | 1139 | 1140 | 1141 | 1142 | 1143 | 1144 | 1145 | 1146 | 1147 | 1148 | 1149 | 120 1150 | 1151 | 1152 | 1153 | 1154 | 1155 | 1156 | 1157 | 1158 | 1159 | 1160 | 1161 | 1162 | 4 1163 | 1164 | 1165 | 1166 | 1167 | 1168 | 1169 | 1170 | 1171 | 1172 | 1173 | 1174 | 1175 | 16 1176 | 1177 | 1178 | 1179 | 1180 | 1181 | 1182 | 1183 | 1184 | 1185 | 1186 | 1187 | 1188 | 64 1189 | 1190 | 1191 | 1192 | 1193 | 1194 | 1195 | 1196 | 1197 | 1198 | 1199 | 1200 | 1201 | 256 1202 | 1203 | 1204 | 1205 | 1206 | 1207 | 1208 | 1209 | 1210 | 1211 | 1212 | 1213 | 1214 | 1024 1215 | 1216 | 1217 | 1218 | 1219 | 1220 | 1221 | 1222 | 1223 | 1224 | 1225 | 1226 | 1227 | 4096 1228 | 1229 | 1230 | 1231 | 1232 | 1233 | 1234 | 1235 | 1236 | 1237 | 1238 | 1239 | 1240 | 16384 1241 | 1242 | 1243 | 1244 | 1245 | 1246 | 1247 | 1248 | 1249 | 1250 | 1251 | 1252 | 1253 | 65536 1254 | 1255 | 1256 | 1257 | 1258 | 1259 | 1260 | 1261 | 1262 | Array size 1263 | 1264 | 1265 | 1266 | 1267 | ordsearch 1268 | 1269 | 1270 | 1271 | 1273 | 1274 | 1275 | 1276 | 1277 | 1278 | 1279 | 1280 | 1281 | 1282 | 1283 | 1284 | 1285 | 1286 | 1287 | 1288 | binary search 1289 | 1290 | 1292 | 1293 | 1294 | 1295 | 1296 | 1297 | 1298 | 1299 | 1300 | 1301 | 1302 | 1303 | 1304 | 1305 | 1306 | 1307 | BTree 1308 | 1309 | 1311 | 1312 | 1313 | 1314 | 1315 | 1316 | 1317 | 1318 | 1319 | 1320 | 1321 | 1322 | 1323 | 1324 | 1325 | 1326 | 1327 | 1328 | 1329 | 1330 | 1331 | 1332 | 1333 | 1334 | 1335 | 1336 | u64 1337 | 1338 | 1339 | 1340 | 1341 | 1342 | 1343 | 1344 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! > NOTE: This crate is generally *slower* than using `Vec::binary_search` over a pre-sorted 2 | //! > vector, contrary to the claims in the referenced paper, and is mainly presented for 3 | //! > curiosity's sake at this point. 4 | //! 5 | //! This crate provides a data structure for approximate lookups in ordered collections. 6 | //! 7 | //! More concretely, given a set `A` of `n` values, and a query value `x`, this library provides an 8 | //! efficient mechanism for finding the smallest value in `A` that is greater than or equal to `x`. 9 | //! In particular, this library caters to the important case where there are many such queries to 10 | //! the same array, `A`. 11 | //! 12 | //! This library is constructed from the best solution identified in [Array Layouts for 13 | //! Comparison-Based Searching](https://arxiv.org/abs/1509.05053) by Paul-Virak Khuong and Pat 14 | //! Morin. For more information, see the paper, [their 15 | //! website](http://cglab.ca/~morin/misc/arraylayout-v2/), and the [C++ implementation 16 | //! repository](https://github.com/patmorin/arraylayout). 17 | //! 18 | //! # Current implementation 19 | //! 20 | //! At the time of writing, this implementation uses a branch-free search over an 21 | //! Eytzinger-arranged array with masked prefetching based on the [C++ 22 | //! implementation](https://github.com/patmorin/arraylayout/blob/3f20174a2a0ab52c6f37f2ea87d087307f19b5ee/src/eytzinger_array.h#L253) 23 | //! written by the authors of the aforementioned paper. This is the recommended algorithm from the 24 | //! paper, and what the authors suggested in 25 | //! https://github.com/patmorin/arraylayout/issues/3#issuecomment-338472755. 26 | //! 27 | //! Note that prefetching is *only* enabled with the (non-default) `nightly` feature due to 28 | //! https://github.com/aweinstock314/prefetch/issues/1. Suggestions for workarounds welcome. 29 | //! 30 | //! # Performance 31 | //! 32 | //! The included benchmarks can be run with 33 | //! 34 | //! ```console,ignore 35 | //! $ cargo +nightly bench --features nightly 36 | //! ``` 37 | //! 38 | //! This will benchmark both construction and search with different number of values, and 39 | //! differently sized values -- look for the line that aligns closest with your data. The general 40 | //! trend is that `ordsearch` is faster when `n` is smaller and `T` is larger as long as you 41 | //! compile with 42 | //! [`target-cpu=native`](https://github.com/jonhoo/ordsearch/issues/2#issuecomment-390441137) and 43 | //! [`lto=thin`](https://github.com/jonhoo/ordsearch/issues/2#issuecomment-390446671). The 44 | //! performance gain seems to be best on Intel processors, and is smaller since the (relatively) 45 | //! recent improvement to [SliceExt::binary_search 46 | //! performance](https://github.com/rust-lang/rust/pull/45333). 47 | //! 48 | //! Below are [summarized](https://github.com/BurntSushi/cargo-benchcmp) results from an AMD 49 | //! ThreadRipper 2600X CPU run with: 50 | //! 51 | //! ```console 52 | //! $ rustc +nightly --version 53 | //! rustc 1.28.0-nightly (e3bf634e0 2018-06-28) 54 | //! $ env CARGO_INCREMENTAL=0 RUSTFLAGS='-C target-cpu=native -C lto=thin' cargo +nightly bench --features nightly 55 | //! ``` 56 | //! 57 | //! Compared to binary search over a sorted vector: 58 | //! 59 | //! ```diff,ignore 60 | //! name sorted_vec ns/iter this ns/iter diff ns/iter diff % speedup 61 | //! -u32::l1 49 54 5 10.20% x 0.91 62 | //! +u32::l1_dup 40 35 -5 -12.50% x 1.14 63 | //! -u32::l2 63 72 9 14.29% x 0.88 64 | //! +u32::l2_dup 64 62 -2 -3.12% x 1.03 65 | //! -u32::l3 120 273 153 127.50% x 0.44 66 | //! -u32::l3_dup 117 219 102 87.18% x 0.53 67 | //! +u8::l1 42 37 -5 -11.90% x 1.14 68 | //! +u8::l1_dup 29 28 -1 -3.45% x 1.04 69 | //! +u8::l2 43 49 6 13.95% x 0.88 70 | //! -u8::l2_dup 33 35 2 6.06% x 0.94 71 | //! -u8::l3 45 66 21 46.67% x 0.68 72 | //! -u8::l3_dup 35 51 16 45.71% x 0.69 73 | //! -usize::l1 49 54 5 10.20% x 0.91 74 | //! +usize::l1_dup 38 37 -1 -2.63% x 1.03 75 | //! -usize::l2 65 76 11 16.92% x 0.86 76 | //! +usize::l2_dup 65 64 -1 -1.54% x 1.02 77 | //! -usize::l3 141 303 162 114.89% x 0.47 78 | //! -usize::l3_dup 140 274 134 95.71% x 0.51 79 | //! ``` 80 | //! 81 | //! Compared to a `BTreeSet`: 82 | //! 83 | //! ```diff,ignore 84 | //! name btreeset ns/iter this ns/iter diff ns/iter diff % speedup 85 | //! +u32::l1 68 54 -14 -20.59% x 1.26 86 | //! +u32::l1_dup 45 35 -10 -22.22% x 1.29 87 | //! +u32::l2 88 72 -16 -18.18% x 1.22 88 | //! -u32::l2_dup 61 62 1 1.64% x 0.98 89 | //! +u32::l3 346 273 -73 -21.10% x 1.27 90 | //! -u32::l3_dup 136 219 83 61.03% x 0.62 91 | //! +u8::l1 45 37 -8 -17.78% x 1.22 92 | //! +u8::l1_dup 31 28 -3 -9.68% x 1.11 93 | //! -u8::l2 44 49 5 11.36% x 0.90 94 | //! -u8::l2_dup 31 35 4 12.90% x 0.89 95 | //! -u8::l3 43 66 23 53.49% x 0.65 96 | //! -u8::l3_dup 30 51 21 70.00% x 0.59 97 | //! +usize::l1 67 54 -13 -19.40% x 1.24 98 | //! +usize::l1_dup 44 37 -7 -15.91% x 1.19 99 | //! +usize::l2 89 76 -13 -14.61% x 1.17 100 | //! -usize::l2_dup 60 64 4 6.67% x 0.94 101 | //! +usize::l3 393 303 -90 -22.90% x 1.30 102 | //! -usize::l3_dup 163 274 111 68.10% x 0.59 103 | //! ``` 104 | //! 105 | //! # Future work 106 | //! 107 | //! - [ ] Implement aligned operation: https://github.com/patmorin/arraylayout/blob/3f20174a2a0ab52c6f37f2ea87d087307f19b5ee/src/eytzinger_array.h#L204 108 | //! - [ ] Implement deep prefetching for large `T`: https://github.com/patmorin/arraylayout/blob/3f20174a2a0ab52c6f37f2ea87d087307f19b5ee/src/eytzinger_array.h#L128 109 | //! 110 | #![deny(missing_docs)] 111 | #![no_std] 112 | #![cfg_attr(feature = "nightly", feature(concat_idents))] 113 | #![cfg_attr(feature = "nightly", feature(core_intrinsics))] 114 | 115 | extern crate alloc; 116 | #[cfg(test)] 117 | extern crate std; 118 | 119 | use alloc::vec::Vec; 120 | use core::{ 121 | borrow::Borrow, 122 | mem::{self, MaybeUninit}, 123 | }; 124 | 125 | /// A collection of ordered items that can efficiently satisfy queries for nearby elements. 126 | /// 127 | /// The most interesting method here is `find_gte`. 128 | /// 129 | /// # Examples 130 | /// 131 | /// ``` 132 | /// # use ordsearch::OrderedCollection; 133 | /// let x = OrderedCollection::from(vec![1, 2, 4, 8, 16, 32, 64]); 134 | /// assert_eq!(x.find_gte(0), Some(&1)); 135 | /// assert_eq!(x.find_gte(1), Some(&1)); 136 | /// assert_eq!(x.find_gte(3), Some(&4)); 137 | /// assert_eq!(x.find_gte(6), Some(&8)); 138 | /// assert_eq!(x.find_gte(8), Some(&8)); 139 | /// assert_eq!(x.find_gte(64), Some(&64)); 140 | /// assert_eq!(x.find_gte(65), None); 141 | /// ``` 142 | pub struct OrderedCollection { 143 | /// Contains all the elements in modified Eytzinger layout 144 | /// 145 | /// This vector is 1-indexed, so the root is at index 1. `[0]` element is intentionally left uninitialized 146 | /// to not introduce any additional trait bounds on `T` (like `Copy` or `Default`). 147 | /// 148 | /// # Safety 149 | /// Not under any circumstances `[0]` should be accessed. This is especially important in `Drop` 150 | /// implementation and [`eytzinger_walk()`]/[`find_gte()`] functions. 151 | items: Vec>, 152 | } 153 | 154 | impl From> for OrderedCollection { 155 | /// Construct a new `OrderedCollection` from a vector of elements. 156 | /// 157 | /// # Examples 158 | /// 159 | /// ``` 160 | /// # use ordsearch::OrderedCollection; 161 | /// let a = OrderedCollection::from(vec![42, 89, 7, 12]); 162 | /// assert_eq!(a.find_gte(50), Some(&89)); 163 | /// ``` 164 | fn from(mut v: Vec) -> OrderedCollection { 165 | v.sort_unstable(); 166 | Self::from_sorted_iter(v) 167 | } 168 | } 169 | 170 | /// Insert items from the sorted iterator `I` into `Vec` in complete binary tree order. 171 | /// 172 | /// Requires `I` to be a sorted iterator. 173 | /// Requires `Vec` capacity to be set to the number of elements in `iter`. 174 | /// The length of `Vec` will not be changed by this function. 175 | fn eytzinger_walk(context: &mut (Vec>, I), i: usize) 176 | where 177 | I: Iterator, 178 | { 179 | let (v, _) = context; 180 | if i >= v.capacity() { 181 | return; 182 | } 183 | 184 | // visit left child 185 | eytzinger_walk(context, 2 * i); 186 | 187 | // reborrow context 188 | let (v, iter) = context; 189 | 190 | // put data at the root 191 | // we know the pointer arithmetics below is safe because we set the Vec's capacity to 192 | // the length of the iterator. 193 | let value = iter.next().unwrap(); 194 | unsafe { 195 | v.as_mut_ptr().add(i).write(MaybeUninit::new(value)); 196 | } 197 | 198 | // visit right child 199 | eytzinger_walk(context, 2 * i + 1); 200 | } 201 | 202 | impl OrderedCollection { 203 | /// this computation is a little finicky, so let's walk through it. 204 | /// 205 | /// we want to prefetch a couple of levels down in the tree from where we are. 206 | /// however, we can only fetch one cacheline at a time (assume a line holds 64b). 207 | /// we therefore need to find at what depth a single prefetch fetches all the descendants. 208 | /// it turns out that, at depth k under some node with index i, the leftmost child is at: 209 | /// 210 | /// 2^k * i 211 | /// 212 | /// this follows from the fact that the leftmost immediate child of node i is at 2i by 213 | /// recursively expanding i. Note that the original paper uses 0-based indexing (`2i + 1`/`2i + 2`) while we 214 | /// use 1-based indexing (`2i`/`2i + 1`). This is because of performance reasons (see: 215 | /// [Optimized Eytzinger layout & memory prefetch](https://github.com/jonhoo/ordsearch/pull/27)). 216 | /// 217 | /// If you're curious, the rightmost child is at: 218 | /// 219 | /// 2^k * i + 2^k - 1 220 | /// 221 | /// at depth k, there are 2^k children. we can fit 64/sizeof(T) children in a cacheline, so 222 | /// we want to use the depth k that has 64/sizeof(T) children. so, we want: 223 | /// 224 | /// 2^k = 64/sizeof(T) 225 | /// 226 | /// but, we don't actually *need* k. we only ever use 2^k. so, we can just use 64/sizeof(T) 227 | /// directly! nice. we call this the multiplier (because it's what we'll multiply i by). 228 | const MULTIPLIER: usize = 64 / mem::size_of::(); 229 | 230 | /// now we know that multiplier == 2^k, so we're done. right? 231 | /// 232 | /// right? 233 | /// 234 | /// well, only sort of. the prefetch instruction fetches the cache-line that *holds* the 235 | /// given memory address. let's denote cache lines with []. what if we have: 236 | /// 237 | /// [..., 2^k + 2^k-1] [2^k + 2^k, ...] 238 | /// 239 | /// essentially, we got unlucky with the alignment so that the leftmost child is not sharing 240 | /// a cacheline with any of the other items at that level! that's not great. so, instead, we 241 | /// prefetch the address that is half-way through the set of children. that way, we ensure 242 | /// that we prefetch at least half of the items. 243 | const OFFSET: usize = Self::MULTIPLIER / 2; 244 | 245 | /// Construct a new `OrderedCollection` from an iterator over sorted elements. 246 | /// 247 | /// Note that if the iterator is *not* sorted, no error will be given, but lookups will give 248 | /// incorrect results. The given iterator must also implement `ExactSizeIterator` so that we 249 | /// know the size of the lookup array. 250 | /// 251 | /// # Examples 252 | /// 253 | /// Using an already-sorted iterator: 254 | /// 255 | /// ``` 256 | /// # use std::collections::BTreeSet; 257 | /// # use ordsearch::OrderedCollection; 258 | /// 259 | /// let mut s = BTreeSet::new(); 260 | /// s.insert(42); 261 | /// s.insert(89); 262 | /// s.insert(7); 263 | /// s.insert(12); 264 | /// let a = OrderedCollection::from_sorted_iter(s); 265 | /// assert_eq!(a.find_gte(50), Some(&89)); 266 | /// ``` 267 | /// 268 | /// Sorting a collection and then iterating (in this case, you'd likely use `new` instead): 269 | /// 270 | /// ``` 271 | /// # use ordsearch::OrderedCollection; 272 | /// let mut v = vec![42, 89, 7, 12]; 273 | /// v.sort_unstable(); 274 | /// let a = OrderedCollection::from_sorted_iter(v); 275 | /// assert_eq!(a.find_gte(50), Some(&89)); 276 | /// ``` 277 | /// 278 | /// The `OrderedCollection` can also be over references to somewhere else: 279 | /// 280 | /// ``` 281 | /// # use std::collections::BTreeSet; 282 | /// # use ordsearch::OrderedCollection; 283 | /// 284 | /// let mut s = BTreeSet::new(); 285 | /// s.insert(42); 286 | /// s.insert(89); 287 | /// s.insert(7); 288 | /// s.insert(12); 289 | /// let a = OrderedCollection::from_sorted_iter(s.iter().copied()); 290 | /// assert_eq!(a.find_gte(50), Some(&89)); 291 | /// ``` 292 | /// 293 | pub fn from_sorted_iter(iter: I) -> Self 294 | where 295 | I: IntoIterator, 296 | I::IntoIter: ExactSizeIterator, 297 | { 298 | let iter = iter.into_iter(); 299 | let n = iter.len(); 300 | // vec with capacity n + 1 because we don't use index 0 and starts with 1 301 | let mut context = (Vec::with_capacity(n + 1), iter); 302 | eytzinger_walk(&mut context, 1); 303 | let (mut items, _) = context; 304 | 305 | // SAFETY: all `n` elements from the iterator was inserted in items. 306 | // [0] is uninitialized, but that's okay since the value type is `MaybeUninit`. 307 | unsafe { items.set_len(n + 1) }; 308 | 309 | OrderedCollection { items } 310 | } 311 | 312 | /// Construct a new `OrderedCollection` from a slice of elements. 313 | /// 314 | /// Note that the underlying slice will be reordered! 315 | /// 316 | /// # Examples 317 | /// 318 | /// ``` 319 | /// # use ordsearch::OrderedCollection; 320 | /// let mut vals = [42, 89, 7, 12]; 321 | /// let a = OrderedCollection::from_slice(&mut vals); 322 | /// assert_eq!(a.find_gte(50), Some(&&89)); 323 | /// ``` 324 | pub fn from_slice(v: &mut [T]) -> OrderedCollection<&T> { 325 | v.sort_unstable(); 326 | OrderedCollection::from_sorted_iter(v.iter()) 327 | } 328 | 329 | /// Find the smallest value `v` such that `v >= x`. 330 | /// 331 | /// Returns `None` if there is no such `v`. 332 | /// 333 | /// # Examples 334 | /// 335 | /// ``` 336 | /// # use ordsearch::OrderedCollection; 337 | /// let x = OrderedCollection::from(vec![1, 2, 4, 8, 16, 32, 64]); 338 | /// assert_eq!(x.find_gte(0), Some(&1)); 339 | /// assert_eq!(x.find_gte(1), Some(&1)); 340 | /// assert_eq!(x.find_gte(3), Some(&4)); 341 | /// assert_eq!(x.find_gte(6), Some(&8)); 342 | /// assert_eq!(x.find_gte(8), Some(&8)); 343 | /// assert_eq!(x.find_gte(64), Some(&64)); 344 | /// assert_eq!(x.find_gte(65), None); 345 | /// ``` 346 | pub fn find_gte(&self, x: X) -> Option<&T> 347 | where 348 | T: Borrow, 349 | X: Ord, 350 | { 351 | let x = x.borrow(); 352 | 353 | // Safety: this function should not address self.items[0], because it is not initialized 354 | let mut i = 1; 355 | 356 | let mask = prefetch_mask(self.items.len()); 357 | // the search loop is arithmetic-bound, not memory-bound when using prefetch. So offset part 358 | // of prefetch address is intentionally not masked, it allows to do less arithmetic in the loop. 359 | // It doesn't affect masking much because `Self::OFFSET` is just half of a cache line. 360 | // (see: [Optimized Eytzinger layout & memory prefetch](https://github.com/jonhoo/ordsearch/pull/27)) 361 | let prefetch_ptr = self.items.as_ptr().wrapping_add(Self::OFFSET); 362 | 363 | while i < self.items.len() { 364 | let offset = (Self::MULTIPLIER * i) & mask; 365 | do_prefetch(prefetch_ptr.wrapping_add(offset)); 366 | 367 | // SAFETY: i < self.items.len(), so in-bounds 368 | // SAFETY: 1 <= i, so not [0], so initialized 369 | let value = unsafe { self.items.get_unchecked(i).assume_init_ref() }.borrow(); 370 | // using branchless index update. At the moment compiler cannot reliably tranform 371 | // if expressions to branchless instructions like `cmov` and `setb` 372 | i = 2 * i + usize::from(x > value); 373 | } 374 | 375 | // Because the branchless loop navigates the tree until we reach a leaf node regardless of whether 376 | // the value is found or not, we now need to decode the found value index, if any. 377 | // 378 | // To understand how this works, it is useful to think of the index as a binary number. 379 | // The index update strategy always multiplies the index by 2 (which can be seen as `i <<= 1`) 380 | // and then adds 1 (which can be seen as `i |= 1`) if the value is greater than the current node value. 381 | // So, we can interpret the bits in index as a history of turns we made in the tree: a 0 bit means 382 | // we went left, a 1 bit means we went right. 383 | // 384 | // Another important observation is that when we find the target value, we make a left turn 385 | // and the corresponding bit in the index will be 0. More importantly, all subsequent bits 386 | // will be 1, because after we made a left turn we ended up in a subtree where all values 387 | // are less than the target value. 388 | // 389 | // Therefore, to decode the index we need to: 390 | // 1. get rid of all trailing 1 bits (dummy turns we made after we found the target value) 391 | // 2. get rid of one more bit to restore the index state before we made a left turn at the target element 392 | // 3. check if the resulting index is greater than 0 (0 means the target value is not in the tree) 393 | i >>= i.trailing_ones() + 1; 394 | // SAFETY: i < self.items.len(), so in-bounds 395 | // SAFETY: 1 <= i, so not [0], so initialized 396 | (i > 0).then(|| unsafe { self.items.get_unchecked(i).assume_init_ref() }) 397 | } 398 | 399 | /// Iterator over elements of a collection. 400 | /// 401 | /// It yields all items in an unspecified order. 402 | /// 403 | /// # Examples 404 | /// 405 | /// ``` 406 | /// # use ordsearch::OrderedCollection; 407 | /// let expected = vec![1, 2, 3, 4, 5]; 408 | /// let coll = OrderedCollection::from(expected.clone()); 409 | /// let mut values: Vec<_> = coll.iter().copied().collect(); 410 | /// values.sort(); 411 | /// assert_eq!(values, expected); 412 | /// ``` 413 | pub fn iter(&self) -> Iter<'_, T> { 414 | Iter { coll: self, idx: 0 } 415 | } 416 | } 417 | 418 | impl<'a, T: Ord> IntoIterator for &'a OrderedCollection { 419 | type Item = &'a T; 420 | type IntoIter = Iter<'a, T>; 421 | 422 | fn into_iter(self) -> Self::IntoIter { 423 | self.iter() 424 | } 425 | } 426 | 427 | impl IntoIterator for OrderedCollection { 428 | type Item = T; 429 | type IntoIter = alloc::vec::IntoIter; 430 | 431 | fn into_iter(self) -> Self::IntoIter { 432 | Vec::from(self).into_iter() 433 | } 434 | } 435 | 436 | /// Immutable iterator over elements in a [`OrderedCollection`] 437 | /// 438 | /// Created by [`OrderedCollection::iter()`]. 439 | pub struct Iter<'a, T> { 440 | coll: &'a OrderedCollection, 441 | idx: usize, 442 | } 443 | 444 | impl<'a, T> Iterator for Iter<'a, T> { 445 | type Item = &'a T; 446 | 447 | fn next(&mut self) -> Option { 448 | self.idx += 1; 449 | if self.idx < self.coll.items.len() { 450 | let value = &self.coll.items[self.idx]; 451 | // SAFETY: i > 0, so only initialized items are accessed 452 | // SAFETY: i < self.coll.items.len() so no out-of-bounds access 453 | Some(unsafe { value.assume_init_ref() }) 454 | } else { 455 | None 456 | } 457 | } 458 | } 459 | 460 | impl From> for Vec { 461 | /// Converts all elemenets into a new [`Vec`] in unspecified order 462 | /// 463 | /// # Examples 464 | /// 465 | /// ``` 466 | /// # use ordsearch::OrderedCollection; 467 | /// let x = vec![1, 2, 3, 4, 5]; 468 | /// let coll = OrderedCollection::from(x.clone()); 469 | /// let mut values = Vec::from(coll); 470 | /// values.sort(); 471 | /// assert_eq!(values, x); 472 | /// ``` 473 | fn from(mut value: OrderedCollection) -> Self { 474 | assert!(!value.items.is_empty()); 475 | 476 | let mut items = mem::take(&mut value.items); 477 | items.swap_remove(0); 478 | // SAFETY: 0-th element already removed, so all initialized 479 | unsafe { mem::transmute(items) } 480 | } 481 | } 482 | 483 | impl Drop for OrderedCollection { 484 | fn drop(&mut self) { 485 | // SAFETY: all elements beyond [0] are initialized, so can be dropped (which .truncate(1) will do) 486 | // at the end of drop(), items will hold a single `MaybeUninit` (`[0]`), which is uninitialized. 487 | // when the `Vec` is dropped, it will then drop `[0]`, but that's fine since dropping an uninitialized 488 | // `MaybeUninit` doesn't call `T::drop` and is sound. 489 | let items: &mut Vec = unsafe { mem::transmute(&mut self.items) }; 490 | items.truncate(1); 491 | } 492 | } 493 | 494 | #[cfg(feature = "nightly")] 495 | #[inline(always)] 496 | fn do_prefetch(addr: *const T) { 497 | unsafe { 498 | core::intrinsics::prefetch_read_data(addr, 3); 499 | } 500 | } 501 | 502 | #[cfg(not(feature = "nightly"))] 503 | fn do_prefetch(_addr: *const T) {} 504 | 505 | /// Calculates the prefetch mask for a given collection size. 506 | /// 507 | /// Creates a binary mask that fully covers a given [`usize`] value (e.g., for the value `0b100`, the mask is `0b111`). 508 | /// The prefetch mask is used to keep an element address inside the array boundaries when prefetching next values from 509 | /// memory. 510 | /// 511 | /// It is totally valid to prefetch invalid addresses from an x86 perspective[^1], but such prefetches do not 512 | /// aid algorithm performance and may worsen it by thrashing the CPU cache. Instead of prefetching outside 513 | /// the array boundaries, we use a prefetch mask to zero the offset and prefetch the first elements of the array 514 | /// instead, aiding subsequent searches. Essentially, masking the offset is a cheaper alternative to the 515 | /// `offset % size` function. 516 | /// 517 | /// [^1]: [Intel® 64 and IA-32 Architectures Software Developer’s Manual](https://software.intel.com/en-us/download/intel-64-and-ia-32-architectures-sdm-combined-volumes-1-2a-2b-2c-2d-3a-3b-3c-3d-and-4) 518 | fn prefetch_mask(n: usize) -> usize { 519 | if n > 0 { 520 | usize::max_value() >> n.leading_zeros() 521 | } else { 522 | 0 523 | } 524 | } 525 | 526 | #[cfg(test)] 527 | mod tests { 528 | use super::*; 529 | use alloc::{boxed::Box, vec}; 530 | 531 | #[test] 532 | fn complete_exact() { 533 | let x = OrderedCollection::from(vec![1, 2, 4, 8, 16, 32, 64]); 534 | assert_eq!(x.find_gte(1), Some(&1)); 535 | assert_eq!(x.find_gte(2), Some(&2)); 536 | assert_eq!(x.find_gte(4), Some(&4)); 537 | assert_eq!(x.find_gte(8), Some(&8)); 538 | assert_eq!(x.find_gte(16), Some(&16)); 539 | assert_eq!(x.find_gte(32), Some(&32)); 540 | assert_eq!(x.find_gte(64), Some(&64)); 541 | } 542 | 543 | #[test] 544 | fn complete_approximate() { 545 | let x = OrderedCollection::from(vec![1, 2, 4, 8, 16, 32, 64]); 546 | assert_eq!(x.find_gte(0), Some(&1)); 547 | assert_eq!(x.find_gte(3), Some(&4)); 548 | assert_eq!(x.find_gte(5), Some(&8)); 549 | assert_eq!(x.find_gte(6), Some(&8)); 550 | assert_eq!(x.find_gte(7), Some(&8)); 551 | for i in 9..16 { 552 | assert_eq!(x.find_gte(i), Some(&16)); 553 | } 554 | for i in 17..32 { 555 | assert_eq!(x.find_gte(i), Some(&32)); 556 | } 557 | for i in 33..64 { 558 | assert_eq!(x.find_gte(i), Some(&64)); 559 | } 560 | assert_eq!(x.find_gte(65), None); 561 | } 562 | 563 | #[test] 564 | fn unbalanced_exact() { 565 | let x = OrderedCollection::from(vec![1, 2, 4, 8, 16, 32, 64, 128, 256]); 566 | assert_eq!(x.find_gte(1), Some(&1)); 567 | assert_eq!(x.find_gte(2), Some(&2)); 568 | assert_eq!(x.find_gte(4), Some(&4)); 569 | assert_eq!(x.find_gte(8), Some(&8)); 570 | assert_eq!(x.find_gte(16), Some(&16)); 571 | assert_eq!(x.find_gte(32), Some(&32)); 572 | assert_eq!(x.find_gte(64), Some(&64)); 573 | assert_eq!(x.find_gte(128), Some(&128)); 574 | assert_eq!(x.find_gte(256), Some(&256)); 575 | } 576 | 577 | #[test] 578 | fn unbalanced_approximate() { 579 | let x = OrderedCollection::from(vec![1, 2, 4, 8, 16, 32, 64, 128, 256]); 580 | assert_eq!(x.find_gte(0), Some(&1)); 581 | assert_eq!(x.find_gte(3), Some(&4)); 582 | assert_eq!(x.find_gte(5), Some(&8)); 583 | assert_eq!(x.find_gte(6), Some(&8)); 584 | assert_eq!(x.find_gte(7), Some(&8)); 585 | for i in 9..16 { 586 | assert_eq!(x.find_gte(i), Some(&16)); 587 | } 588 | for i in 17..32 { 589 | assert_eq!(x.find_gte(i), Some(&32)); 590 | } 591 | for i in 33..64 { 592 | assert_eq!(x.find_gte(i), Some(&64)); 593 | } 594 | for i in 65..128 { 595 | assert_eq!(x.find_gte(i), Some(&128)); 596 | } 597 | for i in 129..256 { 598 | assert_eq!(x.find_gte(i), Some(&256)); 599 | } 600 | assert_eq!(x.find_gte(257), None); 601 | } 602 | 603 | #[test] 604 | fn check_into_iter() { 605 | let expected = vec![1, 2, 4, 8, 16, 32, 64, 128, 256]; 606 | let mut values = OrderedCollection::from_sorted_iter(expected.clone()) 607 | .into_iter() 608 | .collect::>(); 609 | values.sort(); 610 | assert_eq!(values, expected); 611 | } 612 | 613 | #[test] 614 | fn check_into_iter_empty() { 615 | let values = OrderedCollection::::from(vec![]); 616 | assert_eq!(Vec::from(values), vec![]); 617 | } 618 | 619 | #[test] 620 | fn check_iter() { 621 | let expected = vec![1, 2, 4, 8, 16, 32, 64, 128, 256]; 622 | let mut values = OrderedCollection::from_sorted_iter(expected.clone()) 623 | .iter() 624 | .copied() 625 | .collect::>(); 626 | values.sort(); 627 | assert_eq!(values, expected); 628 | } 629 | 630 | #[test] 631 | fn check_iter_empty() { 632 | let values = OrderedCollection::::from(vec![]); 633 | assert_eq!(values.iter().next(), None); 634 | } 635 | 636 | #[test] 637 | fn check_mask() { 638 | assert_eq!(prefetch_mask(0), 0b000); 639 | assert_eq!(prefetch_mask(1), 0b001); 640 | assert_eq!(prefetch_mask(2), 0b011); 641 | assert_eq!(prefetch_mask(3), 0b011); 642 | assert_eq!(prefetch_mask(4), 0b111); 643 | assert_eq!(prefetch_mask(usize::max_value()), usize::max_value()); 644 | } 645 | 646 | /// Because we're using non standard Eytzinger layout with uninitialized first element, we need to ensure that 647 | /// the `OrderedCollection` is safe to drop for non primitive types with custom drop logic. This test is supposed 648 | /// to be run with `miri`. 649 | #[test] 650 | fn check_drop_safety() { 651 | drop(OrderedCollection::from(vec![ 652 | Box::new(1), 653 | Box::new(2), 654 | Box::new(3), 655 | ])); 656 | } 657 | } 658 | --------------------------------------------------------------------------------