From 25921930664233ec6cc0a66a60ff2380fa2159b8 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Tue, 24 Jun 2025 23:27:54 +0200 Subject: [PATCH] review feedback (#32) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * removed superfluous inline (Alexander Grund) * made hasher equivalence a precondition for &=/|= (Andrzej Krzemienski) * documented exception safety guarantees (Andrzej Krzemienski) * mentioned Bloom filters are called so after Burton H Bloom (Dmitry Arkhipov) * added warning about OOM for very small FPR (Ivan Matek) * stressed config chart x axis is capacity/num elements rather than plain capacity (Ivan Matek) * s/[SIMD] is available/is enabled at compile time (Ivan Matek) * shut down clang-tidy warnings (Ivan Matek) * used "set union" for more clarity (Andrzej Krzemienski) * stressed early on that boost::bloom::filter is _not_ a container (Claudio DeSouza) * added bulk operations to roadmap (Dmitry Arkhipov) * added try_insert to roadmap (Konstantin Savvidy) * added estimated_size to roadmap (Konstantin Savvidy) * added alternative filters to roadmap (Konstantin Savvidy) * used instead of (Rubén Pérez) * mentioned endianness when serializing filters (Rubén Pérez) * corrected sloppiness about optimum k determination (Tomer Vromen) * added run-time specification of k to roadmap (Tomer Vromen) * added test/CMakeLists.txt (Rubén Pérez) * added CMake-based testing to GHA (Rubén Pérez) (#8) * added (Rubén Pérez) * added Codecov reporting (Rubén Pérez) (#9) * moved from boost::unordered::hash_is_avalanching to ContainerHash's boost::hash_is_avalanching (Ivan Matek/Peter Dimov) * added syntax highlighting to code snippets (Rubén Pérez) * avoided C-style casts in examples (Rubén Pérez) * added acknowledgements section (Peter Turcan) * added Getting Started section (Peter Turcan) * fixed example Jamfile and added example building to CI (Rubén Pérez) (#10) * added diagram about overlapping vs. non-overlapping subarrays (Rubén Pérez/Ivan Matek/Vinnie Falco) * made first code snippet self-contained (Rubén Pérez/Peter Turcan) * added more comments to genome.cpp (Rubén Pérez) * added support for arrays as blocks (Tomer Vromen) (#24) * removed emplace (Seth Heeren/Peter Dimov) (#25) * required the allocator to be of unsigned char (Seth Heeren/Peter Dimov) (#26) * added compile-time validation of Block types (Rubén Pérez) (#27) * added value type to displayed filter names in tables (Tomer Vromen) (#28) * used -march=native rather than -mavx2 (Ivan Matek) * adopted hash strategy with fastrange plus a separate MCG (Kostas Savvidis/Peter Dimov) (#30) * several maintenance commits --- .codecov.yml | 23 + .github/workflows/ci.yml | 216 +- CMakeLists.txt | 1 - README.md | 4 +- benchmark/comparison_table.cpp | 58 +- benchmark/fpr_c.cpp | 2 +- doc/bloom.adoc | 10 + doc/bloom/acknowledgements.adoc | 31 + doc/bloom/benchmarks.adoc | 1899 ++++++++++------- doc/bloom/configuration.adoc | 55 +- doc/bloom/fpr_estimation.adoc | 33 +- doc/bloom/future_work.adoc | 71 + doc/bloom/implementation_notes.adoc | 41 +- doc/bloom/intro.adoc | 29 +- doc/bloom/primer.adoc | 56 +- doc/bloom/reference.adoc | 1 + doc/bloom/reference/block.adoc | 4 +- doc/bloom/reference/fast_multiblock32.adoc | 2 + doc/bloom/reference/filter.adoc | 79 +- doc/bloom/reference/header_bloom.adoc | 9 + doc/bloom/reference/header_filter.adoc | 17 +- doc/bloom/reference/multiblock.adoc | 4 +- doc/bloom/reference/subfilters.adoc | 2 + doc/bloom/release_notes.adoc | 2 +- doc/bloom/tutorial.adoc | 184 +- doc/img/bloom_insertion.png | Bin 4915 -> 5451 bytes doc/img/bloom_lookup.png | Bin 2978 -> 5308 bytes doc/img/db_speedup.png | Bin 4718 -> 6666 bytes doc/img/fpr_c.png | Bin 57609 -> 50313 bytes doc/img/fpr_n_k.png | Bin 10870 -> 13806 bytes doc/img/fpr_n_k_bk.png | Bin 12650 -> 16040 bytes doc/img/stride.png | Bin 0 -> 11554 bytes doc/index.html | 4 +- example/Jamfile.v2 | 6 +- example/basic.cpp | 5 +- example/genome.cpp | 30 +- example/serialization.cpp | 14 +- extra/boost_bloom.natvis | 4 +- include/boost/bloom.hpp | 18 + include/boost/bloom/block.hpp | 41 +- include/boost/bloom/detail/block_base.hpp | 44 +- include/boost/bloom/detail/block_ops.hpp | 95 + .../bloom/detail/constexpr_bit_width.hpp | 2 +- include/boost/bloom/detail/core.hpp | 139 +- .../bloom/detail/fast_multiblock32_avx2.hpp | 14 +- .../bloom/detail/fast_multiblock32_neon.hpp | 20 +- .../bloom/detail/fast_multiblock32_sse2.hpp | 19 +- .../bloom/detail/fast_multiblock64_avx2.hpp | 14 +- include/boost/bloom/detail/mulx64.hpp | 51 +- include/boost/bloom/detail/type_traits.hpp | 50 + include/boost/bloom/fast_multiblock32.hpp | 4 +- include/boost/bloom/fast_multiblock64.hpp | 4 +- include/boost/bloom/filter.hpp | 106 +- include/boost/bloom/multiblock.hpp | 19 +- index.html | 4 +- test/CMakeLists.txt | 13 + test/Jamfile.v2 | 17 +- test/test_boost_bloom_hpp.cpp | 25 + test/test_capacity.cpp | 4 +- test/test_construction.cpp | 64 +- test/test_fpr.cpp | 5 +- test/test_insertion.cpp | 15 - test/test_types.hpp | 12 +- test/test_utilities.hpp | 3 +- 64 files changed, 2369 insertions(+), 1329 deletions(-) create mode 100644 .codecov.yml create mode 100644 doc/bloom/acknowledgements.adoc create mode 100644 doc/bloom/future_work.adoc create mode 100644 doc/bloom/reference/header_bloom.adoc create mode 100644 doc/img/stride.png create mode 100644 include/boost/bloom.hpp create mode 100644 include/boost/bloom/detail/block_ops.hpp create mode 100644 test/CMakeLists.txt create mode 100644 test/test_boost_bloom_hpp.cpp diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 0000000..ac5bc8d --- /dev/null +++ b/.codecov.yml @@ -0,0 +1,23 @@ +# Copyright 2019 - 2021 Alexander Grund +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at http://boost.org/LICENSE_1_0.txt) +# +# Sample codecov configuration file. Edit as required + +codecov: + max_report_age: off + require_ci_to_pass: yes + notify: + # Increase this if you have multiple coverage collection jobs + after_n_builds: 1 + wait_for_ci: yes + +# Change how pull request comments look +comment: + layout: "reach,diff,flags,files,footer" + +# Ignore specific files or folders. Glob patterns are supported. +# See https://docs.codecov.com/docs/ignoring-paths +ignore: + - extra/**/* + # - test/**/* \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a93f627..a7c8535 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,6 +11,8 @@ on: env: UBSAN_OPTIONS: print_stacktrace=1 + B2_CI_VERSION: 1 + LCOV_BRANCH_COVERAGE: 0 jobs: posix: @@ -246,19 +248,17 @@ jobs: export ADDRMD=${{matrix.address-model}} ./b2 -j3 libs/$LIBRARY/test toolset=${{matrix.toolset}} cxxstd=${{matrix.cxxstd}} ${ADDRMD:+address-model=$ADDRMD} variant=debug,release + - name: Compile examples + run: | + cd ../boost-root + export ADDRMD=${{matrix.address-model}} + ./b2 -j3 libs/$LIBRARY/example toolset=${{matrix.toolset}} cxxstd=${{matrix.cxxstd}} ${ADDRMD:+address-model=$ADDRMD} variant=debug,release + windows: strategy: fail-fast: false matrix: include: - - toolset: msvc-14.0 - cxxstd: 14,latest - addrmd: 32,64 - os: windows-2019 - - toolset: msvc-14.2 - cxxstd: "14,17,20,latest" - addrmd: 32,64 - os: windows-2019 - toolset: msvc-14.3 cxxstd: "14,17,20,latest" addrmd: 32,64 @@ -270,7 +270,7 @@ jobs: - toolset: gcc cxxstd: "11,14,17,2a" addrmd: 64 - os: windows-2019 + os: windows-2022 runs-on: ${{matrix.os}} @@ -305,3 +305,201 @@ jobs: run: | cd ../boost-root b2 -j3 libs/%LIBRARY%/test toolset=${{matrix.toolset}} cxxstd=${{matrix.cxxstd}} address-model=${{matrix.addrmd}} variant=debug,release embed-manifest-via=linker + + - name: Compile examples + shell: cmd + run: | + cd ../boost-root + b2 -j3 libs/%LIBRARY%/example toolset=${{matrix.toolset}} cxxstd=${{matrix.cxxstd}} address-model=${{matrix.addrmd}} variant=debug,release embed-manifest-via=linker + + posix-cmake-test: + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-22.04 + - os: ubuntu-24.04 + - os: macos-13 + - os: macos-14 + - os: macos-15 + + runs-on: ${{matrix.os}} + + steps: + - uses: actions/checkout@v4 + + - name: Install packages + if: matrix.install + run: sudo apt-get -y install ${{matrix.install}} + + - name: Setup Boost + run: | + echo GITHUB_REPOSITORY: $GITHUB_REPOSITORY + LIBRARY=${GITHUB_REPOSITORY#*/} + echo LIBRARY: $LIBRARY + echo "LIBRARY=$LIBRARY" >> $GITHUB_ENV + echo GITHUB_BASE_REF: $GITHUB_BASE_REF + echo GITHUB_REF: $GITHUB_REF + REF=${GITHUB_BASE_REF:-$GITHUB_REF} + REF=${REF#refs/heads/} + echo REF: $REF + BOOST_BRANCH=develop && [ "$REF" == "master" ] && BOOST_BRANCH=master || true + echo BOOST_BRANCH: $BOOST_BRANCH + cd .. + git clone -b $BOOST_BRANCH --depth 1 https://github.com/boostorg/boost.git boost-root + cd boost-root + mkdir -p libs/$LIBRARY + cp -r $GITHUB_WORKSPACE/* libs/$LIBRARY + git submodule update --init tools/boostdep + python tools/boostdep/depinst/depinst.py -I benchmark -I example --git_args "--jobs 3" $LIBRARY + + - name: Configure + run: | + cd ../boost-root + mkdir __build__ && cd __build__ + cmake -DBOOST_INCLUDE_LIBRARIES=$LIBRARY -DBUILD_TESTING=ON .. + + - name: Build tests + run: | + cd ../boost-root/__build__ + cmake --build . --target tests + + - name: Run tests + run: | + cd ../boost-root/__build__ + ctest --output-on-failure --no-tests=error + + windows-cmake-test: + strategy: + fail-fast: false + matrix: + include: + - os: windows-2022 + + runs-on: ${{matrix.os}} + + steps: + - uses: actions/checkout@v4 + + - name: Setup Boost + shell: cmd + run: | + echo GITHUB_REPOSITORY: %GITHUB_REPOSITORY% + for /f %%i in ("%GITHUB_REPOSITORY%") do set LIBRARY=%%~nxi + echo LIBRARY: %LIBRARY% + echo LIBRARY=%LIBRARY%>>%GITHUB_ENV% + echo GITHUB_BASE_REF: %GITHUB_BASE_REF% + echo GITHUB_REF: %GITHUB_REF% + if "%GITHUB_BASE_REF%" == "" set GITHUB_BASE_REF=%GITHUB_REF% + set BOOST_BRANCH=develop + for /f %%i in ("%GITHUB_BASE_REF%") do if "%%~nxi" == "master" set BOOST_BRANCH=master + echo BOOST_BRANCH: %BOOST_BRANCH% + cd .. + git clone -b %BOOST_BRANCH% --depth 1 https://github.com/boostorg/boost.git boost-root + cd boost-root + mkdir -p libs\%LIBRARY% # remove when/if the library makes it into Boost + xcopy /s /e /q %GITHUB_WORKSPACE% libs\%LIBRARY%\ + git submodule update --init tools/boostdep + python tools/boostdep/depinst/depinst.py -I benchmark -I example --git_args "--jobs 3" %LIBRARY% + + - name: Configure + shell: cmd + run: | + cd ../boost-root + mkdir __build__ && cd __build__ + cmake -DBOOST_INCLUDE_LIBRARIES=%LIBRARY% -DBUILD_TESTING=ON .. + + - name: Build tests (Debug) + shell: cmd + run: | + cd ../boost-root/__build__ + cmake --build . --target tests --config Debug + + - name: Run tests (Debug) + shell: cmd + run: | + cd ../boost-root/__build__ + ctest --output-on-failure --no-tests=error -C Debug + + - name: Build tests (Release) + shell: cmd + run: | + cd ../boost-root/__build__ + cmake --build . --target tests --config Release + + - name: Run tests (Release) + shell: cmd + run: | + cd ../boost-root/__build__ + ctest --output-on-failure --no-tests=error -C Release + + codecov: + strategy: + fail-fast: false + matrix: + include: + - toolset: gcc-14 + cxxstd: "11" + os: ubuntu-24.04 + install: g++-14-multilib + address-model: 32,64 + + runs-on: ${{matrix.os}} + container: + image: ${{matrix.container}} + volumes: + - /node20217:/node20217:rw,rshared + - ${{ startsWith(matrix.container, 'ubuntu:1') && '/node20217:/__e/node20:ro,rshared' || ' ' }} + + defaults: + run: + shell: bash + + steps: + - name: Setup container environment + if: matrix.container + run: | + apt-get update + apt-get -y install sudo python3 git g++ curl xz-utils + if [[ "${{matrix.container}}" == "ubuntu:1"* ]]; then + # Node 20 doesn't work with Ubuntu 16/18 glibc: https://github.com/actions/checkout/issues/1590 + curl -sL https://archives.boost.io/misc/node/node-v20.9.0-linux-x64-glibc-217.tar.xz | tar -xJ --strip-components 1 -C /node20217 + fi + + - uses: actions/checkout@v4 + + - name: Fetch Boost.CI + uses: actions/checkout@v4 + with: + repository: boostorg/boost-ci + ref: master + path: boost-ci-cloned + + - name: Install Boost.CI + run: | + cp -r boost-ci-cloned/ci . + rm -rf boost-ci-cloned + + - name: Install packages + if: matrix.install + run: | + sudo apt-get update + sudo apt-get -y install ${{matrix.install}} + + - name: Setup Boost + env: + B2_TOOLSET: ${{matrix.toolset}} + B2_CXXSTD: ${{matrix.cxxstd}} + B2_ADDRESS_MODEL: ${{matrix.address-model}} + run: source ci/github/install.sh + + - name: Setup coverage collection + run: ci/github/codecov.sh "setup" + + - name: Run tests + run: ci/build.sh + + - name: Upload coverage + env: + CODECOV_TOKEN: ${{secrets.CODECOV_TOKEN}} + run: ci/codecov.sh "upload" \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 1fd37e2..bd5e5bb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,7 +20,6 @@ target_link_libraries(boost_bloom Boost::core Boost::throw_exception Boost::type_traits - Boost::unordered ) target_compile_features(boost_bloom INTERFACE cxx_std_11) diff --git a/README.md b/README.md index 2c567d1..a39e142 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Candidate Boost Bloom Library -[![Branch](https://img.shields.io/badge/branch-master-brightgreen.svg)](https://github.com/joaquintides/bloom/tree/master) [![CI](https://github.com/joaquintides/bloom/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/joaquintides/bloom/actions/workflows/ci.yml) [![Drone status](https://img.shields.io/drone/build/joaquintides/bloom/master?server=https%3A%2F%2Fdrone.cpp.al&logo=drone&logoColor=%23CCCCCC&label=CI)](https://drone.cpp.al/joaquintides/bloom) [![Documentation](https://img.shields.io/badge/docs-master-brightgreen.svg)](https://master.bloom.cpp.al/)
-[![Branch](https://img.shields.io/badge/branch-develop-brightgreen.svg)](https://github.com/joaquintides/bloom/tree/develop) [![CI](https://github.com/joaquintides/bloom/actions/workflows/ci.yml/badge.svg?branch=develop)](https://github.com/joaquintides/bloom/actions/workflows/ci.yml) [![Drone status](https://img.shields.io/drone/build/joaquintides/bloom/develop?server=https%3A%2F%2Fdrone.cpp.al&logo=drone&logoColor=%23CCCCCC&label=CI)](https://drone.cpp.al/joaquintides/bloom) [![Documentation](https://img.shields.io/badge/docs-develop-brightgreen.svg)](https://develop.bloom.cpp.al/)
+[![Branch](https://img.shields.io/badge/branch-master-brightgreen.svg)](https://github.com/joaquintides/bloom/tree/master) [![CI](https://github.com/joaquintides/bloom/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/joaquintides/bloom/actions/workflows/ci.yml) [![Drone status](https://img.shields.io/drone/build/joaquintides/bloom/master?server=https%3A%2F%2Fdrone.cpp.al&logo=drone&logoColor=%23CCCCCC&label=CI)](https://drone.cpp.al/joaquintides/bloom) [![codecov](https://codecov.io/gh/joaquintides/bloom/branch/master/graph/badge.svg)](https://app.codecov.io/gh/joaquintides/bloom/tree/master) [![Documentation](https://img.shields.io/badge/docs-master-brightgreen.svg)](https://master.bloom.cpp.al/)
+[![Branch](https://img.shields.io/badge/branch-develop-brightgreen.svg)](https://github.com/joaquintides/bloom/tree/develop) [![CI](https://github.com/joaquintides/bloom/actions/workflows/ci.yml/badge.svg?branch=develop)](https://github.com/joaquintides/bloom/actions/workflows/ci.yml) [![Drone status](https://img.shields.io/drone/build/joaquintides/bloom/develop?server=https%3A%2F%2Fdrone.cpp.al&logo=drone&logoColor=%23CCCCCC&label=CI)](https://drone.cpp.al/joaquintides/bloom) [![codecov](https://codecov.io/gh/joaquintides/bloom/branch/develop/graph/badge.svg)](https://app.codecov.io/gh/joaquintides/bloom/tree/develop) [![Documentation](https://img.shields.io/badge/docs-develop-brightgreen.svg)](https://develop.bloom.cpp.al/)
[![BSL 1.0](https://img.shields.io/badge/license-BSL_1.0-blue.svg)](https://www.boost.org/users/license.html) C++11 required Header-only library (Candidate) Boost.Bloom provides the class template `boost::bloom::filter` that diff --git a/benchmark/comparison_table.cpp b/benchmark/comparison_table.cpp index 1074428..347361e 100644 --- a/benchmark/comparison_table.cpp +++ b/benchmark/comparison_table.cpp @@ -53,11 +53,7 @@ void resume_timing() measure_start+=std::chrono::high_resolution_clock::now()-measure_pause; } -#include -#include -#include -#include -#include +#include #include #include #include @@ -212,14 +208,14 @@ using namespace boost::bloom; template using filters1=boost::mp11::mp_list< filter, - filter>, - filter,1> + filter>, + filter,1> >; template using filters2=boost::mp11::mp_list< - filter>, - filter,1>, + filter>, + filter,1>, filter> >; @@ -230,6 +226,13 @@ using filters3=boost::mp11::mp_list< filter,1> >; +template +using filters4=boost::mp11::mp_list< + filter>, + filter,1>, + filter> +>; + int main(int argc,char* argv[]) { if(argc<2){ @@ -275,9 +278,9 @@ int main(int argc,char* argv[]) "\n" " \n" " \n" - " \n" - " \n" - " \n" + " \n" + " \n" + " \n" " \n" " \n" " \n"<< @@ -294,9 +297,9 @@ int main(int argc,char* argv[]) std::cout<< " \n" " \n" - " \n" - " \n" - " \n" + " \n" + " \n" + " \n" " \n" " \n" " \n"<< @@ -313,9 +316,9 @@ int main(int argc,char* argv[]) std::cout<< " \n" " \n" - " \n" - " \n" - " \n" + " \n" + " \n" + " \n" " \n" " \n" " \n"<< @@ -329,5 +332,24 @@ int main(int argc,char* argv[]) row>(16); row>(20); + std::cout<< + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n"<< + subheader<< + subheader<< + subheader<< + " \n"; + + row>( 8); + row>(12); + row>(16); + row>(20); + std::cout<<"
filter<K>filter<1,block<uint64_t,K>>filter<1,block<uint64_t,K>,1>filter<int,K>filter<int,1,block<uint64_t,K>>filter<int,1,block<uint64_t,K>,1>
c
filter<1,multiblock<uint64_t,K>>filter<1,multiblock<uint64_t,K>,1>filter<1,fast_multiblock32<K>>filter<int,1,multiblock<uint64_t,K>>filter<int,1,multiblock<uint64_t,K>,1>filter<int,1,fast_multiblock32<K>>
c
filter<1,fast_multiblock32<K>,1>filter<1,fast_multiblock64<K>>filter<1,fast_multiblock64<K>,1>filter<int,1,fast_multiblock32<K>,1>filter<int,1,fast_multiblock64<K>>filter<int,1,fast_multiblock64<K>,1>
c
filter<int,1,block<uint64_t[8],K>>filter<int,1,block<uint64_t[8],K>,1>filter<int,1,multiblock<uint64_t[8],K>>
c
\n"; } diff --git a/benchmark/fpr_c.cpp b/benchmark/fpr_c.cpp index e11db18..180b6c7 100644 --- a/benchmark/fpr_c.cpp +++ b/benchmark/fpr_c.cpp @@ -97,7 +97,7 @@ int main() std::cout < ++++ @@ -35,7 +43,9 @@ include::bloom/tutorial.adoc[] include::bloom/configuration.adoc[] include::bloom/benchmarks.adoc[] include::bloom/reference.adoc[] +include::bloom/future_work.adoc[] include::bloom/fpr_estimation.adoc[] include::bloom/implementation_notes.adoc[] include::bloom/release_notes.adoc[] +include::bloom/acknowledgements.adoc[] include::bloom/copyright.adoc[] diff --git a/doc/bloom/acknowledgements.adoc b/doc/bloom/acknowledgements.adoc new file mode 100644 index 0000000..3791b51 --- /dev/null +++ b/doc/bloom/acknowledgements.adoc @@ -0,0 +1,31 @@ +[#acknowledgements] += Acknowledgements + +:idprefix: acknowledgements_ + +Peter Dimov and Christian Mazakas reviewed significant portions of the code +and documentation during the development phase. Sam Darwin provided support +for CI setup and documentation building. + +The Boost acceptance review took place between the 13th and 22nd of May, +2025. Big thanks to Arnaud Becheler for his expert managing. The +following people participated in the review: +Dmitry Arkhipov, +David Bien, +Claudio DeSouza, +Peter Dimov, +Vinnie Falco, +Alexander Grund, +Seth Heeren, +Andrzej Krzemieński, +Ivan Matek, +Christian Mazakas, +Rubén Pérez, +Kostas Savvidis, +Peter Turcan, +Tomer Vromen. Many thanks to all of them for their very helpful feedback. + +Boost.Bloom was designed and written in +https://en.wikipedia.org/wiki/C%C3%A1ceres%2c_Spain[Cáceres^] and +https://en.wikipedia.org/wiki/Oropesa,_Spain[Oropesa^], +January-June 2025. \ No newline at end of file diff --git a/doc/bloom/benchmarks.adoc b/doc/bloom/benchmarks.adoc index bab9ecd..3d839ca 100644 --- a/doc/bloom/benchmarks.adoc +++ b/doc/bloom/benchmarks.adoc @@ -4,10 +4,10 @@ :idprefix: benchmarks_ (More results in a -https://github.com/joaquintides/boost_bloom_benchmarks[dedicated repo^].) +https://github.com/boostorg/boost_bloom_benchmarks[dedicated repo^].) The tables show the false positive rate and execution times in nanoseconds per operation -for nine different configurations of `boost::bloom::filter` where 10M elements have +for several configurations of `boost::bloom::filter` where 10M elements have been inserted. * **ins.:** Insertion. @@ -18,18 +18,19 @@ Filters are constructed with a capacity `c * N` (bits), so `c` is the number of bits used per element. For each combination of `c` and a given filter configuration, we have selected the optimum value of `K` (that yielding the minimum FPR). Standard release-mode settings are used; AVX2 is indicated for Visual Studio builds -(`/arch:AVX2`) and GCC/Clang builds (`-mavx2`), which causes +(`/arch:AVX2`) and GCC/Clang builds (`-march=native`), which causes `fast_multiblock32` and `fast_multiblock64` to use their AVX2 variant. == GCC 14, x64 +++ - +
+
- - - + + + @@ -52,80 +53,80 @@ Standard release-mode settings are used; AVX2 is indicated for Visual Studio bui - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - + + + @@ -148,80 +149,80 @@ Standard release-mode settings are used; AVX2 is indicated for Visual Studio bui - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - + + + @@ -244,87 +245,185 @@ Standard release-mode settings are used; AVX2 is indicated for Visual Studio bui - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
filter<K>filter<1,block<uint64_t,K>>filter<1,block<uint64_t,K>,1>filter<int,K>filter<int,1,block<uint64_t,K>>filter<int,1,block<uint64_t,K>,1>
c
8 62.156612.2111.0616.942.151914.0415.8220.84 43.34624.444.734.733.34675.826.506.54 53.04485.235.375.383.03835.466.136.15
12 90.314618.0917.0817.860.318047.6552.2827.38 51.03105.025.075.151.030010.0710.9810.98 60.82446.876.346.280.826811.5212.5212.49
16 110.045628.6729.4317.810.046983.2599.7534.96 60.40356.306.486.310.403418.3418.3818.39 70.28857.437.297.570.288318.9719.0819.08
20 140.006646.5439.9119.260.0065125.12135.1639.56 70.187910.0810.499.530.188721.8721.9921.99 80.11859.689.089.680.119422.8525.2125.17
filter<1,multiblock<uint64_t,K>>filter<1,multiblock<uint64_t,K>,1>filter<1,fast_multiblock32<K>>filter<int,1,multiblock<uint64_t,K>>filter<int,1,multiblock<uint64_t,K>,1>filter<int,1,fast_multiblock32<K>>
c
8 52.45155.686.496.502.45106.427.107.12 52.32086.067.477.712.31577.238.988.96 52.72343.373.803.752.73615.905.675.70
12 80.42447.399.459.360.420713.4517.1417.16 80.37588.2010.0810.120.372416.4920.6120.65 80.54072.723.383.350.54157.637.927.97
16 110.077611.2815.0815.130.076429.2530.2630.26 110.064117.9015.6515.550.064234.9035.5235.29 110.11746.766.874.870.117919.1120.6414.95
20 130.014814.3920.0318.670.015038.4039.1839.20 140.012016.4122.9422.460.012240.6850.8550.24 130.02779.389.606.480.027522.0023.6616.68
filter<1,fast_multiblock32<K>,1>filter<1,fast_multiblock64<K>>filter<1,fast_multiblock64<K>,1>filter<int,1,fast_multiblock32<K>,1>filter<int,1,fast_multiblock64<K>>filter<int,1,fast_multiblock64<K>,1>
c
8 52.46253.363.733.702.47884.243.923.96 52.43884.925.655.582.45465.595.865.89 52.31985.035.495.572.32345.976.015.92
12 80.44283.353.693.670.43948.198.038.03 80.41903.464.774.760.42108.759.699.74 80.37474.815.525.460.375411.1312.2312.20
16 110.08666.697.185.100.086518.6020.3414.59 11 0.07818.639.827.7924.6525.8621.31 110.06519.809.557.630.064223.7226.1221.53
20 130.01809.089.057.130.017821.8023.5616.42 130.014711.6013.649.100.016031.9336.2524.75 140.011211.2915.1216.840.011031.6436.5225.04
filter<int,1,block<uint64_t[8],K>>filter<int,1,block<uint64_t[8],K>,1>filter<int,1,multiblock<uint64_t[8],K>>
cKFPR
[%]
ins.succ.
lkp.
uns.
lkp.
KFPR
[%]
ins.succ.
lkp.
uns.
lkp.
KFPR
[%]
ins.succ.
lkp.
uns.
lkp.
852.32927.588.6215.6262.298613.0710.8020.7272.338915.2815.2715.29
1270.414017.2218.4418.8470.384522.1921.2820.79100.346826.6328.3528.33
1690.085227.3927.8621.79100.071435.4833.8326.65110.049345.0049.1449.05
20120.019641.6142.3424.90120.015250.9050.3128.79150.007674.4173.9273.83
+ +++ == Clang 18, x64 +++ - +
+
- - - + + + @@ -347,80 +446,80 @@ Standard release-mode settings are used; AVX2 is indicated for Visual Studio bui - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - + + + @@ -443,80 +542,80 @@ Standard release-mode settings are used; AVX2 is indicated for Visual Studio bui - - - + + + + + + + + - - - - - - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - + + + @@ -539,87 +638,185 @@ Standard release-mode settings are used; AVX2 is indicated for Visual Studio bui - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
filter<K>filter<1,block<uint64_t,K>>filter<1,block<uint64_t,K>,1>filter<int,K>filter<int,1,block<uint64_t,K>>filter<int,1,block<uint64_t,K>,1>
c
8 62.156610.8410.5116.472.151913.5714.1220.85 43.34623.834.634.493.34675.655.675.63 53.04484.495.195.193.03835.426.035.98
12 90.314615.6915.3716.960.318047.8549.4926.49 51.03104.295.104.961.030011.0310.9710.98 60.82444.985.785.730.826810.6411.5611.58
16 110.045623.8324.8216.990.046983.5485.3531.09 60.40355.466.316.130.403417.8917.9517.96 70.28856.177.837.520.288317.2319.0719.08
20 140.006642.2439.9220.020.0065120.39119.0936.47 70.18798.799.6115.230.188721.9821.7221.72 80.11855.616.205.940.119414.7115.3015.26
filter<1,multiblock<uint64_t,K>>filter<1,multiblock<uint64_t,K>,1>filter<1,fast_multiblock32<K>>filter<int,1,multiblock<uint64_t,K>>filter<int,1,multiblock<uint64_t,K>,1>filter<int,1,fast_multiblock32<K>>
c
8 52.45153.534.132.45104.325.175.2152.3157 4.104.634.66 52.32083.573.953.9552.72343.033.023.042.73613.563.453.44
12 80.42443.033.693.660.42078.8310.5510.56 80.37584.054.184.220.37249.9112.4612.52 80.54072.472.552.550.54157.857.547.54
16 110.07767.077.797.990.076419.6222.7222.75 110.06417.268.078.040.064219.0522.9322.92 110.11745.455.854.450.117915.1916.3212.45
20 130.01489.1010.9910.580.015023.8729.7329.79 140.01209.6211.6812.150.012224.9230.8030.80 130.02777.778.397.290.027517.3618.3413.99
filter<1,fast_multiblock32<K>,1>filter<1,fast_multiblock64<K>>filter<1,fast_multiblock64<K>,1>filter<int,1,fast_multiblock32<K>,1>filter<int,1,fast_multiblock64<K>>filter<int,1,fast_multiblock64<K>,1>
c
8 52.46253.072.922.952.47883.723.383.36 52.43884.184.734.712.45464.765.075.02 52.31984.274.604.572.32345.275.375.25
12 80.44282.962.792.780.43947.607.587.56 80.41903.204.054.130.42108.669.249.27 80.37474.334.534.660.375410.2811.5211.58
16 110.08665.545.623.920.086514.4316.0911.92 11 0.07816.627.535.9120.1921.5517.22 110.06517.037.616.420.064218.8321.0516.85
20 130.01809.889.246.200.017816.6418.2613.45 130.014710.0411.538.070.016025.1229.4820.08 140.011210.1411.207.990.011025.3726.9019.62
filter<int,1,block<uint64_t[8],K>>filter<int,1,block<uint64_t[8],K>,1>filter<int,1,multiblock<uint64_t[8],K>>
cKFPR
[%]
ins.succ.
lkp.
uns.
lkp.
KFPR
[%]
ins.succ.
lkp.
uns.
lkp.
KFPR
[%]
ins.succ.
lkp.
uns.
lkp.
852.32927.677.9715.0862.298613.9110.2719.7672.338913.8313.8713.86
1270.414017.0615.6117.5270.384525.8021.2120.63100.346831.1732.5832.61
1690.085228.8926.7421.75100.071436.2132.6624.97110.049345.5848.1047.96
20120.019643.3035.8124.25120.015252.3840.0826.46150.007678.0672.3472.55
+ +++ == Clang 15, ARM64 +++ - +
+
- - - + + + @@ -642,176 +839,80 @@ Standard release-mode settings are used; AVX2 is indicated for Visual Studio bui - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - - - - - - - - - - - + + + + + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + - - - + + + @@ -834,87 +935,281 @@ Standard release-mode settings are used; AVX2 is indicated for Visual Studio bui - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - - - - - - + + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
filter<K>filter<1,block<uint64_t,K>>filter<1,block<uint64_t,K>,1>filter<int,K>filter<int,1,block<uint64_t,K>>filter<int,1,block<uint64_t,K>,1>
c
8 62.15669.567.9217.752.15197.756.1612.93 43.34621.953.563.323.34672.051.992.00 53.04482.782.832.853.03832.132.022.04
12 90.314623.4321.4922.680.318013.1511.0416.16 51.03105.866.514.6560.82445.335.765.96
16110.045640.5132.7322.2660.40358.988.137.8470.28859.189.258.74
20140.006667.3550.6824.7670.18799.5110.229.3780.11858.187.947.73
filter<1,multiblock<uint64_t,K>>filter<1,multiblock<uint64_t,K>,1>filter<1,fast_multiblock32<K>>
cKFPR
[%]
ins.succ.
lkp.
uns.
lkp.
KFPR
[%]
ins.succ.
lkp.
uns.
lkp.
KFPR
[%]
ins.succ.
lkp.
uns.
lkp.
852.45153.042.813.4852.32083.483.913.6752.72343.063.461.03003.50 3.47
1280.42447.577.397.9980.37586.958.089.2280.54072.736.676.463.4960.82683.223.163.09
16 110.077615.169.9211.60110.064115.3512.6711.48110.117410.8510.727.260.046930.8824.0318.3860.40346.666.316.5670.28836.925.985.87
20130.014817.7717.0518.43 140.012020.0217.3617.71130.027711.0613.688.150.006553.8340.2920.9170.18879.137.947.8680.11947.696.466.74
filter<1,fast_multiblock32<K>,1>filter<1,fast_multiblock64<K>>filter<1,fast_multiblock64<K>,1>filter<int,1,multiblock<uint64_t,K>>filter<int,1,multiblock<uint64_t,K>,1>filter<int,1,fast_multiblock32<K>>
c
8 52.46253.244.323.192.45102.702.482.56 52.45153.674.584.332.31572.702.592.58 52.32083.244.294.172.73612.382.492.50
12 80.44285.935.954.540.42073.764.624.28 80.42447.688.479.150.37244.314.644.53 80.37584.124.684.520.54152.753.443.49
16 110.08667.367.475.010.076410.959.929.78 110.07769.488.738.700.064210.689.539.51 110.06419.468.538.500.11798.698.415.77
20 130.01809.4610.425.96130.014814.2913.2513.520.015015.2212.6812.37 140.012015.8213.630.012214.9612.9012.72130.027510.4311.086.61
filter<int,1,fast_multiblock32<K>,1>filter<int,1,fast_multiblock64<K>>filter<int,1,fast_multiblock64<K>,1>
cKFPR
[%]
ins.succ.
lkp.
uns.
lkp.
KFPR
[%]
ins.succ.
lkp.
uns.
lkp.
KFPR
[%]
ins.succ.
lkp.
uns.
lkp.
852.47882.392.512.5452.45102.722.512.5552.31572.712.572.59
1280.43942.943.103.0380.42073.703.883.8880.37244.264.684.39
16110.08658.298.495.82110.076410.949.889.73110.064211.289.959.82
20130.017810.4110.886.60130.015016.0012.9013.27140.012215.77 13.4713.55
filter<int,1,block<uint64_t[8],K>>filter<int,1,block<uint64_t[8],K>,1>filter<int,1,multiblock<uint64_t[8],K>>
cKFPR
[%]
ins.succ.
lkp.
uns.
lkp.
KFPR
[%]
ins.succ.
lkp.
uns.
lkp.
KFPR
[%]
ins.succ.
lkp.
uns.
lkp.
852.32924.644.3011.2962.29868.724.9213.5072.33898.876.656.71
1270.41409.378.4513.0370.384513.337.8012.91100.346814.5712.2512.16
1690.085216.4213.6814.25100.071421.9815.8816.60110.049326.0521.9623.13
20120.019623.1317.5215.45120.015228.9122.2117.26150.007649.7138.1939.07
+ +++ == VS 2022, x64 +++ - +
+
- - - + + + @@ -937,266 +1232,362 @@ Standard release-mode settings are used; AVX2 is indicated for Visual Studio bui - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
filter<K>filter<1,block<uint64_t,K>>filter<1,block<uint64_t,K>,1>filter<int,K>filter<int,1,block<uint64_t,K>>filter<int,1,block<uint64_t,K>,1>
c
8 62.156614.8413.0517.912.151911.1011.8715.76 43.34627.064.494.503.34674.213.843.79 53.04488.115.745.853.03834.724.584.52
12 90.314625.1820.5918.580.318018.0518.0416.58 51.03109.135.445.501.03006.225.705.67 60.824410.507.776.62
16110.045636.5539.3119.4660.403513.407.317.2870.288512.048.9114.47
20140.006683.3083.9324.9870.187916.3112.6415.8280.118520.8115.8315.73
filter<1,multiblock<uint64_t,K>>filter<1,multiblock<uint64_t,K>,1>filter<1,fast_multiblock32<K>>
cKFPR
[%]
ins.succ.
lkp.
uns.
lkp.
KFPR
[%]
ins.succ.
lkp.
uns.
lkp.
KFPR
[%]
ins.succ.
lkp.
uns.
lkp.
852.45159.506.316.3952.32089.616.456.4352.72343.784.274.26
1280.424415.7510.5111.3080.375820.979.039.3780.54073.526.144.50
16110.077625.5820.3118.44110.064127.3515.2419.41110.117410.9214.3212.54
20130.014834.7830.3633.15140.012038.8728.7825.22130.027714.1619.4613.75
filter<1,fast_multiblock32<K>,1>filter<1,fast_multiblock64<K>>filter<1,fast_multiblock64<K>,1>
cKFPR
[%]
ins.succ.
lkp.
uns.
lkp.
KFPR
[%]
ins.succ.
lkp.
uns.
lkp.
KFPR
[%]
ins.succ.
lkp.
uns.
lkp.
852.46253.674.184.2352.43885.066.175.9652.31985.125.825.86
1280.44283.866.115.1080.41905.788.727.1680.37477.777.710.82687.09 6.916.79
16 110.08666.948.878.600.046968.2479.4525.9460.403416.3213.9813.9770.288316.4216.0016.02
20140.0065102.36117.6931.0870.188720.8719.3319.2680.119420.7022.4822.55
filter<int,1,multiblock<uint64_t,K>>filter<int,1,multiblock<uint64_t,K>,1>filter<int,1,fast_multiblock32<K>>
cKFPR
[%]
ins.succ.
lkp.
uns.
lkp.
KFPR
[%]
ins.succ.
lkp.
uns.
lkp.
KFPR
[%]
ins.succ.
lkp.
uns.
lkp.
852.45106.104.694.6052.31578.575.054.9752.73613.172.402.31
1280.420710.188.798.5980.372414.859.569.2980.54154.814.924.34
16110.076426.7023.2423.54110.064229.6927.8527.90110.117914.9216.5011.60
20130.015036.0534.9335.15140.012239.8138.1638.14130.027516.3717.8912.37
filter<int,1,fast_multiblock32<K>,1>filter<int,1,fast_multiblock64<K>>filter<int,1,fast_multiblock64<K>,1>
cKFPR
[%]
ins.succ.
lkp.
uns.
lkp.
KFPR
[%]
ins.succ.
lkp.
uns.
lkp.
KFPR
[%]
ins.succ.
lkp.
uns.
lkp.
852.47883.182.262.1652.45464.083.483.4352.32344.173.403.33
1280.43945.215.564.7080.42106.736.135.6780.37547.656.645.69
16110.086515.0615.4510.99 11 0.078112.5511.109.4023.1718.4915.69 110.065112.3215.2315.450.064220.8219.1116.18
20 130.018012.2216.9614.460.017816.7417.8812.32 130.014718.5624.0218.810.016028.6626.9218.97 140.011223.0521.3714.280.011028.5826.8719.11
filter<int,1,block<uint64_t[8],K>>filter<int,1,block<uint64_t[8],K>,1>filter<int,1,multiblock<uint64_t[8],K>>
cKFPR
[%]
ins.succ.
lkp.
uns.
lkp.
KFPR
[%]
ins.succ.
lkp.
uns.
lkp.
KFPR
[%]
ins.succ.
lkp.
uns.
lkp.
852.32927.727.4311.9562.298610.689.7214.9572.338911.5210.0310.06
1270.414011.3712.0613.7170.384514.5213.9614.46100.346815.4514.3914.26
1690.085225.2625.5219.68100.071433.2832.5320.02110.049341.1840.2440.31
20120.019635.5834.6222.93120.015242.8741.6322.03150.007668.9964.7964.87
+++ \ No newline at end of file diff --git a/doc/bloom/configuration.adoc b/doc/bloom/configuration.adoc index d32761e..b87c431 100644 --- a/doc/bloom/configuration.adoc +++ b/doc/bloom/configuration.adoc @@ -16,7 +16,8 @@ The chart plots FPR vs. _c_ (capacity / number of elements inserted) for several as shown in the table below. +++ - +
+
@@ -26,69 +27,81 @@ as shown in the table below. - + - + - + - + - + - + + + + + - + + + + + - + - +
c = capacity / number of elements inserted14 15 16 17 18 19 20 21 22 23 24
filter<1,block<uint32_t,K>> 3 3 3 4 4 5 5 5 5 5filter<T,1,block<uint32_t,K>> 3 3 3 4 4 5 5 5 5 5 5 5 6 6 7 7 7 7 7 7 7
filter<1,block<uint32_t,K>,1> 2 3 4 4 4 4 5 5 5 6filter<T,1,block<uint32_t,K>,1> 2 3 4 4 4 4 5 5 5 6 6 6 6 6 6 6 7 7 7 7 7
filter<1,block<uint64_t,K>> 2 3 4 4 5 5 5 5 5 6filter<T,1,block<uint64_t,K>> 2 3 4 4 5 5 5 5 5 6 6 6 6 6 7 7 7 7 7 7 7
filter<1,block<uint64_t,K>,1> 2 3 4 4 4 5 6 6 6 7filter<T,1,block<uint64_t,K>,1> 2 3 4 4 4 5 6 6 6 7 7 7 7 7 8 8 8 8 8 9 9
filter<1,multiblock<uint32_t,K>> 3 3 4 5 6 6 8 8 8 8filter<T,1,multiblock<uint32_t,K>> 3 3 4 5 6 6 8 8 8 8 9 9 9 10 13 13 15 15 15 16 16
filter<1,multiblock<uint32_t,K>,1> 3 3 4 5 6 6 7 7 8 8filter<T,1,block<uint64_t[8],K>> 4 4 4 5 5 6 7 7 7 88 9 9 10 10 11 12 12 12 12 12
filter<T,1,multiblock<uint32_t,K>,1> 3 3 4 5 6 6 7 7 8 8 9 9 10 10 12 12 14 14 14 14 15
filter<1,multiblock<uint64_t,K>> 4 4 5 5 6 6 6 7 8 8filter<T,1,block<uint64_t[8],K>,1> 3 3 4 5 6 6 7 7 7 88 8 10 11 11 12 12 12 12 12 13
filter<T,1,multiblock<uint64_t,K>> 4 4 5 5 6 6 6 7 8 8 10 10 12 13 14 15 15 15 15 16 17
filter<1,multiblock<uint64_t,K>,1> 3 3 4 5 5 6 6 7 9 10filter<T,1,multiblock<uint64_t,K>,1> 3 3 4 5 5 6 6 7 9 10 10 11 11 12 12 13 13 13 15 16 16
filter<K> 3 4 4 5 5 6 6 8 8 9filter<T,K> 3 4 4 5 5 6 6 8 8 9 10 11 12 13 13 13 14 16 16 16 17
+ +++ Let's see how this can be used by way of an example. Suppose we plan to insert 10M elements -and want to keep the FPR at 10^−4^. The chart gives us five possibilities: +and want to keep the FPR at 10^−4^. The chart gives us five different +values of _c_ (the array capacity divided by the number of elements, in our case 10M): -* `filter` -> _c_ ≅ 19 bits per element -* `filter<1, multiblock, 1>` -> _c_ ≅ 20 bits per element -* `filter<1, multiblock>` -> _c_ ≅ 21 bits per element -* `filter<1, multiblock, 1>` -> _c_ ≅ 21.5 bits per element -* `filter<1, multiblock>` -> _c_ ≅ 23 bits per element +* `filter` -> _c_ ≅ 19 bits per element +* `filter, 1>` -> _c_ ≅ 20 bits per element +* `filter>` -> _c_ ≅ 21 bits per element +* `filter, 1>` -> _c_ ≅ 21 bits per element +* `filter, 1>` -> _c_ ≅ 21.5 bits per element +* `filter>` -> _c_ ≅ 22 bits per element +* `filter>` -> _c_ ≅ 23 bits per element These options have different tradeoffs in terms of space used and performance. If -we choose `filter<1, multiblock, 1>` as a compromise (or better yet, -`filter<1, fast_multiblock32, 1>`), the only remaining step is to consult the +we choose `filter, 1>` as a compromise (or better yet, +`filter, 1>`), the only remaining step is to consult the value of `K` in the table for _c_ = 21 or 22, and we get our final configuration: -[listing,subs="+macros,+quotes"] +[source,subs="+macros,+quotes"] ----- using my_filter=filter, 1>; ----- The resulting filter can be constructed in any of the following ways: -[listing,subs="+macros,+quotes"] +[source] ----- // 1) calculate the capacity from the value of c we got from the chart -my_filter pass:[f((]std::size_t)(10'000'000 * 21.5)); +my_filter f((std::size_t)(10'000'000 * 21.5)); // 2) let the library calculate the capacity from n and target fpr // expect some deviation from the capacity in 1) diff --git a/doc/bloom/fpr_estimation.adoc b/doc/bloom/fpr_estimation.adoc index 800d6c4..6541b32 100644 --- a/doc/bloom/fpr_estimation.adoc +++ b/doc/bloom/fpr_estimation.adoc @@ -6,57 +6,62 @@ For a classical Bloom filter, the theoretical false positive rate, under some simplifying assumptions, is given by -[.text-center] +[.formula-center] {small}stem:[\text{FPR}(n,m,k)=\left(1 - \left(1 - \displaystyle\frac{1}{m}\right)^{kn}\right)^k \approx \left(1 - e^{-kn/m}\right)^k]{small-end} for large {small}stem:[m]{small-end}, where {small}stem:[n]{small-end} is the number of elements inserted in the filter, {small}stem:[m]{small-end} its capacity in bits and {small}stem:[k]{small-end} the number of bits set per insertion (see a https://en.wikipedia.org/wiki/Bloom_filter#Probability_of_false_positives[derivation^] -of this formula). For a given inverse load factor {small}stem:[c=m/n]{small-end}, the optimum {small}stem:[k]{small-end} is -the integer closest to: +of this formula). For a fixed inverse load factor {small}stem:[c=m/n]{small-end}, +the expression reaches at -[.text-center] -{small}stem:[k_{\text{opt}}=c\cdot\ln2,]{small-end} +[.formula-center] +{small}stem:[k_{\text{opt}}=c\cdot\ln2]{small-end} -yielding a minimum attainable FPR of {small}stem:[1/2^{k_{\text{opt}}} \approx 0.6185^{c}]{small-end}. +its minimum value +{small}stem:[1/2^{k_{\text{opt}}} \approx 0.6185^{c}]{small-end}. +The optimum {small}stem:[k]{small-end}, which must be an integer, +is either +{small}stem:[\lfloor k_{\text{opt}}\rfloor]{small-end} or +{small}stem:[\lceil k_{\text{opt}}\rceil]{small-end}. In the case of filter of the form `boost::bloom::filter>`, we can extend the approach from https://citeseerx.ist.psu.edu/document?repid=rep1&type=pdf&doi=f376ff09a64b388bfcde2f5353e9ddb44033aac8[Putze et al.^] to derive the (approximate but very precise) formula: -[.text-center] +[.formula-center] {small}stem:[\text{FPR}_{\text{block}}(n,m,b,k,k')=\left(\displaystyle\sum_{i=0}^{\infty} \text{Pois}(i,nbk/m) \cdot \text{FPR}(i,b,k')\right)^{k},]{small-end} where -[.text-center] +[.formula-center] {small}stem:[\text{Pois}(i,\lambda)=\displaystyle\frac{\lambda^i e^{-\lambda}}{i!}]{small-end} is the probability mass function of a https://en.wikipedia.org/wiki/Poisson_distribution[Poisson distribution^] with mean {small}stem:[\lambda]{small-end}, and {small}stem:[b]{small-end} is the size of `Block` in bits. If we're using `multiblock`, we have -[.text-center] +[.formula-center] {small}stem:[\text{FPR}_\text{multiblock}(n,m,b,k,k')=\left(\displaystyle\sum_{i=0}^{\infty} \text{Pois}(i,nbkk'/m) \cdot \text{FPR}(i,b,1)^{k'}\right)^{k}.]{small-end} As we have commented xref:primer_multiblock_filters[before], in general -[.text-center] +[.formula-center] {small}stem:[\text{FPR}_\text{block}(n,m,b,k,k') \geq \text{FPR}_\text{multiblock}(n,m,b,k,k') \geq \text{FPR}(n,m,kk'),]{small-end} that is, block and multiblock filters have worse FPR than the classical filter for the same number of bits set per insertion, but they will be faster. We have the particular case -[.text-center] +[.formula-center] {small}stem:[\text{FPR}_{\text{block}}(n,m,b,k,1)=\text{FPR}_{\text{multiblock}}(n,m,b,k,1)=\text{FPR}(n,m,k),]{small-end} which follows simply from the observation that using `{block|multiblock}` behaves exactly as a classical Bloom filter. We don't know of any closed, simple formula for the FPR of block and multiblock filters when -`Bucketsize` is not its "natural" size `xref:subfilters_used_value_size[_used-value-size_]`, +`Stride` is not its "natural" size `xref:subfilters_used_value_size[_used-value-size_]`, that is, when subfilter subarrays overlap. -We can use the following approximations ({small}stem:[s]{small-end} = `BucketSize` in bits): +We can use the following approximations ({small}stem:[s]{small-end} = `Stride` in bits): -[.text-center] +[.formula-center] {small}stem:[\text{FPR}_{\text{block}}(n,m,b,s,k,k')=\left(\displaystyle\sum_{i=0}^{\infty} \text{Pois}\left(i,\frac{n(2b-s)k}{m}\right) \cdot \text{FPR}(i,2b-s,k')\right)^{k},]{small-end} + {small}stem:[\text{FPR}_\text{multiblock}(n,m,b,s,k,k')=\left(\displaystyle\sum_{i=0}^{\infty} \text{Pois}\left(i,\frac{n(2bk'-s)k}{m}\right) \cdot \text{FPR}\left(i,\frac{2bk'-s}{k'},1\right)^{k'}\right)^{k},]{small-end} diff --git a/doc/bloom/future_work.adoc b/doc/bloom/future_work.adoc new file mode 100644 index 0000000..69bdc07 --- /dev/null +++ b/doc/bloom/future_work.adoc @@ -0,0 +1,71 @@ +[#future_work] += Future Work + +:idprefix: future_work_ + +A number of features asked by reviewers and users of Boost.Bloom are +considered for inclusion into future versions of the library. + +== Bulk operations + +Each insertion/lookup operation for `boost::bloom::filter` likely involves one or more +cache misses in the access to the internal bit array. Following a similar +approach to that of +https://bannalia.blogspot.com/2023/10/bulk-visitation-in-boostconcurrentflatm.html[bulk visitation^] +in Boost.Unordered, we can pipeline several operations so that +cache miss stalls are leveraged to do useful computation. The interface +for this functionality could be as follows: + +[source] +----- +f.insert(first1, last1); +f.may_contain(first2, last2, [] (const value_type& x, bool res) { + // x is (likely) in the filter if res == true +}); +----- + +== `try_insert` + +To avoid inserting an already present element, we now have to do: + +[source] +----- +if(!f.may_contain(x)) f.insert(x); +----- + +These two calls can be combined in a potentially faster, +single operation: + +[source] +----- +bool res = f.try_insert(x); // returns true if x was not present +----- + +== Estimation of number of elements inserted + +For a classical Bloom filter, the number of elements actually inserted +can be estimated from the number {small}stem:[B]{small-end} of bits set +to one in the array as + +[.formula-center] +{small}stem:[n\approx-\displaystyle\frac{m}{k}\ln\left(1-\displaystyle\frac{B}{m}\right),]{small-end} + +which can be used for the implementation of a member function +`estimated_size`. As of this writing, we don't know how to extend the +formula to the case of block and multiblock filters. Any help on this +problem is much appreciated. + +== Run-time specification of _k_ + +Currently, the number _k_ of bits set per operation is configured at compile time. +A variation of (or extension to) `boost::bloom::filter` can be provided +where the value of _k_ is specified at run-time, the tradeoff being that +its performance will be worse than the static case (preliminary experiments +show an increase in execution time of around 10-20%). + +== Alternative filters + +We can consider adding additional data structures such as +https://en.wikipedia.org/wiki/Cuckoo_filter[cuckoo^] and +https://arxiv.org/pdf/1912.08258[xor^] filters, which are more +space efficient and potentially faster. \ No newline at end of file diff --git a/doc/bloom/implementation_notes.adoc b/doc/bloom/implementation_notes.adoc index 9f27db4..467a0e9 100644 --- a/doc/bloom/implementation_notes.adoc +++ b/doc/bloom/implementation_notes.adoc @@ -8,7 +8,7 @@ This is the bit-mixing post-process we use to improve the statistical properties of the hash function when it doesn't have the avalanching property: -[.text-center] +[.formula-center] {small}stem:[m\leftarrow\text{mul}(h,C)]{small-end}, + {small}stem:[h'\leftarrow\text{high}(m)\text{ xor }\text{low}(m)]{small-end}, @@ -37,7 +37,7 @@ show how to relax this requirement down to two different hash functions {small}stem:[h_1(x)]{small-end} and {small}stem:[h_2(x)]{small-end} linearly combined as -[.text-center] +[.formula-center] {small}stem:[g_i(x)=h_1(x)+ih_2(x).]{small-end} Without formal justification, we have relaxed this even further to just one @@ -47,32 +47,35 @@ by means of very cheap mixing schemes. In what follows {small}stem:[k]{small-end}, {small}stem:[k']{small-end} are the homonym values in a filter of the form `boost::bloom::filter>`, {small}stem:[b]{small-end} is `sizeof(Block) * CHAR_BIT`, -and {small}stem:[r]{small-end} is the number of buckets in the filter. +and {small}stem:[r]{small-end} is the number of subarrays in the filter. -=== Bucket Location +=== Subarray Location To produce a location (i.e. a number {small}stem:[p]{small-end} in {small}stem:[[0,r)]{small-end}) from {small}stem:[h_{i-1}]{small-end}, instead of the straightforward but costly procedure {small}stem:[p\leftarrow h_{i-1}\bmod r]{small-end} we resort to -Lemire's https://arxiv.org/pdf/1805.10941[fastrange technique^]. Moreover, -we combine this calculation with the production of {small}stem:[h_{i}]{small-end} -from {small}stem:[h_{i-1}]{small-end} as follows: +Lemire's https://arxiv.org/pdf/1805.10941[fastrange technique^]: -[.text-center] +[.formula-center] {small}stem:[m\leftarrow\text{mul}(h_{i-1},r),]{small-end} + -{small}stem:[p\leftarrow\lfloor m/2^{64} \rfloor=\text{high}(m),]{small-end} + -{small}stem:[h_i\leftarrow m \bmod 2^{64}=\text{low}(m).]{small-end} +{small}stem:[p\leftarrow\lfloor m/2^{64} \rfloor=\text{high}(m).]{small-end} +To decorrelate {small}stem:[p]{small-end} from further uses of the hash value, +we produce {small}stem:[h_{i}]{small-end} from {small}stem:[h_{i-1}]{small-end} as + +[.formula-center] +{small}stem:[h_i\leftarrow c \cdot h_{i-1} \bmod 2^{64}=\text{low}(c \cdot h_{i-1}),]{small-end} + +with {small}stem:[c=\text{0xf1357aea2e62a9c5}]{small-end} (64-bit mode), +{small}stem:[c=\text{0xe817fb2d}]{small-end} (32-bit mode) obtained +from https://arxiv.org/pdf/2001.05304[Steele and Vigna^]. The transformation {small}stem:[h_{i-1} \rightarrow h_i]{small-end} is a simple https://en.wikipedia.org/wiki/Linear_congruential_generator[multiplicative congruential generator^] over {small}stem:[2^{64}]{small-end}. For this MCG to produce long -cycles, {small}stem:[h_0]{small-end} must be odd and the multiplicative constant -{small}stem:[r]{small-end} must be {small}stem:[\equiv \pm 3 \text{ (mod 8)}]{small-end}: -to meet these requirements, the implementation adjusts {small}stem:[h_0]{small-end} -to {small}stem:[h_0']{small-end} and {small}stem:[r]{small-end} -to {small}stem:[r']{small-end}. This renders the least significant bit -of {small}stem:[h_i]{small-end} unsuitable for pseudorandomization -(it is always one). +cycles {small}stem:[h_0]{small-end} must be odd, so the implementation adjusts +{small}stem:[h_0]{small-end} to {small}stem:[h_0'= (h_0\text{ or }1)]{small-end}, +which renders the least significant bit of {small}stem:[h_i]{small-end} +unsuitable for pseudorandomization (it is always one). === Bit selection @@ -109,14 +112,14 @@ In the case of SSE2, we don't have the 128-bit equivalent of `+++_+++mm256_sllv_epi32`, so we use the following, mildly interesting technique: a `+++__+++m128i` of the form -[.text-center] +[.formula-center] {small}stem:[((x_0+127)\cdot 2^{23},(x_1+127)\cdot 2^{23},(x_2+127)\cdot 2^{23},(x_3+127)\cdot 2^{23}),]{small-end} where each {small}stem:[x_i]{small-end} is in {small}stem:[[0,32)]{small-end}, can be `reinterpret_cast`+++ed+++ to (i.e., has the same binary representation as) the `+++__+++m128` (register of `float`+++s+++) -[.text-center] +[.formula-center] {small}stem:[(2^{x_0},2^{x_1},2^{x_2},2^{x_3}),]{small-end} from which our desired `+++__+++m128i` of shifted 1s can be obtained diff --git a/doc/bloom/intro.adoc b/doc/bloom/intro.adoc index 659c224..48aad10 100644 --- a/doc/bloom/intro.adoc +++ b/doc/bloom/intro.adoc @@ -8,10 +8,11 @@ that can be configured to implement a classical Bloom filter as well as variations discussed in the literature such as block filters, multiblock filters, and more. -[listing,subs="+macros,+quotes"] +[source,subs="+macros,+quotes"] ----- -#include +#include #include +#include #include int main() @@ -25,7 +26,6 @@ int main() // insert elements (they can't be erased, Bloom filters are insert-only) f.insert("hello"); f.insert("Boost"); - //... // elements inserted are always correctly checked as such assert(f.may_contain("hello") == true); @@ -35,7 +35,10 @@ int main() // the number of bits set per element and generally how the boost::bloom::filter // was specified if(f.may_contain("bye")) { // likely false - //... + std::cout << "false positive\n"; + } + else { + std::cout << "everything worked as expected\n"; } } ----- @@ -46,4 +49,20 @@ Boost.Bloom has been implemented with a focus on performance; SIMD technologies such as AVX2, Neon and SSE2 can be leveraged to speed up operations. -Boost.Bloom is a header-only library. C++11 or later required. \ No newline at end of file +== Getting Started + +Consult the website +https://www.boost.org/doc/user-guide/getting-started.html[section^] +on how to install the entire Boost project or only Boost.Bloom +and its dependencies. + +Boost.Bloom is a header-only library, so no additional build phase is +needed. C++11 or later required. The library has been verified to +work with GCC 4.8, Clang 3.9 and Visual Studio 2015 (and later versions +of those). You can check that your environment is correctly set up +by compiling the +link:../../example/basic.cpp[example program] shown above. + +If you are not familiar with Bloom filters in general, see the +xref:primer[primer]; otherwise, you can jump directly to the +xref:tutorial[tutorial]. \ No newline at end of file diff --git a/doc/bloom/primer.adoc b/doc/bloom/primer.adoc index 89d69a7..4c069fb 100644 --- a/doc/bloom/primer.adoc +++ b/doc/bloom/primer.adoc @@ -3,12 +3,20 @@ :idprefix: primer_ -A Bloom filter is a probabilistic data structure where inserted elements can be looked up -with 100% accuracy, whereas looking up for a non-inserted element may fail with -some probability called the filter's _false positive rate_ or FPR. The tradeoff here is -that Bloom filters occupy much less space than traditional non-probabilistic containers -(typically, around 8-20 bits per element) for an acceptably low FPR. The greater -the filter's _capacity_ (its size in bits), the lower the resulting FPR. +A Bloom filter (named after its inventor Burton Howard Bloom) is a probabilistic data +structure where inserted elements can be looked up with 100% accuracy, whereas looking +up for a non-inserted element may fail with some probability called the filter's +_false positive rate_ or FPR. The tradeoff here is that Bloom filters occupy much less +space than traditional non-probabilistic containers (typically, around 8-20 bits per +element) for an acceptably low FPR. The greater the filter's _capacity_ (its size in bits), +the lower the resulting FPR. + +In general, Bloom filters are useful to prevent/mitigate queries against large data sets +when exact retrieval is costly and/or can't be made in main memory. + +[.boxed] +==== +*Example: Speeding up unsuccessful requests to a database* One prime application of Bloom filters and similar data structures is for the prevention of expensive disk/network accesses when these would fail to retrieve a given piece of @@ -18,7 +26,7 @@ For instance, suppose we are developing a frontend for a database with access ti Inserting a Bloom filter with a lookup time of 200 ns and a FPR of 0.5% will reduce the average response time of the system from 10 ms to -[.text-center] +[.formula-center] (10 + 0.0002) × 50.25% + 0.0002 × 49.75% ≅ 5.03 ms, that is, we get a ×1.99 overall speedup. If the database holds 1 billion records, @@ -27,8 +35,8 @@ which is perfectly realizable. image::db_speedup.png[align=center, title="Improving DB negative access time with a Bloom filter."] -In general, Bloom filters are useful to prevent/mitigate queries against large data sets -when exact retrieval is costly and/or can't be made in main memory. +==== + Applications have been described in the areas of web caching, dictionary compression, network routing and genomics, among others. https://www.eecs.harvard.edu/~michaelm/postscripts/im2005b.pdf[Broder and Mitzenmacher^] @@ -36,17 +44,17 @@ provide a rather extensive review of use cases with a focus on networking. == Implementation -The implementation of a Bloom filter consists of an array of _m_ bits, initially set to zero. +The implementation of a classical Bloom filter consists of an array of _m_ bits, initially set to zero. Inserting an element _x_ reduces to selecting _k_ positions pseudorandomly (with the help of _k_ independent hash functions) and setting them to one. -image::bloom_insertion.png[align=center, title="Insertion in a classical Bloom filter, _k_ = 6."] +image::bloom_insertion.png[align=center, title="Insertion in a classical Bloom filter with _k_ = 6 different hash functions. Inserting _x_ reduces to setting to one the bits at positions 10, 14, 43, 58, 1, and 39 as indicated by _h_~1~(_x_), ... , _h_~6~(_x_)."] To check if an element _y_ is in the filter, we follow the same procedure and see if the selected bits are all set to one. In the example figure there are two unset bits, which definitely indicates _y_ was not inserted in the filter. -image::bloom_lookup.png[align=center, title="Lookup in a classical Bloom filter."] +image::bloom_lookup.png[align=center, title="Lookup in a classical Bloom filter. Value _y_ is not in the filter because bits at positions 20 and 61 are not set to one."] A false positive occurs when the bits checked happen to be all set to one due to other, unrelated insertions. The probability of having a false positive increases as we @@ -57,19 +65,19 @@ when the array is sparsely populated, a higher value of _k_ improves (decreases) as there are more chances that we hit a non-set bit; however, if _k_ is very high the array will have more and more bits set to one as new elements are inserted, which eventually will reach a point where we lose out to a filter with a lower _k_ and -thus a smaller proportions of set bits. +thus a smaller proportions of set bits. For given values of _n_ and _m_, the optimum _k_ is +{small}stem:[\lfloor k_{\text{opt}}\rfloor]{small-end} or +{small}stem:[\lceil k_{\text{opt}}\rceil]{small-end}, with -image::fpr_n_k.png[align=center, title="FPR vs. number of inserted elements for two filters with _m_ = 10^5^ bits."] +[.formula-center] +{small}stem:[k_{\text{opt}}=\displaystyle\frac{m\cdot\ln2}{n},]{small-end} -For given values of _n_ and _m_, the optimum _k_ is the integer closest to - -[.text-center] -{small}stem:[k_{\text{opt}}=\displaystyle\frac{m\cdot\ln2}{n}]{small-end} - -for a minimum FPR of +for a minimum FPR close to {small}stem:[1/2^{k_{\text{opt}}} \approx 0.6185^{m/n}]{small-end}. See the appendix on xref:fpr_estimation[FPR estimation] for more details. +image::fpr_n_k.png[align=center, title="FPR vs. number of inserted elements for two filters with _m_ = 10^5^ bits. _k_ = 6 (red) has a better (lower) FPR than _k_ = 2 (blue) for small values of _n_, but eventually degrades more as _n_ grows. The dotted line shows the minimum attainable FPR resulting from selecting the optimum value of _k_ for each value of _n_."] + == Variations on the Classical Filter === Block Filters @@ -82,21 +90,21 @@ setting/checking in a small block of _b_ bits pseudorandomly selected from the entire array. If the block is small enough, it will fit in a CPU cacheline, thus drastically reducing the number of cache misses. -image::block_insertion.png[align=center, title="Block filter."] +image::block_insertion.png[align=center, title="Block filter. A block of _b_ bits is selected based on _h_~0~(x), and all subsequent bit setting is constrained there."] The downside is that the resulting FPR is worse than that of a classical filter for the same values of _n_, _m_ and _k_. Intuitively, block filters reduce the uniformity of the distribution of bits in the array, which ultimately hurts their probabilistic performance. -image::fpr_n_k_bk.png[align=center, title="FPR (logarithmic scale) vs. number of inserted elements for a classical and a block filter, _m_ = 10^5^ bits."] +image::fpr_n_k_bk.png[align=center, title="FPR (logarithmic scale) vs. number of inserted elements for a classical and a block filter with the same _k_ = 4, _m_ = 10^5^ bits."] A further variation in this idea is to have operations select _k_ blocks with _k'_ bits set on each. This, again, will have a worse FPR than a classical filter with _k·k'_ bits per operation, but improves on a plain _k·k'_ block filter. -image::block_multi_insertion.png[align=center, title="Block filter with multi-insertion."] +image::block_multi_insertion.png[align=center, title="Block filter with multi-insertion. _k_ = 2 blocks are selected, and _k_' = 3 bits are set in each."] === Multiblock Filters @@ -106,7 +114,7 @@ so that each block takes exactly one bit. This still maintains a good cache locality but improves FPR with respect to block filters because bits set to one are more spread out across the array. -image::multiblock_insertion.png[align=center, title="Multiblock filter."] +image::multiblock_insertion.png[align=center, title="Multiblock filter. A range of _k_' = 4 consecutive blocks is selected based on _h_~0~(x), and a bit is set to one in each of the blocks."] Multiblock filters can also be combined with multi-insertion. In general, for the same number of bits per operation and equal values of _n_ and _m_, diff --git a/doc/bloom/reference.adoc b/doc/bloom/reference.adoc index 563ab7d..632f17c 100644 --- a/doc/bloom/reference.adoc +++ b/doc/bloom/reference.adoc @@ -1,6 +1,7 @@ [#reference] = Reference +include::reference/header_bloom.adoc[] include::reference/header_filter.adoc[] include::reference/filter.adoc[] include::reference/subfilters.adoc[] diff --git a/doc/bloom/reference/block.adoc b/doc/bloom/reference/block.adoc index ffe7bab..606e5a0 100644 --- a/doc/bloom/reference/block.adoc +++ b/doc/bloom/reference/block.adoc @@ -34,9 +34,11 @@ struct block |=== |`Block` -|An unsigned integral type. +|An unsigned integral type or an array of 2^`N`^ elements of unsigned integral type. |`K` | Number of bits set/checked per operation. Must be greater than zero. |=== + +''' \ No newline at end of file diff --git a/doc/bloom/reference/fast_multiblock32.adoc b/doc/bloom/reference/fast_multiblock32.adoc index 63f7b5a..923be57 100644 --- a/doc/bloom/reference/fast_multiblock32.adoc +++ b/doc/bloom/reference/fast_multiblock32.adoc @@ -50,3 +50,5 @@ The non-SIMD case falls back to regular `multiblock`. `xref:subfilters_used_value_size[_used-value-size_]>` is `4 * K`. + +''' \ No newline at end of file diff --git a/doc/bloom/reference/filter.adoc b/doc/bloom/reference/filter.adoc index 51b05c3..66e8b5d 100644 --- a/doc/bloom/reference/filter.adoc +++ b/doc/bloom/reference/filter.adoc @@ -34,8 +34,9 @@ namespace bloom{ template< typename T, std::size_t K, - typename Subfilter = block, std::size_t BucketSize = 0, - typename Hash = boost::hash, typename Allocator = std::allocator + typename Subfilter = block, std::size_t Stride = 0, + typename Hash = boost::hash, + typename Allocator = std::allocator > class filter { @@ -44,7 +45,7 @@ public: using value_type = T; static constexpr std::size_t k = K; using subfilter = Subfilter; - static constexpr std::size_t xref:filter_bucket_size[bucket_size] = xref:filter_bucket_size[__see below__]; + static constexpr std::size_t xref:filter_stride[stride] = xref:filter_stride[__see below__]; using hasher = Hash; using allocator_type = Allocator; using size_type = std::size_t; @@ -120,8 +121,6 @@ public: boost::span xref:#filter_array[array]() const noexcept; // modifiers - template - void xref:#filter_emplace[emplace](Args&&... args); void xref:#filter_insert[insert](const value_type& x); template void xref:#filter_insert[insert](const U& x); @@ -172,39 +171,52 @@ bit setting/checking into the filter's internal array. The subfilter is invoked per operation on `K` pseudorandomly selected portions of the array (_subarrays_) of width `xref:subfilters_used_value_size[_used-value-size_]`. -|`BucketSize` +|`Stride` | Distance in bytes between the initial positions of consecutive subarrays. -If `BucketSize` is specified as zero, the actual distance is automatically selected to +If `Stride` is specified as zero, the actual distance is automatically selected to `_used-value-size_` (non-overlapping subarrays). -Otherwise, `BucketSize` must be not greater than `_used-value-size_`. +Otherwise, `Stride` must be not greater than `_used-value-size_`. |`Hash` |A https://en.cppreference.com/w/cpp/named_req/Hash[Hash^] type over `T`. |`Allocator` -|An https://en.cppreference.com/w/cpp/named_req/Allocator[Allocator^] whose value type is `T`. +|An https://en.cppreference.com/w/cpp/named_req/Allocator[Allocator^] whose value type is +`unsigned char`. |=== Allocation and deallocation of the internal array is done through an internal copy of the -provided allocator. `value_type` construction/destruction (which only happens in -`xref:filter_emplace[emplace]`) uses -`std::allocator_traits::construct`/`destroy`. +provided allocator. If `xref:filter_stride[stride]` is a +multiple of _a_ = `alignof(Subfilter::value_type)`, the array is byte-aligned to +max(64, _a_). -If `link:../../../unordered/doc/html/unordered/reference/hash_traits.html#hash_traits_hash_is_avalanching[boost::unordered::hash_is_avalanching]::value` +If `link:../../../container_hash/doc/html/hash.html#ref_hash_is_avalanchinghash[boost::hash_is_avalanching]::value` is `true` and `sizeof(std::size_t) >= 8`, the hash function is used as-is; otherwise, a bit-mixing post-processing stage is added to increase the quality of hashing at the expense of extra computational cost. +*Exception Safety Guarantees* + +Except when explicitly noted, all non-const member functions and associated functions taking +`boost::bloom::filter` by non-const reference provide the +https://en.cppreference.com/w/cpp/language/exceptions#Exception_safety[basic exception guarantee^], +whereas all const member functions and associated functions taking +`boost::bloom::filter` by const reference provide the +https://en.cppreference.com/w/cpp/language/exceptions#Exception_safety[strong exception guarantee^]. + +Except when explicitly noted, no operation throws an exception unless that exception +is thrown by the filter's `Hash` or `Allocator` object (if any). + === Types and Constants -[[filter_bucket_size]] +[[filter_stride]] [listing,subs="+macros,+quotes"] ---- -static constexpr std::size_t bucket_size; +static constexpr std::size_t stride; ---- -Equal to `BucketSize` if that parameter was specified as distinct from zero. +Equal to `Stride` if that parameter was specified as distinct from zero. Otherwise, equal to `xref:subfilters_used_value_size[_used-value-size_]`. === Constructors @@ -236,6 +248,7 @@ filter( Constructs an empty filter using copies of `h` and `al` as the hash function and allocator, respectively. [horizontal] +Preconditions:;; `fpr` is between 0.0 and 1.0. Postconditions:;; `capacity() == 0` if `m == 0`, `capacity() >= m` otherwise (first overload). + `capacity() == capacity_for(n, fpr)` (second overload). @@ -259,7 +272,8 @@ and inserts the values from `[first, last)` into it. [horizontal] Preconditions:;; `InputIterator` is a https://en.cppreference.com/w/cpp/named_req/InputIterator[LegacyInputIterator^] referring to `value_type`. + -`[first, last)` is a valid range. +`[first, last)` is a valid range. + +`fpr` is between 0.0 and 1.0. Postconditions:;; `capacity() == 0` if `m == 0`, `capacity() >= m` otherwise (first overload). + `capacity() == capacity_for(n, fpr)` (second overload). + `may_contain(x)` for all values `x` from `[first, last)`. @@ -366,7 +380,6 @@ filter( Equivalent to `xref:#filter_iterator_range_constructor[filter](il.begin(), il.end(), m, h, al)` (first overload) or `xref:#filter_iterator_range_constructor[filter](il.begin(), il.end(), n, fpr, h, al)` (second overload). - ==== Capacity Constructor with Allocator [listing,subs="+macros,+quotes"] @@ -378,7 +391,6 @@ filter(size_type n, double fpr, const allocator_type& al); Equivalent to `xref:#filter_capacity_constructor[filter](m, hasher(), al)` (first overload) or `xref:#filter_capacity_constructor[filter](n, fpr, hasher(), al)` (second overload). - ==== Initializer List Constructor with Allocator [listing,subs="+macros,+quotes"] @@ -425,6 +437,7 @@ Preconditions:;; If `pocca`, `hasher` is nothrow https://en.cppreference.com/w/cpp/named_req/Swappable[Swappable^]. Postconditions:;; `*this == x`. Returns:;; `*this`. +Exception Safety:;; Strong. ==== Move Assignment @@ -449,6 +462,7 @@ Preconditions:;; If `pocma`, `hasher` is nothrow https://en.cppreference.com/w/cpp/named_req/Swappable[Swappable^]. Postconditions:;; `x.capacity() == 0`. Returns:;; `*this`. +Exception Safety:;; Nothrow as indicated, otherwise strong. ==== Initializer List Assignment @@ -520,20 +534,6 @@ Returns:;; A span over the internal array. === Modifiers -==== Emplace - -[listing,subs="+macros,+quotes"] ----- -template void emplace(Args&&... args); ----- - -Inserts an element constructed from `std::forward(args)+++...+++`. - -[horizontal] -Preconditions:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/EmplaceConstructible[EmplaceConstructible^] -into `filter` from `std::forward(args)+++...+++`. + -`value_type` is https://en.cppreference.com/w/cpp/named_req/Erasable[Erasable^] from `filter`. - ==== Insert [listing,subs="+macros,+quotes"] @@ -548,6 +548,7 @@ bits of the internal array deterministically selected from the value [horizontal] Postconditions:;; `may_contain(x)`. +Exception Safety:;; Strong. Notes:;; The second overload only participates in overload resolution if `hasher::is_transparent` is a valid member typedef. @@ -591,7 +592,7 @@ If `pocs`, swaps the internal allocator with that of `x`. Preconditions:;; `pocs || get_allocator() == x.get_allocator()`. + If `pocs`, `Allocator` is nothrow https://en.cppreference.com/w/cpp/named_req/Swappable[Swappable^]. + `hasher` is nothrow https://en.cppreference.com/w/cpp/named_req/Swappable[Swappable^]. - +Exception Safety:;; Nothrow. ==== Clear @@ -615,8 +616,10 @@ equal to `capacity()`, and clears the filter. + Second overload: Equivalent to `reset(capacity_for(n, fpr))`. [horizontal] +Preconditions:;; `fpr` is between 0.0 and 1.0. Postconditions:;; In general, `capacity() >= m`. + If `m == 0` or `m == capacity()` or `m == capacity_for(n, fpr)` for some `n` and `fpr`, then `capacity() == m`. +Exception Safety:;; If `m == 0` or `capacity_for(n, fpr) == 0`, nothrow, otherwise strong. ==== Combine with AND @@ -630,7 +633,9 @@ otherwise, changes the value of each bit in the internal array with the result o doing a logical AND operation of that bit and the corresponding one in `x`. [horizontal] +Preconditions:;; The `Hash` objects of `x` and `y` are equivalent. Returns:;; `*this`; +Exception Safety:;; Strong. ==== Combine with OR @@ -644,7 +649,9 @@ otherwise, changes the value of each bit in the internal array with the result o doing a logical OR operation of that bit and the corresponding one in `x`. [horizontal] +Preconditions:;; The `Hash` objects of `x` and `y` are equivalent. Returns:;; `*this`; +Exception Safety:;; Strong. === Observers @@ -698,6 +705,7 @@ bool operator==( ---- [horizontal] +Preconditions:;; The `Hash` objects of `x` and `y` are equivalent. Returns:;; `true` iff `x.capacity() == y.capacity()` and `x`++'++s and `y`++'++s internal arrays are bitwise identical. @@ -713,6 +721,7 @@ bool operator!=( ---- [horizontal] +Preconditions:;; The `Hash` objects of `x` and `y` are equivalent. Returns:;; `!(x xref:filter_operator[==] y)`. @@ -728,3 +737,5 @@ void swap(filter& x, filter& y) ---- Equivalent to `x.xref:filter_swap[swap](y)`. + +''' \ No newline at end of file diff --git a/doc/bloom/reference/header_bloom.adoc b/doc/bloom/reference/header_bloom.adoc new file mode 100644 index 0000000..0589c39 --- /dev/null +++ b/doc/bloom/reference/header_bloom.adoc @@ -0,0 +1,9 @@ +[#header_bloom] +== `` + +:idprefix: header_bloom_ + +Convenience header including all the other headers listed in this +reference. + +''' \ No newline at end of file diff --git a/doc/bloom/reference/header_filter.adoc b/doc/bloom/reference/header_filter.adoc index 2a1b793..1cf24e6 100644 --- a/doc/bloom/reference/header_filter.adoc +++ b/doc/bloom/reference/header_filter.adoc @@ -13,27 +13,28 @@ namespace bloom{ template< typename T, std::size_t K, - typename Subfilter = block, std::size_t BucketSize = 0, - typename Hash = boost::hash, typename Allocator = std::allocator + typename Subfilter = block, std::size_t Stride = 0, + typename Hash = boost::hash, + typename Allocator = std::allocator > class xref:filter[filter]; template< - typename T, std::size_t K, typename S, std::size_t B, typename H, typename A + typename T, std::size_t K, typename SF, std::size_t S, typename H, typename A > bool xref:filter_operator[operator+++==+++]( - const filter& x, const filter& y); + const filter& x, const filter& y); template< - typename T, std::size_t K, typename S, std::size_t B, typename H, typename A + typename T, std::size_t K, typename SF, std::size_t S, typename H, typename A > bool xref:filter_operator_2[operator!=]( - const filter& x, const filter& y); + const filter& x, const filter& y); template< - typename T, std::size_t K, typename S, std::size_t B, typename H, typename A + typename T, std::size_t K, typename SF, std::size_t S, typename H, typename A > -void xref:filter_swap_2[swap](filter& x, filter& y) +void xref:filter_swap_2[swap](filter& x, filter& y) noexcept(noexcept(x.swap(y))); } // namespace bloom diff --git a/doc/bloom/reference/multiblock.adoc b/doc/bloom/reference/multiblock.adoc index cddd1d4..7ce7e3c 100644 --- a/doc/bloom/reference/multiblock.adoc +++ b/doc/bloom/reference/multiblock.adoc @@ -34,7 +34,7 @@ struct multiblock |=== |`Block` -|An unsigned integral type. +|An unsigned integral type or an array of 2^`N`^ elements of unsigned integral type. |`K` | Number of bits set/checked per operation. Must be greater than zero. @@ -43,3 +43,5 @@ struct multiblock Each of the `K` bits set/checked is located in a different element of the `Block[K]` array. + +''' \ No newline at end of file diff --git a/doc/bloom/reference/subfilters.adoc b/doc/bloom/reference/subfilters.adoc index 19662df..4db6bd9 100644 --- a/doc/bloom/reference/subfilters.adoc +++ b/doc/bloom/reference/subfilters.adoc @@ -55,3 +55,5 @@ constexpr std::size_t _used-value-size_; // exposition only constant exists, or `sizeof(Subfilter::value_type)` otherwise. The value is the effective size in bytes of the subarrays upon which a given subfilter operates. + +''' \ No newline at end of file diff --git a/doc/bloom/release_notes.adoc b/doc/bloom/release_notes.adoc index 7f5d630..a6ef3b1 100644 --- a/doc/bloom/release_notes.adoc +++ b/doc/bloom/release_notes.adoc @@ -3,7 +3,7 @@ :idprefix: release_notes_ -== Boost 1.xx +== Boost 1.89 * Initial release. diff --git a/doc/bloom/tutorial.adoc b/doc/bloom/tutorial.adoc index 4f5b942..59a78e5 100644 --- a/doc/bloom/tutorial.adoc +++ b/doc/bloom/tutorial.adoc @@ -3,81 +3,127 @@ :idprefix: tutorial_ -== Filter Definition - -A `boost::bloom::filter` can be regarded as a bit array divided into _buckets_ that +A `boost::bloom::filter` can be regarded as a bit array divided into _subarrays_ that are selected pseudo-randomly (based on a hash function) upon insertion: -each of the buckets is passed to a _subfilter_ that marks several of its bits according +each of the subarrays is passed to a _subfilter_ that marks several of its bits according to some associated strategy. +Note that although `boost::bloom::filter` mimics the interface of a container +and provides operations such as `insert`, it is actually _not_ a +container: for instance, insertion does not involve the actual storage +of the element in the data stucture, but merely sets some bits in the internal +array based on the hash value of the element. + +== Filter Definition + [listing,subs="+macros,+quotes"] ----- template< typename T, std::size_t K, - typename Subfilter = block, std::size_t BucketSize = 0, - typename Hash = boost::hash, typename Allocator = std::allocator + typename Subfilter = block, std::size_t Stride = 0, + typename Hash = boost::hash, + typename Allocator = std::allocator > class filter; ----- * `T`: Type of the elements inserted. -* `K`: Number of buckets marked per insertion. +* `K`: Number of subarrays marked per insertion. * `xref:tutorial_subfilter[Subfilter]`: Type of subfilter used. -* `xref:tutorial_bucketsize[BucketSize`]: Size in bytes of the buckets. +* `xref:tutorial_stride[Stride`]: Distance in bytes between the initial positions of consecutive subarrays. * `xref:tutorial_hash[Hash]`: A hash function for `T`. -* `Allocator`: An allocator for `T`. +* `Allocator`: An allocator for `unsigned char`. === `Subfilter` -The following subfilters can be selected, offering different compromises -between performance and _false positive rate_ (FPR). -See the xref:primer_variations_on_the_classical_filter[Bloom Filter Primer] -for a general explanation of block and multiblock filters. +A subfilter defines the local strategy for setting or checking bits within +a selected subarray of the bit array. It determines how many bits are +modified per operation, how they are arranged in memory, and how memory is accessed. +The following subfilters are provided: -`block` +++++ +
+++++ +[options="header"] +|=== +| Subfilter | Description | Pros | Cons -[.indent] -Sets `K'` bits in an underlying value of the unsigned integral type `Block` -(e.g. `unsigned char`, `uint32_t`, `uint64_t`). So, -a `filter>` will set `K * K'` bits per element. -The tradeoff here is that insertion/lookup will be (much) faster than -with `filter` while the FPR will be worse (larger). -FPR is better the wider `Block` is. +| `block` +| Sets `K'` bits in a subarray of type `Block` +| Very fast access time +| FPR is worse (higher) the smaller `Block` is -`multiblock` +| `multiblock` +| Sets one bit in each of the elements of a `Block[K']` subarray +| Better (lower) FPR than `block` for the same `Block` type +| Performance may worsen if cacheline boundaries are crossed when accessing the subarray -[.indent] -Instead of setting `K'` bits in a `Block` value, this subfilter sets -one bit on each of the elements of a `Block[K']` subarray. This improves FPR -but impacts performance with respect to `block`, among other -things because cacheline boundaries can be crossed when accessing the subarray. +| `fast_multiblock32` +| Statistically equivalent to `multiblock`, but uses +faster SIMD-based algorithms when SSE2, AVX2 or Neon are enabled at +compile time +| Always prefer it to `multiblock` when SSE2/AVX2/Neon is available +| FPR is worse (higher) than `fast_multiblock64` for the same `K'` -`fast_multiblock32` +| `fast_multiblock64` +| Statistically equivalent to `multiblock`, but uses a +faster SIMD-based algorithm when AVX2 is enabled at compile time +| Always prefer it to `multiblock` when AVX2 is available +| Slower than `fast_multiblock32` for the same `K'` +|=== +++++ +
+++++ -[.indent] -Statistically equivalent to `multiblock`, but uses -faster SIMD-based algorithms when SSE2, AVX2 or Neon are available. +In the table above, `Block` can be an unsigned integral type +(e.g. `unsigned char`, `uint32_t`, `uint64_t`), or +an array of 2^`N`^ unsigned integrals (e.g. `uint64_t[8]`). In general, +the wider `Block` is, the better (lower) the resulting FPR, but +cache locality worsens and performance may suffer as a result. -`fast_multiblock64` +Note that the total number of of bits set/checked for a +`boost::bloom::filter>` is `K * K'`. The +default configuration `boost::bloom::filter` = +`boost::bloom::filter>`, which corresponds to a +xref:primer_implementation[classical Bloom filter], has the best (lowest) FPR among all filters +with the same number of bits per operation, but is also the slowest: a new +subarray is accessed for each bit set/checked. Consult the +xref:benchmarks[benchmarks section] to see different tradeoffs between FPR and +performance. -[.indent] -Statistically equivalent to `multiblock`, but uses a -faster SIMD-based algorithm when AVX2 is available. +Once a subfilter have been selected, the parameter `K'` can be tuned +to its optimum value (minimum FPR) if the number of elements that will be inserted is +known in advance, as explained in a xref:configuration[dedicated section]; +otherwise, low values of `K'` will generally be faster and preferred to +higher values as long as the resulting FPR is at acceptable levels. -The default configuration with `block` corresponds to a -xref:primer[classical Bloom filter] setting `K` bits per element uniformly -distributed across the array. +=== `Stride` -=== `BucketSize` +As we have seen, `Subfilter` defines the subarray (`Block` in the case of +`block`, `Block[K']` for `multiblock`) used by +`boost::bloom::filter`: contiguous portions of the underlying bit array +are then accessed and treated as those subarrays. The `Stride` parameter +controls the distance in bytes between the initial positions of +consecutive subarrays. -When the default value 0 is used, buckets have the same size as -the _subarrays_ subfilters operate on (non-overlapping case). -Otherwise, bucket size is smaller and subarrays spill over adjacent buckets, -which results in an improved (lower) FPR in exchange for a possibly -worse performance due to memory unalignment. +When the default value 0 is used, the stride is automatically set +to the size of the subarrays, and so there's no overlapping between them. +If `Stride` is set to a smaller value than that size, contiguous +subarrays superimpose on one another: the level of overlap is larger +for smaller values of `Stride`, with maximum overlap happening when +`Stride` is 1 byte. + +image::stride.png[align=center, title="Two different configurations of `Stride`: (a) non-overlapping subarrays, (b) overlapping subarrays.+++
+++Each subarray is associated to the stride of the same color."] + +As it happens, overlapping improves (decreases) the resulting FPR +with respect to the non-overlapping case, the tradeoff being that +subarrays may not be aligned in memory, which can impact performance +negatively. === `Hash` +Unlike other Bloom filter implementations requiring several hash functions per operation, +`boost::bloom::filter` uses only one. By default, link:../../../container_hash/index.html[Boost.ContainerHash] is used. Consult this library's link:../../../container_hash/doc/html/hash.html#user[dedicated section] if you need to extend `boost::hash` for your own types. @@ -87,16 +133,16 @@ as is; otherwise, a bit-mixing post-process is applied to hash values that impro their statistical properties so that the resulting FPR approaches its theoretical limit. The hash function is determined to be of high quality (more precisely, to have the so-called _avalanching_ property) via the -`link:../../../unordered/doc/html/unordered/reference/hash_traits.html#hash_traits_hash_is_avalanching[boost::unordered::hash_is_avalanching]` +`link:../../../container_hash/doc/html/hash.html#ref_hash_is_avalanchinghash[boost::hash_is_avalanching]` trait. == Capacity The size of the filter's internal array is specified at construction time: -[listing,subs="+macros,+quotes"] +[source,subs="+macros,+quotes"] ----- -using filter = boost::bloom::filter; +using filter = boost::bloom::filter; filter f(1'000'000); // array of 1'000'000 **bits** std::cout << f.capacity(); // >= 1'000'000 ----- @@ -109,7 +155,7 @@ Instead of specifying the array's capacity directly, we can let the library figure it out based on the number of elements we plan to insert and the desired FPR: -[listing,subs="+macros,+quotes"] +[source] ----- // we'll insert 100'000 elements and want a FPR ~ 1% filter f(100'000, 0.01); @@ -118,11 +164,20 @@ filter f(100'000, 0.01); filter f2(filter::capacity_for(100'000, 0.01)); ----- +Be careful when the FPR specified is very small, as the resulting capacity +may be too large to fit in memory: + +[source] +----- +// resulting capacity ~ 1.4E12, out of memory std::bad_alloc is thrown +filter f3(100'000, 1E-50); +----- + Once a filter is constructed, its array is fixed (for instance, it won't grow dynamically as elements are inserted). The only way to change it is by assignment/swapping from a different filter, or using `reset`: -[listing,subs="+macros,+quotes"] +[source,subs="+macros,+quotes"] ----- f.reset(2'000'000); // change to 2'000'000 bits **and clears the filter** f.reset(100'000, 0.005); // equivalent to reset(filter::capacity_for(100'000, 0.005)); @@ -133,10 +188,9 @@ f.reset(); // null array (capacity == 0) Insertion is done in much the same way as with a traditional container: -[listing,subs="+macros,+quotes"] +[source] ----- f.insert("hello"); -f.emplace(100, 'X'); // ~ insert(std::string(100, 'X')) f.insert(data.begin(), data.end()); ----- @@ -145,7 +199,7 @@ storage of elements into the filter, but rather the setting of bits in the internal array based on the hash values of those elements. Lookup goes as follows: -[listing,subs="+macros,+quotes"] +[source] ----- bool b1 = f.may_contain("hello"); // b1 is true since we actually inserted "hello" bool b2 = f.may_contain("bye"); // b2 is most likely false @@ -156,7 +210,7 @@ element has not been previously inserted, that is, it may yield false positives -- this is the essence of probabilistic data structures. `fpr_for` provides an estimation of the false positive rate: -[listing,subs="+macros,+quotes"] +[source] ----- // we have inserted 100 elements so far, what's our FPR? std::cout<< filter::fpr_for(100, f.capacity()); @@ -170,7 +224,7 @@ operation. Once inserted, there is no way to remove a specific element from the filter. We can only clear up the filter entirely: -[listing,subs="+macros,+quotes"] +[source] ----- f.clear(); // sets all the bits in the array to zero ----- @@ -180,18 +234,18 @@ f.clear(); // sets all the bits in the array to zero `boost::bloom::filter`+++s+++ can be combined by doing the OR logical operation of the bits of their arrays: -[listing,subs="+macros,+quotes"] +[source] ----- filter f2 = ...; ... f |= f2; // f and f2 must have exactly the same capacity ----- -The result is equivalent to a filter "containing" both the elements +The result is equivalent to a filter "containing" the set union of the elements of `f` and `f2`. AND combination, on the other hand, results in a filter holding the _intersection_ of the elements: -[listing,subs="+macros,+quotes"] +[source] ----- filter f3 = ...; ... @@ -208,7 +262,7 @@ case. The contents of the bit array can be accessed directly with the `array` member function, which can be leveraged for filter serialization: -[listing,subs="+quotes"] +[source] ----- filter f1 = ...; ... @@ -216,25 +270,29 @@ filter f1 = ...; // save filter std::ofstream out("filter.bin", std::ios::binary); std::size_t c1=f1.capacity(); -out.write((const char*) &c1, sizeof(c1)); // save capacity (bits) +out.write(reinterpret_cast(&c1), sizeof(c1)); // save capacity (bits) boost::span s1 = f1.array(); -out.write((const char*) s1.data(), s1.size()); // save array +out.write(reinterpret_cast(s1.data()), s1.size()); // save array out.close(); // load filter filter f2; std::ifstream in("filter.bin", std::ios::binary); std::size_t c2; -in.read((char*) &c2, sizeof(c2)); +in.read(reinterpret_cast(&c2), sizeof(c2)); f2.reset(c2); // restore capacity boost::span s2 = f2.array(); -in.read((char*) s2.data(), s2.size()); // load array +in.read(reinterpret_cast(s2.data()), s2.size()); // load array in.close(); ----- Note that `array()` is a span over `unsigned char`+++s+++ whereas capacities are measured in bits, so `array.size()` is -`capacity() / CHAR_BIT`. +`capacity() / CHAR_BIT`. If you load a serialized filter in a computer +other than the one where it was saved, take into account that +the CPU architectures at each end must have the same +https://es.wikipedia.org/wiki/Endianness[endianness^] for the +reconstruction to work. == Debugging diff --git a/doc/img/bloom_insertion.png b/doc/img/bloom_insertion.png index f24afbe43f89bb6c9077828d77c024b6d6bd44e6..a5c94f9535730e2c00fb02833ffb7993e9fbc7f2 100644 GIT binary patch literal 5451 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 PFfe$!`njxgN@xNAfeJnL literal 4915 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 *m*1lUJe>?B}vpdneT|%!*ym(h@ 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%0DUru|3X z*5hgX)is+9u55Ca&Q`S7;(>IfK?%Dn<{C!>70lyu;Qyzo_@ok(Z z^6l28y@h`-x^1_JLb2w~!`5YwZ=Kq{_O4Z_(qHj7gLg-&w%_}kD;Wunc^_VRiD#}S zPrdt~ba`X&|G4O~*Vp5sBP^=Fy>WWxeN6G%;brA-U!RZm7I&}tkWzLo@4Jr9nz!%g zow9na8v6E{>1QLemsVysPqVp29GiANjrZY2DT!;_C#9DE_~fLt`gs41hpx+#|I2L; zpJ!G2@zvek_vh~W=(*nXZpHgoDSvzY_3EwvAH8>P=l?)AkF@7orrTHEc{y)&`1$wS z>N%{`cdyDmExq>g$=;Q2amUq8%Sar&b?DUMm)9OYQINRUIPvD6d%wPwzBji&(>*7p z?8mXi*X~^H+pBkW?O|@8<9?Uk=hgl`6`W~$`S9oB&-(u+l+^xho%6nair}Q1mJfek zD9KLN%}|#$5}7+W^6IrKKhK~0&cCq@WRIN0L(O}qUUrLD`_FMJOW3pIXK?V_vftMh zzbs)i(=YlwiI4y8?O3DTPv=Z~MzP2lw53z4QIu+064(ckl$g z?Opow=hfbXOuM(OURKW=wjbZ8aiw^@UHjTs2htNGc#L*kGQ4PRq9u9QRbow&wDjuL zs}q^Nn^&sLir>dLz2jWh&h063yAJbPc+Z=jc;NLetNYuRvF*KF-$ebWW>`X-$*)o}W)w z*C=XzThGYgAnWPQ(1DJh{_pE6y7JC^UiKBslj|*}AGWs2TJli%&abddr^FZ8eD6y? zH^iUHa&mIg z)6=V}tp3J5zPh6LU+w#Q_UmJRU!QCJ|81hTpKKJwe zw{QNFCnvh7wb$>f|8H(=>^W1d``CxSzrUx?GBJ?4_vcfqs)mkBqNNw#|B}zo-uLZE z3;TBJ)sG)Pmd~%7HRbRQ!QQ`FS1j{;`v2E$UzKRl}Bk3IH7%K56>$79U;CiL&$9yR^4l7s2L zTUoK*-3Bssx3hjuWnK`Kza`2t@6u)d_;WS2rawbhzu*7g&a$WG&&T5rUo)21+|1s_ zZj zr|o6^SWv$B#;o~2@9qjw`*GA*EF|=OIZMNg60Dekfq}tdjn;oIO9mcwd)(@9>8HB5 c_|~ZZ>^HL_jn^a{Jq~i6r>mdKI;Vst0CN1vjQ{`u diff --git a/doc/img/db_speedup.png b/doc/img/db_speedup.png index 245f0c3b64646f13c16e89ef28db89c7e13166a1..f357ef50f0e6ede280528f7548dc0324150edb72 100644 GIT binary patch literal 6666 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 literal 4718 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
  • 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 Y1f46p(eiY*8mRd4boFyt=akR{0M{~~1ONa4 literal 57609 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

    OG-!*a4Nt+E1_^!># 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 literal 10870 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 ngdlQBA3pp4X z+%%XN7EoP?lke!ncg*a3CH*hTr+wj4UeT5onzMgHz#0LDh9hl@rpMe>{*>Pspd*mZ zEq-gWbi~yqG5h&+SQrjWesOvEL?^@YVS`2?hl> zQQ@wAehLf>=RyPg1sNJDC=8*bBHfsb@c!_==!ddbj~MHC|Cx8VM&0JO@QRR@q@WXp zA*W>)E^b+LJ?h2bqMC&ox~KW4%%};FcQ>{#VsbW2ye}Q#%yHD^#K{d4^GbX4J|@KO zJ7nEeR^Y$ATG4T?+H3Z&N$&OQg!VuG;cQq4QM@7Oa=eU<-7o#R^b1#&*2enjR{BP> zXGA|sc&nP$W?5BzJLSZ-ecRKbtFuD|E<|nHG~aSZb@WgBMRzA1-yXL;YZgalWx|7J z@9x_F+9+SV)g!yRw)W+=welf$Vb4-8l&97{%UQT8wN4@5wNZX;!Q7l|uR7+`x_pK? z3L=M#W_M)WEKfO+9o1JqlX;7-!fGGi`-;ZzIcDA5=5X07E%iX3$<=j#qCYX-+A{O| z#cdDF1g{qDU8i?aD zfA!{zYt)ygb=ldwa(}abtuy(0aMS&%Umd@iFaMwX+3e0MYtCSGkCykm4ZvM|CzYejPm~Pbx*H)96we5thLf}cGLU5 z-*%t+%ekj~Sx{J2?;qhGb2D0BSF~=s!}V_~=g)f$QcfCbjnhMJ9$vi1d;dAPHOB33 z?`OQU_o=!a>&u8YzQ`xW0WujUu~5IgfaqkZL! z_e)OgGhfgq(8c*s*53Hb!GNwSf>*8UwC~={N?8>fz3TV9TeqWDe|c!R!Sr(0yGD!Z zlxg8rzlBs^KA88}cBcICn(%AklmDKnj8FJi9`qHQPFy8q+D{+;&KAP&Bsl9!xy6?0 zjqiC^sNpFdJQnp%ojP@OcxvU9o0iuv7Vh6#x@e`R*j_8S7A1y+<121Nzj!hvL3rco_B`4;rTf36) zeogrF+Ig47)0XO`|G#$M_G@|FFaNUtci#W~v;Op_==(4K^v3^PT>m}$)yEZuiXw~* z3s*b|4*#(%DoUyBN6OC!`F~oD%+uNbe>1!4x4P%?|J*xmBWwI`e%AeU`ABmB3q#7v z&2zhJYnPojzjosV_vT5{_Ww9Jf6|TL^8XX&AGufmuQg1x`v0r#dOsKG6;wXH8{J;5 zR(j6lG;5Jv%+~xc(RRUiO6?45se9Z(`&m0^|9m{DX48r%)qlh9@!fwo`RRj?fu8%!{qIkHVqKk^m_B(u zhm#Jg`y89k#{d7+*Zr>k|8;(y zf9&(mN24dE-k+%Gxo@%hdyh|F>n-$8s(1hE5M3waRCuIXuIhsF>r2`_2ik8%hHm0_ z(7&O;pmONeojEP@cP)+W{Va29>uK?v#A*M(f3NpC^!||k|6MKjXS3g*^yIhs|A+cD zoZ%@a`S+*H`&^>&a^Z`o;om#{L@S7F_7p#|YD2+7+rPUqni)3SJu~zC%x&8?t^djU z?~eYT7xI56e`de^?@N6B|L?c&{r`7x-d_71kGyv%dGof#D^4x^wdG8}nY(A!`D!rT zs1vHoN)j(!?JPNW|3%wsmH+GhAItatH#g_M@X7t}|D5Zu;S8?{WRANM;9q(+zcx9z z-Y%q0LyGa`i&DO|XP@NWTxel$U(e5QXlM2BZ%}_g3vHyR2_`QtmC)JpbPtE^7Sp5F)_x^t$ z+LP z+E?7JyUiAIEZ}*y&A#~W`YWI3$NexhKmAPp?>+fA%gT54-}}q<*#AAYdwY#yc+JE= zlh>;Jzrv$G=TFJ%RqHjYR?b+NuwPAtF~{}n&qc@Ty=F```R8iYz3F6#cX#>E5A}av zzTp4=>ioa+KIebl4BFi=X?oU&?o;-^>TfH&T&VFfHE?BGRl`ng=HPRy7F}aE!<=-kQm!?U>9b91bBeV|hDw!O9cR(m^@)cuLe`qgcubRmXGW=>7i zW^;R$;HRyNz6f`^Fg*0ET6xl5_530GR~~bjruHq)(a^P#m;ayhQ~Tv%&q;G%)GMrf zc6z7N>3@AwLf%>0uYUJ`vfG#APD-Ik{(=l#lj0r()$doi`F3yhVfO`Bw(M?@*492B zpX<@)Iccg$<13vNJGoZa3v012! zM&DdM`2^`cDUi6zUGu)V{Z*s;D%lVdCqaj!5wm9p_cP4N%H*uq4NwZ;Wtp0*>Jl1U z=5FxhsY5(Vk-kf)k_e;9_010-9^JR^=C^O17tD_?my!z652%`;#^fp~>$vjgxtWrt zGE+F#2(~CSEQyGZiSfKIt|+^E!gHsqTOCT3LWDxhr$2;EgODQi;hzAfA_d~))k z=B#{vc7ID=PR(OAZ3|%8ki0~FIonZ>4Xra}(>Cmj<86<8@z(S@ljzHnp7nlJrRu-r z_x$^|L;nMlxK+~$cMYZ+c9&*Ow%L9Emei-NgjEY2&vvT`YW(%M>oseahKB5JbN~C* zZ`bzMHcsDvLC{5_MX5nz>XfEe*AC3ww(U>ff(ara<)^&2c^>*{q+dUA$&@`If0(cGpFzOi2pG8*)t{048Ja~L#Fb(Uv}HxSMt=<)L3cx`A6$Lxq_|$mJO_& z#+@ddW}Vx0k9VvR32{y~3kxd|-g(CJqOLygs@F|HGxsTMV_(RjaNzXImq+*Q`@ME; z^gVqEHP)+tg;rVbcz-k@4t&mSUoDxPGbb}U z`qle-OWU>EbsSh9UYuOs6|Z13r*0WbID4QYhwB0>PyYPV+%20i+y`}Nt^S~qvdY= za`LsGzwGTc7u#7b5ZVyHqBXnZ(>9(%3uAXLi;CJNqqca(L8D2q6mP0=mM{A6I8kBBxkV+Cs`@(%nL>L4 zShSi;K0VvL%Puoduc+7H>V<;+TT55&w=XDqTeI?|e$~12VmoV-?oU+YJhb_`n1yMV zl%odIN!6R%p1u{{fXYEpXc#5jh*X zU*~4d)K2!_(Jv6d;hJzfXWiP`x_5W}Uc080?yf#-)-x-sLwD>Fb8c$dRDC*kGn>0- zI^!q7t^;M&mbSXG`;?O1-M_z?nVTOkCvic#MIm6J^B<~o_4zV+bA<*&WIdwCmd zT9h{Usys=%bt{OMcOOSc_b#6uH?D_2*(vubTF1wOYXyVy0zZ9`(AuQ)6*b z5^0q1Jf!>Oo7m2NC!?TAo0e=|ob>EtckpyMQ7cESr?$`2gD=OwxW(Y4EW-Fb&d>jR zX^BXc&3cw4N0zjto|vt{b8=_-xicbFQGYK^E?*M7W6!R2*~}WU3pp%4*VNQ(+q?Gy zXV6tmotItN>+XTV%yfwv`(PQA>@2pyXsq}d~KcG!5Xr5 zrUl2QH)Y+tKQU-=T1hTLpFo$x%q8c;U*vTi{Pk;hJ6~M)4n8;y67zPa171s7>DNvH!!@?Z%cX$>({3J z`n^0Viiv&VDjlBBbMDnNPxilWQ>*{snG@SNhTHuCEM1zDTFuMf&&$l_wtqP*H|g1F zr{r|L?>=7ZCpH=%=u;4BWH{f}zVx$rMW4yl3kJ9E#l?x8v#GmSTxa%SX6yR>wsqfb zG5vK)NEhsKxOVH#ofvob2Nppmogzj5trXdIe%gMmlJ$QNK3=@3>vgMxlR#I)_btm> z@7+6~9^&EPwSU7EX)O`cDIvQW_9;wpy&HURa&_YpMUlYES5%$j1x_t`w^AejO^=6f z{l{l7tM@5PS^D$FS>eC@Ga7h3bix8YOtjK@50$oSTlS4lQMaT7*yk`1qx0i40q93YQGB ztkCguGMpTFQ%7e)(o-#Ckz(`DSIfljcv!vsru*$HYn+kHt*zP1)~?<2(D>2jr|a+i zevtStBS>>%k=zZIkU14j7hkvK`fErrGCuz;vQsnSmiwIf^Y^!NnUp>~^`d-#_21d| zzob6gnqQ;*ReOKY@4eqk_Fnq?p*h;wm@34LE2P7WQoSu21Ww_}<0!QsdaFJppGt_n*nW*=Cm%T4iTf_x-}cKR<6Q$=-40{(9}Hd6lJe*C_^APm=43 zYfDY7u6h}BI#yDmG)y#q@Aj3w6>BP5*)H;2yM8}ESGSs_cShBYszv`B^|@0QW^;rX zE@#f=IXO?_y#KSee5RSXd3oFR+WwDo<~*Sh&fA)DPx}46PpOKr&I`nj2zSN(*I9RG z(g9VW>^q+h9C*98x;C<6@49#I7CN7AYcE}T$+mv0V8!dd59fLAYIErAkm&k%d7@j7 ztm_r6mw$A>e0#>f$kWusq^E=b(-Ctc<>TYwCbA_X)d`7d>_o6DRdY_dgV)CVB(`;2| z8yS91{iiDi${?0omt-@nubRTWq zqQ2_Y!&U1T7lc`Iy1qY^^h;^cyu91h{%VV2gsO$=_E}76m@?yv=_%E{2mUOVp7)~u zKrt_)M&5eOS?K`{Ui|&S!sm}4?{eFz!dH9P3sm?#Kf|*nXSDLE~ z&Xfqg)LP{7v(O?U-v076vD73pReQeK{deD3+Ml+NV)%I{CfHmn~D7qMKj zR^ek*5m?xf`}OC};+1V}7LN)FKE2G&ul12(>Ns@qW$^nTCFSgZGpDYu7WVZmJo50$ z;n4fF?MDJ+2T*m&((e3m%p{RQ=Zac!4hD`c1ywS zb$9po=;*f=Q&N|$)qTX1>y)(q`JXlUT<^=zohew%+MvPVdL*eh*Y{V`K1=WAQBjNT zm+#oO@#g=BHJ>-mE|c-9pLjfyp`$OLrP}-N-+c#{ZvXzS*G{`vVouGj{mQAwA4T6NeE%SX}-Ys;#__gK1Y|Ap5K3A+WlIG*18u;KknvCVJa`W>6@ z6@S5eLEFcr3}s!b8_nhzuZ@~iGpW8zrq9&OYUVt*FZCAr=i`6fikA1cjk)@oA>p>= zYt2;&Uj+I>T@Iaprx3S(b@i2-E3dmgeyw1%fXzY3!oD_(?aqW=(foa@Pw#62wVc$= z6}aNr120WvG-a4{;IHXXpXn0=)+9)4Yk#xtX#V=OySBC}*5vf+*WW~z3H1t1sFG!P zvqi#hzEHbpUS-08x?@Kpk{_RbnYmYQ*1?m@zX=3;y35sbC?b>lQ!KYu(JQlB#Wl-T< z$Z7>S+>@9ea9KQ)LrjSTsSJ-UB$|vGJA4>%t!m68#WPwo~HM&rat;m6?-?Ret(?c z+0Wt9vhV$8yVSg0+rLv=-hs7KUBp;idZL50w)T7z^Vu$Yc2;j+-RYzH|4rbsNquKt z^Dfxr!gF-aa*bOrG}JDiewkUVce!(!TKHn0^*y#5^_AxB`r@&kq2ZLEzrSGTm!CU} z4P#y2SlVYl3RpBTa2bPEh$_z(&-)(*r|fzu>Xx7v9UAid>Z{w=!t$@pTXpQAtIPeE zW$X9b{=4}pyh@fKWY&`QMZ2sb7ieo8J=%PB)q~cBd*ohjsS{*7+6erl@7i9@wm^l=Z_zM+aMM0e@1+{o^4Q}#_5BP7st4;1enfg zNiy49;={TmROO-b#b?|{_uN`%pE~Kr=BpMmVajjR9rphFW>K!hv_j3#*>JWKYtWh) z)>*f;uD&_9psMy@mdCcTUy75HD-#@=0#p~dEMBedur@zO&bIQn%A!4K5xfmrk5xoe z3--J@nU?e|G$gJlb!Ch3+UM(d?ko&d-Fxr0+2OP5KC_PQvzy$($39DM{W|ac7gQE~ z5&z1?utKSi?beGm7Y!fl2{U_M2u+{os@}QcQGn{BTX*Kf37%cXjw!>c zsfz>dxJ}yOlB8fQ=>GjpWPHizoUWDatH1rpogVC6#mW#e_Y}7)%O}-Ehi40Zmz4}E z)X<-?pVy(ZShVZg)?Hb9qo&8~a{+a6wHIv^F06OYdiIffFB8M6DYFB7_|pwNXZ+?f z>|WVlVYzk5qX4lyH2erv&Se?98@QfigUl#CM7^(*Igu=n>*>R`y4 zHaQ@q^Mu3firjT;dri$&x$KDxf378%F1O!c^NzyrtPN7nRYZ2GCR^8By0b@d zP5c_x+&rN6u&^^{ zOp|+i{pKynzrW=FvEAFRnVWs(VsL1i9dN}wamH=oo&JVW#q;;A_U=3%pF1O3D8Bl2 zvRV;ygM*)w;bp@!X^OE&Ki%ZNTwbdAvEcj?uOokDHJ7brY;bzS>H0oJYheW6Z#kau z?=NRRT=u(<$M;OlN^eGnN>J0-;>yAodzFPx8(+P&NGNd8#gj{2ertB#X<%eH3Tgzu zlaW$Mik!yI7NT_OQOc?J{?p@TK3t~$Oa4U=g9GC^CqY5a{TF&Hrv6)`*Ly%N=JT)H zk9o{l8+0wLYgbvf?>c_-s#7~Q8QeE#XnA5HvbouBvtg&fncmLD)!$w$;&|iT zdFCk_!$V6>SKC)6vpa8w-Uq%A+=bfOZi^2VNv0)EfU4s|G5ND*3$~l z`o%r9V^hH+5r#ztLS6T+9{sW z&fNWV>HZbnr;c_8GZdT%^$%=KT-NVup1u4X6jV&^%(+QnX&TS+rmb8nyvJeo$64=R8Qne7 zF?rYC?+2ZycAPz)SG3xW!6NYG!VH!ri?3U5F$+#yp}a9+_Q7Y*pTBo4`~L0i>-&F} zaUH$*zAk+Fw5RLy@BCbPfA2!4k5}UFU0TWz@D@_Lbcl4RCD_dDi;&@JUsOLGG>C4b zAhLV6Z=mPG7cDQ<+~!|7Ya{cj;QSIjCI*+V+=UsRTNj1zzH4tBnarTZqyYAJzS;loVc)HxnAch8R zPkz_TtBcAKz$4wt=T<$6-amKYi-LFgpEY`}-}^OV`77pz=s@Q{cE5%nLh06z&+uOM z-Ltd0d`jM-OWJN@aWO*LE{7 zybFob2$Ko#@7OgbDyVbE`x64@YD^9?97ii`<>kvS`NrJ8VIo}rea`%=OBooJa~!Rh z>F(nCMs42SZ3~0~7ft+V${--@Wa##7a?5mfwvx=3)55QVYM6x@FF|=>onV*YtctxX`pG0Pc z+id|QXYS0ixPEbAx0kua&k)r|c5)qz47WQ1N-~pEkKUQL!_0iSw|DuMC$gelHUi8H z+k_XsxRew>-|NW6V{Wk_R=+>~`V=*tiQ$pE#;z1O5z(jLT#D7(zKE9!G9*L`be*%X zEZ?&K>^e82aCyI#b!p(sGXk^! z@jnM?-!0IUm$Po|%;M$x%a@mCc1{bw9;$Te&|C?I4SzXY&B5iYTcu9h%%g3JW(*sY zIb6-(ZQr&kpr`2kKk4`Pf)-Y!zUkCsV#rZ;y4X8=clqjon4MLj;!|I2e!4zH>7BLF zmVFyRMLWo|2W+&nHtee_&h?!>?ZuU)?ph-EZEF3+85y>TFMQEZdG6oay`Z=_!}DO} zOZ^f-hJWt!!VdB{=gIFGHEw zLW!#{=GkhAEIqxV+w04@nUbPiHnxIbiIc}pb%D#CPf^pSd02Ho6frPZHwN?^i3!lD zF5BKYQy-i*S9LQmShoc9sQhAeEq?c9FTdO?C58ihiXzILb&eB@morR|X-WENcKuq2 z(y5?(tgh$gGC3>~=z1Exvno_I>87}dWjPl^r<2CC_krolxsLu`yLQ(K7m!a~6h%&d z_blGbb@b>yyVwvekj6_PP8xkB@)iXPs$KSPUJ@?hX!=I$t+?2}e^n2icGtb;yrZ&Z zVeIY@jcMjjSI&GtrTQDAf+A>WXGLlMiT$ijhI30;BSrU^Ou2rqev#|KZD!`ng$g(9 zixZ!E!bZF6ryrw&qSoUtnKKv8*d4j+TgU1ElkIQ)O-x@d3aZ)l#Y;8m^TyerAib<* zE7IFg^5rYn)3sc#%3fCIwryV>FhzXV3X#9^=T-+ii40_zAanIvu*r3v&;C=FIbU4$ z)-~%!1Zdc7diD?T`oPvlYwiCf$A)y>|I^$bqVezN)7v0}MIobPtA2i2sW0+5D}Q5U zVW8Vho%QQhyMRU&p1)ysC<^&%>Z&hlEc)hpSB~ti@qC?C0dFey zpHctH%Y8M|6lKEIW?fP?CGR2ZP}ZJ%YB&m7*4utOgna_D(cmR%cobWigvmC-@E9_(9ohN z;%$8Y>QbkTS0eqb@68WeSn=`LB9L2o7Dh;&bQ0~F)VE8cPFIa- z%l7;>oN&Bj3S)(`jC`9n2~Ch{_<$SjOt z`drhqa++AINRwrG6{LJp5n33*R6O}nPW5iRTc^@b$FForQIclxuyfkT@@!$?!~7r+ zcZEVr66dn0s1S{|{hkv$a~Kvh33RE>$jAy>_#>wxLR|Eyiy1>eB*#&qJ9qX*w}QG= zdRkhhe_OqdT)!`O-Ibw%MOnnVVDCD=)}p zY+rGj8z_nO2JmEWT<_lssqO-wrS+U%sS4BLK6kE`$a5>JlEA=cO^=p)uLni{O^%}* z&XkyeTC*z*jCV`>A7g0Xa?)rk{eD4Fw9EVN-(4#<96Y&POJwF8_Q2LfC6c9r3<@7X#>?ct?vD0< zo9lMd;Hf4{2xF~SHeovqoL%>3gqXE~RJqyuj zoB3l3OyJCs<-)$cyH;#CY&%&?WPOh%0_o3*g=D^}Dro&!z*pOfzmCc)xwyt3^!f zdTaw1-uU?M+IC|m1|ij#3tzBCK$ds-USi#(r4y6kzdww3S-01Q3%6H06`sC%Kr)q? zVd`olzxmTZqZ3QB<2mv&wLwh>`O>miiR`FdIp9()WG?XofCV$ky2`c-7Fg3n~pg9~Tqeljvm(W(2hdjBe? z!abWm$0a`AUTXevdfoRnJtl@7#`gB>`%iyd@$B;QeF0yxp0^||Ok-p)T?JhZu^>L$ zF>^gDgTl{+8avYiCDWR8%Tp7UpI?=fx=?HfE5m}cP&a?W@Wwi228N3vaT;F!{T?g~ zM?hs11H+M~02T%ZWf4Y(3mmQt3?@#33=F+!LWSDbTnf*nd70+k(q((6vESg(^FZS} zsmtv;D*LaM&8$7P>-dCTcCl$&j+Z>0BdN0Jjp~_o-ES`5Q@dunEcdValM;|B(~a*8 zQ-Ytqy%@3~{QUztX70x~lB0U+E!PQl9X6*wb8M$KpSS%6^{=SFM%XyEK^xfpRw}A-~W^I zl=c_8SD(pL-hc2zHRl~OkG!L6lIorMzDqpvKk-zq_`c>(U(0*R%aSJAFU+VGIa-(6 z=xcdrvFg74G81>Z|JA$gP!~M$`wM}E5j}s5-ro0ma6gRY<}>E$L{{?nc2Jf_H*-hS^eMsuMPYrv}0}EWbN-i5>lj3tSP`JY%56!Q4zRi}SFGsQOdJ~K~U`(?$k&#OgmExw!bAna!=-}j7@ zV$aNEjv|l*m+l;u!dr#({dMa1i9Q}vW z_0Hm0X~Eau(CD&hO_ebFsRhWqY!{RxWw8@6ZK+HFiAzyH3>`MdJV zw8%zCfNz?*r}J<6nQNEV9^3A|GI?9ars)TAzs`>NCMTL7y}E2q^_8=!tY%LOw;sQ9 z_R6Xo^~Z8-@3!5xt-88Kv@!bVF89>a{SS-!ckNtrV42O+cNZ<4HfsDffAc?g`uX3= z8|rS(1EsM(mQd~1H&O2`@7Y#`{;9@}Ss(zz4n*rwGx zd7r5^+4S_|yNjz|-4Cn_=KHRZze)6|Z`IV@wrNpYSn{UEzbNQev~Rm?E43+XeaZIY z76M(-AM4IXJ&Z5c+;e}O?&YipeV@1&*Uo!$e`!Z)|F!;|w$)qAHvM~FxYxZ}|K?wp zo2SEe^W8}PVXqioz2(HMBwx!r$tUa;%d59=Z?@ky+4k7S`dZ2Ap0E1L10G9mD&4s3 z)Qw~toY@vjP9`j*&@)GUiPf_C=U&y%S>J!<)*`pH9iPh^XSr)E>RLMg_T0}rUpMhD zGnDIC`qCg%yk2A42KM)}OaIii+^_E0&)pE~zpr)K$5#;$hRvD>Ir`)`o253XScb;;~b`c+R&~u>s31*PdH0b0XApneF=%_wK$G zWs`qUx?1jY;FdeQpQ6rj_U_qw)V?O5_Vsbk>8Y!im~Xx}%RgbGoljk`Qo`pw=O1jH z_;a0G(f4`rH-9bLwR>&6`~!)F9a?I~1K$g*{y*8SW;y?|XRUvu;#Oq648NJYxjuK7 z!R9dWnPHdMi!X2MziIG&>ra+QM&0OC#_Y}V=c`lg=KTzC{93yEp7(z_pS?~+tDl)p z-CyZ&``+`-t0GOAv`*KTPRutxpz9Rqa{&HQ5i zCHLoR&df7v|6F64Tk)Q0mLGSPoyV-cWF!qJ)l+F5~+~3rG^@g53A$ODc z$riyak9!tO+w9A}_UyLAwO6A>*5~gvt2RiP`XOHjWUS`Rv(0wR`QZ_2FP>$@ho?AOvz{li!Ilg!T4$`Z~u?rtanM1_qt&o-U3d6>)Fx_Lqk~o%_D< z-@%a46OKofa-F+yD{xBI3cf{)V??(G z3M_VTV9^p;!Y0`9$4FAZ@rk(yhoZ{Cd*|)W7k{h#R{ZU!TF#_`vQKqu_liBYng7># zx83|VhcEj56lP#Ju}GGYq2pp71H+LFcLs)}Ey@fGhPMP68YJFwFgWy-u`npOSu-&R zEJjk6@Meque50*PZ|`ReP}-twP?q7oU`>X5!kY_y4v`o892{5KrC05)4*wavD|*?l z>r=~eKEA$MWfZ9>bvdB)Y|7;qx#!eoK3XehSnb8owOF>%N$anujhI>1@vOP3&F?~g ze|gomb>peiE8aD2ubj>Dbq3F?hF^=faW-f>Su?d>$vTyJrSGNH){C=lMNE0N_RW(w zd#tAX@w8=H++4=O<#kG<)Bjs;++(zie)3v|-=h z3?-1N?|HATtdO~@@s=Y-@|K{6(n|gHw|u8QTD9l8ws}Nlp!E7RVbOuI*Med%=N$jz zD>Sw6zuEIC3?i=9OsZnrH5vk)teKv=S~4ZKma%wgYr6ky6O<4pCcrV`C1*yNP5rMA zA8VueN_6gizxIAhiQfOSeb=9D=(+uG+E+0yr>vhQPV3i)2s39q%>vi9$zpZQ zo@Mp#Q2uh)$)=}6v6dKLwrez?}p|8(T7#f|&FKc3tAC26w0d|vI^_mh+V&AV!O zvHDqK>G=!YzWe^v+}yMGxZQ{EYy9rd6qe6K?Cizo{l2e>T+lu>nBDsP+&=-{jpu~}ZY-b9cDMiUWaZdj zQD^{BFH+`-D-;KJz-h5f@|0jZ9JNNzGolhU==~gZKvO4L_SAM=7^1pAb zlHUHA6HIX}hie&u?rz18U$2+zw6mUT`qrtI>5G27r@p@!es*z|VF|Nf?%{zdzA z{hht)drYIQO#C{3>$O{fpYts~XX#yA^2XSN-Bw@jMTFG7Wq<2yG<7`A?tge@_0EXN zo6cX5`Ld@X`u3d{eGS!|FZP{U8=eWP2Cq% zzU=w&!}<52+h1PHdACBL)>`iFZo|0+FORB;moACh{qg5)vE{;gdv82h)ORJ@eBavA zI;)45x_$LtdF;LR;FGF6LQ+0N*If2&c36v z`_;TmJ2%NC@@@A&z74;ta=S=he*aX@Ux`0@q_@wteJroLtfpwm>9ZSl$JLiic%MC` zYu=~3`DXsM_1kAB?zj2Ru3q(1Wx3CnDew2KDn5I+{&m_7@#VEE-=E=>U(EAm%Kuw$ zuiO1OD}9%z`uf_rFL}CyE(F*83Z47qW8tax==kEd6)ESd%v*#ke;zcuyZa?~Ug6st zn;x>tFR+QbwJqxVyLT_^pWZ5bU94;8Q28eI>!UTV<%%z7N5B5?UruG#**4STci+zb zyE!&5<^=MEIs*ek!v%a5jv1(`WN4W9(SK5;n~GbOOVScI6}KoIA!Sn~!ysiabt!40 z#7e==iBb?stow*ZuSd_74mj2A3f4u8W~qrgu6#}&wQ4i~A&Ci^rs$h2L>NH%BJroX zCj&!(D|(X)qXnRod0e)A_qDRpGxxXbSib*y@1mG_wO==GTl#>XEMmis#S{g=7&pPV(OZhd=v+n&-@+2;Rw`~*C$<9?r+JHPgWmF_A}^Dd#6@-h`4 z+Roe8o=x8(Q(xaM@4AwQ_sjn`(d+O0n!7gYTlr`2te-qQF-6<+kM-XEBjBZ%dHnXR zXS>R-JYFpA+WBH<{j;;dvDSInbKkoMYRT{0`%V1*FMad9dhwq&%DYzP+%GzIci&C> z+fUmAiaf2i?ft$i`?&u3|BUkMrk(qG{`{YB=l4Il_3>zC{G-EHjiL&#mp_mD?QFjH zUTL;ENJ;beH>KP2PcCw<<4~QxCHen@1LAeoXGNFHQ;uy2s(96(yDPi?-+`_D{~!M^ zVBPudpQN3$XUV=lOY-g<%KrC9{ryyvt~-DHhs7E@m&m5( zhPP+C>)OW@$@@=OdSTPU)-UVMRu?VHcGu1S^Ga-f#kwzhql^9@Dw$MU;%5$0 zcCE@{Z?=tvpUOVjr;VlOmjthsQ~k-6ZJb^G{_J|YkB`69#s2?u=HJ1_zSiPPmET&W zm*r*8^>;Vh{H`?DJMW`>XLe-a_40PMU#5GP1;2f3o*%#ONwU?Z%-p#8qH8_hFa7@d zz_yy5P46eq)9`F}&iSreRw#$gl>VOoc=wvK3GX-ZZRqOMF_ zb*BH`-4o@JvyZI&Tiy5O%>{kEMExvI`R|9*1$AF}gg#sMHvOkwwy{OYgPQB_uH_NXi2S^=4M~{z2kiF<0Hb`7Yo-G9KV!vdV_qe z`}|uQO&%J(S$_W_d%4cpZ67XvKQirpU!PS_@CV=EO$@Sm3nxeYqhc|>-Iah zr~b2M)(J11`FC^dE^%)$*{IufJ#lkxtSMt*NGKwsdxMeJddgU41^oV^863UquQbEs z{xX(hG3BSPw8bvhSvBd1ubcw_C3-+P+UaT5Ivi z-wQZauMT_kD&bSX%TP1_mf!3KVj1oUTdvw_rLk+3hF8zumK;>M?yc5SasIGvtM{zA zZQl^+Zq2kKb#5AJ-$U%nlH>8E~o!;{g;pLxo#Vt&Xss8e_#d34MEjM zb}uU9*>+|XRAl3%u;^|4(p!>09qLzK|BCJP(r6}$(k;pcrzU;-ylQWl?5aKaSNise zYOiK(KKSkqU(q-Ige|0X2C;<}mOdjcWl4Ls`p-Aodw2i3<(-;+*meJ7~0-hS)U#R|2$Q@?6m&hbt) zsgyr+?rNxZVd;jl3%d^YI8><3T)lE`*;AG~KU>776>q#a(>=e~jB}l?O_J~3m!V<) z?y5FBFWmCq;dl}U)X*eButFz40_K^5caY9i6hQ!`D| zKc{4s9$)3N>0`pPn4qbl&NEFP8DG40bC?BeBE)CH#>Ri^sCpu=6^Q3xbN`6 z7hg<6ZpSoj)o%KJ((~8a*X1EQn*vJa%=*C*bn^!9vgfjErpf76Z>oGgIhI)>eT(w3 z;Bz^%Jk*cuGVe@L*c@7RtLNH!Ii=zicMk4i=GC?ne!Fg0^R07-lAp*fG80=`scmg( zoxkPzi!V*vAMWx;equ>g+YO3sd4Z z?^=0}ZFAa|x~~E=omrbBFE+MKwS|}*`9yBVu7%I9^_;V*n!VezwEg3*dH0u;RlS^< zVjeJQ>gM$iReqnisbiDzwzpnzPHSA+wbMn~sru{^;UK3Ql(t`E99%*3IodXaBgnJ$?HbuWNnl%&dZ* z$-LG)yUIRflh;4amfd`O(UN@r(UEUO)_nGaTd~qjKf5^h@Ju7#O~th)pOUalV;CG|BH<}N-ZIt&-x&JKx(LLX}vu+wpTl27gR@~O=iC$%yZw^k&k$Jmb zjq~ri%cuQSo(s$?JG^@B8i`J zs;i{M{#mA&`J|X%J6NO_|LF3%cgOj!&h@|hl;Lq#*^wPy%WSIGtv<8vpXn!_|He69 zsGA-i}JDAH|wW7E|@e;^I`nqsb97jcG5LBqYV_{%Jg(s zcFYf6e5*QHGbOi|v7G8EV>!n6mSc+OTMm&WuZov#-(5b{e(CPiRrRr%$Hn%?tyP~a zy2y|>^tz8(@4aU^lQi$W65PC;Wr4*lL5(Gs;twV#E-qVhEz;;k+*befc@fg9_w>Bd ztC+prGob(E0=^aYS+f~-C~Z*=@L0QSj@#QA>w=}%uIzQqym!j)+_}}xC+A;LHn7TY zU$9_F&n5T64{saZPBYCrd&^7|lr&aZ^IZG7I&k6v(Z#Zj+7rJ7F70W{J^m^qAnoAm zf{;!5pUpq+&Oe{`^4bFVMr{{srtl?E4Zki=5Bap}U+S&Mt)h=_^s9kX6~15Q*L&(> z@1?#2Qy0oMDoy(JY1Q2j&2^`$_ZBrz72o>tRMu9}tBJZ-P0yB{xvvr^`8~hbkJBM= zi*i6xVKK5gIxGBm8VHxfVwoEE|k}uI{=t!__iha}&o)|v*Tl1CcR;3@!%`MK) zn!EPtXa4yAH`&)GOKv~<_r}&U_qSA>J{bGAnfc$bm_A}hnRd#k`^~wrIsN>vudlC% zY}vNWuD|d}-J44{Z&rSJadF{7#rkBm)hBQF+5X)-t8f2@+nJD&rAY1P^8cRf*zdn= z-)HApefj$rY(2a|qp%)ejcwtv%~<9DkMMK5-hjr!>0WE`xc8rMx%Ind3VbHUg+HZMccc&diQ?q&{ro;ev5qlB+H%X0ioma zx5V!)bAMja&bspFb^l$Wv${<+fAgGmJm3BL`ugwXD;|B{|Nrk2trMPC`aYZM`l-iM zZ9M5JyYhNo&Cf~wmpP}$_^&mv#5v_yQUtlJ{Ynyi|6(%$=!zTQ`TD z)BkqVqi6F6@ma3#*SxrOce3Bs^LiEmZ@MQ+q=N@|`ekjauJ~`6s$cuz=n2p;(9O;1 z{ql2PsCip2t6x|e{5r?J#mK{+=l`F>*Z+SyH#cum?#}I}k83o4Is8G=JTH1%PV4NH z>uVw(Cr@$wl~|FTogE)PUw-ZdHCO9(Z(kJ{znT90{{t8O`A4UAf=7Dde;xHPKYXt= z`}s{{n_tsGg5jlUf4l$pni*6b5rFy*=Vyp584Cd z^mC)vR)6oeue!H2Ir?>?`~n-ZHJ0nD&EFqP&8xb*uXZk_&8jQhs^ zIZ=0hJ)32k4HCJwK7N1U<74N}`TehRnyqvH6eB~z6QXAZ;CUQ2fv_ggx~?j7!}li# z7r)Q{_2BLF@A-8f_nX_^-&vz~`iK7e{W}Bf>veaDr}vlee~Ul%zy9(ChyN4j%HEE8 zzW?Lu=&-xSxkqE~*Z!JyenZdyPpAC%t((7J>~1&z{kYrZ&y4>4pUu{;S0;adW6}3T z$Ibsey2x9;bji)IZaIhuvQTR(|gA>?*xE-^w#A=JUnL+}OV~|8CQ~^!h8C zKm1Og|Kr}u$ogYNTc`Oyt934S?0$FR=-tv*Rp{|sFlDY)Fx*yshmFMo;J_g?9Bh(;`;|IeUFsy|Hgk2EW!Q$jBV+^ z75k$r?e<<+toh^1`u!5V7j%5Le$o58-8;SLU*ef9%6lUgEe&+P)U6sNRjRT#Vv*{0 z<(Ee<>3)@s%s#$v^YKM-HUH1H%D?~h_ocISwZGhzN7oPkxKw+Q|Nonbd4C@T{r|b` zU0?kAM_2yY2I}woGuh6qcUkXZ6)#_ZdAIxFuF&D%b9vf5P2DVC@!Bu%j=5g1)tg%!zx!{L?{Ph+;K0aryX)PmmTy^M8oTBB zi)q%k_DwstlYiab(90XUFNB0l^Us|(7p%LNza-bn_P73}o8R92$YLe@E~#tw^BG$d%HP-O)@EJduG*+`W#TVeHC>PL$CJu@?APDk{ceBT z-(Bppjx3+f_C3Gu;$4fsew+WC%irx;c2YN9^4n{<3?XOczpLNPe3mz-Zh2+J+y1*M zzfVuSzj^<=#%G2WB_A*HmhX_>UVJWo*UPVG^8e|5@0QDmn$H*aCh|vZ`R^~Y&n_3Q zS~kc2@5b4Q_p|d~+LxbN-(e*2=KbBj$C7uI&lbP;GVO-z_1brn`@j8`NWD=0>`3?J zeM_p%>wo<(F@7VP8*hE>aQx@#p66RHe_!+CTJ!n3o9EYV{-5;X2)mtgWsFt%r!#Z* z7n*-O6n);;^3Of#b+Y_#Bc6skR?fNczwh1MuKK^rvXA%npZUUg3N-bztNi=D|KaOo z<15nF@dwD|m+99g-wQF{_)YJxxqa8BK+RpVE53EU_w}@&``YrHu{&gF`KkP0`Rjf1 zdv{-C*Wd>gQfF)4)bB34Q#|Fp$+Uhs`TKJp@9#0ba5P?G`cygjyS_GmU+eo_Jbe6M z`SjJw`mgN|H9h-y@V0*bocialH&6aAays>|d&C8sdq1w~`s?jTyftM8F4{K|{tz;RDT}@n&5y_&_ssY!G$48LwtjrNWiZk8c{a3T1G0GysQd zvd{z#WHTK86fy?TCa?8h(LuKffJXumje>F1}( z($n{Re0Dl|n*Prn@Bd7YpX(4;|MkSFulf7`eVa9PxA<4yIiD8`pPsdM?d-CB_cp%f zpF4N1xPIK1#lk1f&9&w)U1Rs{+NFJ^+;)2n4F7w+xxHb-{^F|#{^|a+J*AU*{Pq0# zKX0%F=(@5kLe&$avHY+p5ZCS_!JT;KZM{@=GN zYwRx@DgJ(Z`C#|ESAOUJaoe{TdEY9$cIxLkS^0?XJ<@XxKvR9!OONyayR|j>_#sQX z84lmpRRvBjJ74-a##%43lmermv&HNly|MFJh z>uYQ6W@zl%wW}m{ZgGCW-@<*h>-g0saLabzy;^JZUb>DWb=sE4x7P7p@0(x$<5%zR z?K=~gEdHGQsQ;&I*1FfTcK^S&_3@(DcHb}Vmaer?oMT<~qg8s@t4nhKp7pMiO~11v z_xiWZc6$3KAJ@NE_qiMmKYow3-nMO< zoL$Y0<4>>OzP-ERV^ZvOY3_e_zqZd?$F|GtXNA*_l4l=Y#@Anrz1zBd-si;{#{bvO z*#62TSt}o0k=q}1@@tI6-_uj?7hhl6SMzo2 zwk-;vMsv8<=6(D2Z8NJ6e09QD_rJOBiG$5^>y}R1UGnb9%=>@l?q3&QADCa;VD$Oz zisNfGc%S;mJ9pdndp`Q^wfn!{V6QsH)t$mr^4~KRSNy*p8hYoKQeLpl)utezrVfp)_1S1|Mex) zHo4W+h;iS-%m3f4-l`q+w0_TZb`8t>eUs|=W|pO#ZWG@ob7$-S5KFs<7Y{Ew_ibtJ z>dSFggzCcP)N#x#dvfi0RNtF7Kb|c9o7ehmdAsY}5BEa#m+f7l`e%p5Um2sfC$?QX z9J{-^Pj26Xu#BRrh$}bqB8qL^y6QemKl-wLLZ-Xk{hb?r|L9A$WxvaH)}O!I>Y?xN zY0F^-T)O!B?d|RIwpCx=+$^d|Sl8}4H#a;y{8qP6U6@QA$4l#?ulelX@+GZ4HbyV} z`SN9eddxB!%vT9#^3tiURGZzT6K(TwQ5X=@kNi%I*BXWPseUA%d31nW&buV zbUpJ#?^k_)@c!@9&=->0Yo<-eI<`N{E^%f1G`-lW2L~AIb49BTa8;|uTrs@p5v-fI zzWww}%lgarcbhU%X(QZb3Yt&PkR^pHED4i&r0R#I#yST@~^*{|F&z_a!Ya3Mdv`v9n98P z-diE={OUw_nNCH?VcG9%zx_8$`K%Ap&Het&-7Wc*VBOpE?(RxEJ1h0{w4diP^Ris# zzI}Ff_U>J~q`oG3$X4#F%{zbSeZA3p>A;UBZx^{;;ry=mcRROi_sgT5s}il57z{FL zH=KB(<<@flnYS*zz0V*~zeU;L*{IO??9-VHPd4M zr_X%y*Y6eE`!)7vkfiTDcGrm5twQ1KliR)5Z>m(fW8bjR!J29D+INA5)&VPb-}=2p zcJis>+;GkP6&p6iK25#*kFm$|merbPw z_>I2Eyu%s)8Qf&wa`a5RrF>6xYx0KFuw1Cf)GfC*)!tYjJ6XKWrZUt#LjSH1cs3?* z+Lrm@Z@0b*cv`!-?91ASNL>k@vgc(JHrF#O?j^2TBruQ$>tqwH%ti3+<>fy4<@J8K zFUyiwW?B3y;x~VF?77CaG>i7t8I$7eW--0(IDP%D(bezq|8$mJoOoq%+2sN*AHG2O zzGq8j$|QQdd*@aq^7_yEkjqz`|Mg1n?s8dQ_y24Gx53SeeLHeaw!FN({-DmXhk{og zKlRUDd{wgURDR7jWvf#WerFe#F7;hqTlMwVpR+6WtjlVPZaCRjb|m%kmC(2QPU&2G zB+r!fImGDU(<_Uwl}%Z?e(9#H)J4U+z3#DpPA z?%ehAd|Te)AX{>!RemshWu*XLDH zU>~x5`oz4Aw|^^muj$QG24z?4M@6lZBU7IhP7X7g;}<`3<(bP|H#fdzkI$}&Mr=9ukya!Ryi(c>#e+$WqmmSPIsz8x_SQ^fZ9sV{j^8fr9c=88?i;afaQ&p#CW#QwR@)M)12D=mj#p1XYT)~gYtyd0+}(GIIj$UAWRjn%p8mY|o6Ov}4`wyfJ*DK!C3Z?bi>o`A`8xEi zNYQ!qpPS9NdSu>qoW5Gn#B=Y;v-JT+v+RUjf}{6%mZtA?zc4@Y>v5^hGyAfO_#4Mz^lABTu=~*L^6pC{0^_u}7uocFMAC6_a1hJFNVK z|61Uzs}Fpn-m1y)om*veXWrfuzk;PVetEt?x7qgB^~&c0bG$!T$xMB7`;TwYx}|=b z%D#U2Gte-~JX;hb2wE zcXprIdd2(N2lvwO3Hv-$cbj}m@HXrF7CyP9?)psm#3H`wmE3z$PP@4?f4b1OBj=#Y z;^~)fPxjOdoIPWa)*dF!g(r(zaJpRsR$)$r|RP^|II_+3tSZ==i(aa{t<4zXoFf46qwnYPY1 z$E - + Boost.Bloom Documentation Automatic redirection failed, please go to -html/index.html +html/bloom.html \ No newline at end of file diff --git a/example/Jamfile.v2 b/example/Jamfile.v2 index b244bef..d53a481 100644 --- a/example/Jamfile.v2 +++ b/example/Jamfile.v2 @@ -5,12 +5,14 @@ # # See http://www.boost.org/libs/bloom for library home page. +import config : requires ; + project : requirements # /boost/bloom//boost_bloom - 11 + [ requires cxx11_noexcept ] # used as a proxy for C++11 support ; exe basic : basic.cpp ; -exe genome : genome.cpp : 17 ; +exe genome : genome.cpp : [ requires cxx17_if_constexpr ] ; exe serialization : serialization.cpp ; \ No newline at end of file diff --git a/example/basic.cpp b/example/basic.cpp index 24c2aae..65ec743 100644 --- a/example/basic.cpp +++ b/example/basic.cpp @@ -8,7 +8,7 @@ * See https://www.boost.org/libs/bloom for library home page. */ -#include +#include #include #include #include @@ -41,4 +41,7 @@ int main() if(f.may_contain("bye")) { /* likely false */ std::cout << "false positive\n"; } + else { + std::cout << "everything worked as expected\n"; + } } diff --git a/example/genome.cpp b/example/genome.cpp index 6085d0b..9011d80 100644 --- a/example/genome.cpp +++ b/example/genome.cpp @@ -11,8 +11,8 @@ #include #include #include -#include #include +#include #include #include #include @@ -29,7 +29,7 @@ struct k_mer { static_assert( K >= 0 && - 2 * K <= sizeof(boost::uint64_t) * CHAR_BIT); + 2 * K <= sizeof(std::uint64_t) * CHAR_BIT); static constexpr std::size_t size() { @@ -45,8 +45,8 @@ struct k_mer k_mer& operator+=(char n) { - static constexpr boost::uint64_t mask= - (((boost::uint64_t)1) << (2 * size())) - 1; + static constexpr std::uint64_t mask= + (((std::uint64_t)1) << (2 * size())) - 1; data <<= 2; data &= mask; @@ -54,7 +54,7 @@ struct k_mer return *this; } - boost::uint64_t data = 0; + std::uint64_t data = 0; using table_type=std::array; @@ -71,20 +71,28 @@ struct k_mer template std::size_t hash_value(const k_mer& km) { - if constexpr (sizeof(std::size_t) >= sizeof(boost::uint64_t)) { + /* k:mer::data is 8 bytes wide. We use it directly as the associated + * hash value in 64-bit mode, as std::size_t is the same size; in 32-bit + * mode, we XOR the high and low portions of data to make it fit into + * a std::size_t. + */ + + if constexpr (sizeof(std::size_t) >= sizeof(std::uint64_t)) { return (std::size_t)km.data; } - else{ + else{ /* 32-bit mode */ return (std::size_t)(km.data ^ (km.data >> 32)); } } /* Insert all the k-mers of a given genome in a boost::bloom::filter. * Assumed format is FASTA with A, C, G, T. + * https://en.wikipedia.org/wiki/FASTA_format */ using genome_filter = boost::bloom::filter< - k_mer<20>, 1, boost::bloom::fast_multiblock32<8> >; + k_mer<20>, /* using k-mers of length 20 */ + 1, boost::bloom::fast_multiblock32<8> >; genome_filter make_genome_filter(const char* filename) { @@ -93,7 +101,11 @@ genome_filter make_genome_filter(const char* filename) std::ifstream in(filename, std::ios::ate); /* open at end to tell size */ if(!in) throw std::runtime_error("can't open file"); - /* number of k-mers ~ length of the genome, FPR = 1% */ + /* As a rough estimation, we assume that the number of k-mers + * is approximately equal to the length of the genome --this is + * overpessimistic due to the likely presence of duplicate k-mers. + * We set FPR = 1%. + */ genome_filter f((std::size_t)in.tellg(), 0.01); in.seekg(0); diff --git a/example/serialization.cpp b/example/serialization.cpp index 840043c..42dcbc8 100644 --- a/example/serialization.cpp +++ b/example/serialization.cpp @@ -11,8 +11,8 @@ #include #include #include -#include #include +#include #include #include #include @@ -24,7 +24,7 @@ struct uuid_generator boost::uuids::uuid operator()() { std::uint8_t data[16]; - boost::uint64_t x = rng(); + std::uint64_t x = rng(); std::memcpy(&data[0], &x, sizeof(x)); x = rng(); std::memcpy(&data[8], &x, sizeof(x)); @@ -36,7 +36,7 @@ struct uuid_generator }; using filter = boost::bloom::filter< - boost::uuids::uuid, 1, boost::bloom::multiblock >; + boost::uuids::uuid, 1, boost::bloom::multiblock >; static constexpr std::size_t num_elements = 10000; @@ -54,19 +54,19 @@ void save_filter(const filter& f, const char* filename) { std::ofstream out(filename, std::ios::binary | std::ios::trunc); std::size_t c=f.capacity(); - out.write((const char*) &c, sizeof(c)); /* save capacity (bits) */ + out.write(reinterpret_cast(&c), sizeof(c)); /* save capacity (bits) */ auto s = f.array(); - out.write((const char*) s.data(), s.size()); /* save array */ + out.write(reinterpret_cast(s.data()), s.size()); /* save array */ } filter load_filter(const char* filename) { std::ifstream in(filename, std::ios::binary); std::size_t c; - in.read((char*) &c, sizeof(c)); + in.read(reinterpret_cast(&c), sizeof(c)); filter f(c); auto s = f.array(); - in.read((char*) s.data(), s.size()); /* load array */ + in.read(reinterpret_cast(s.data()), s.size()); /* load array */ return f; } diff --git a/extra/boost_bloom.natvis b/extra/boost_bloom.natvis index 2b38267..764a7f5 100644 --- a/extra/boost_bloom.natvis +++ b/extra/boost_bloom.natvis @@ -12,12 +12,12 @@ See https://www.boost.org/libs/bloom for library home page. - + diff --git a/include/boost/bloom.hpp b/include/boost/bloom.hpp new file mode 100644 index 0000000..6424e80 --- /dev/null +++ b/include/boost/bloom.hpp @@ -0,0 +1,18 @@ +/* Copyright 2025 Joaquin M Lopez Munoz. + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * See https://www.boost.org/libs/bloom for library home page. + */ + +#ifndef BOOST_BLOOM_HPP +#define BOOST_BLOOM_HPP + +#include +#include +#include +#include +#include + +#endif diff --git a/include/boost/bloom/block.hpp b/include/boost/bloom/block.hpp index 2b5abf7..e0d0a5d 100644 --- a/include/boost/bloom/block.hpp +++ b/include/boost/bloom/block.hpp @@ -10,36 +10,61 @@ #define BOOST_BLOOM_BLOCK_HPP #include +#include #include -#include #include +#include namespace boost{ namespace bloom{ template struct block: - private detail::block_base,public detail::block_fpr_base + public detail::block_fpr_base, + private detail::block_base { static constexpr std::size_t k=K; using value_type=Block; - static inline void mark(value_type& x,boost::uint64_t hash) + /* NOLINTNEXTLINE(readability-redundant-inline-specifier) */ + static inline void mark(value_type& x,std::uint64_t hash) { - loop(hash,[&](boost::uint64_t h){x|=Block(1)<<(h&mask);}); + loop(hash,[&](std::uint64_t h){block_ops::set(x,h&mask);}); } - static inline bool check(const value_type& x,boost::uint64_t hash) + /* NOLINTNEXTLINE(readability-redundant-inline-specifier) */ + static inline bool check(const value_type& x,std::uint64_t hash) { - Block fp=0; - mark(fp,hash); - return (x&fp)==fp; + return check(x,hash,typename block_ops::is_extended_block{}); } private: using super=detail::block_base; using super::mask; using super::loop; + using super::loop_while; + using block_ops=detail::block_ops; + + /* NOLINTNEXTLINE(readability-redundant-inline-specifier) */ + static inline bool check( + const value_type& x,std::uint64_t hash, + std::false_type /* non-extended block */) + { + Block fp; + block_ops::zero(fp); + mark(fp,hash); + return block_ops::testc(x,fp); + } + + /* NOLINTNEXTLINE(readability-redundant-inline-specifier) */ + static inline bool check( + const value_type& x,std::uint64_t hash, + std::true_type /* extended block */) + { + return loop_while(hash,[&](std::uint64_t h){ + return block_ops::get_at_lsb(x,h&mask)&1; + }); + } }; } /* namespace bloom */ diff --git a/include/boost/bloom/detail/block_base.hpp b/include/boost/bloom/detail/block_base.hpp index 5babfe0..4861937 100644 --- a/include/boost/bloom/detail/block_base.hpp +++ b/include/boost/bloom/detail/block_base.hpp @@ -12,8 +12,9 @@ #include #include #include -#include +#include #include +#include namespace boost{ namespace bloom{ @@ -24,23 +25,31 @@ namespace detail{ #pragma warning(disable:4714) /* marked as __forceinline not inlined */ #endif -// TODO: describe +/* Validates type Block and provides common looping facilities for block + * and multiblock. + */ template struct block_base { - static constexpr std::size_t k=K; - static constexpr std::size_t hash_width=sizeof(boost::uint64_t)*CHAR_BIT; - static constexpr std::size_t block_width=sizeof(Block)*CHAR_BIT; static_assert( - (block_width&(block_width-1))==0, - "Block's size in bits must be a power of two"); + is_unsigned_integral_or_extended_unsigned_integral::value|| + ( + is_array_of< + Block,is_unsigned_integral_or_extended_unsigned_integral>::value&& + is_power_of_two::value>::value + ), + "Block must be an (extended) unsigned integral type or an array T[N] " + "with T an (extended) unsigned integral type and N a power of two"); + static constexpr std::size_t k=K; + static constexpr std::size_t hash_width=sizeof(std::uint64_t)*CHAR_BIT; + static constexpr std::size_t block_width=sizeof(Block)*CHAR_BIT; static constexpr std::size_t mask=block_width-1; static constexpr std::size_t shift=constexpr_bit_width(mask); static constexpr std::size_t rehash_k=(hash_width-shift)/shift; template - static BOOST_FORCEINLINE void loop(boost::uint64_t hash,F f) + static BOOST_FORCEINLINE void loop(std::uint64_t hash,F f) { for(std::size_t i=0;i + static BOOST_FORCEINLINE bool loop_while(std::uint64_t hash,F f) + { + for(std::size_t i=0;i>=shift; + if(!f(h))return false; + } + hash=detail::mulx64(hash); + } + auto h=hash; + for(std::size_t i=0;i>=shift; + if(!f(h))return false; + } + return true; + } }; #if defined(BOOST_MSVC) diff --git a/include/boost/bloom/detail/block_ops.hpp b/include/boost/bloom/detail/block_ops.hpp new file mode 100644 index 0000000..fcc8473 --- /dev/null +++ b/include/boost/bloom/detail/block_ops.hpp @@ -0,0 +1,95 @@ +/* Copyright 2025 Joaquin M Lopez Munoz. + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * See https://www.boost.org/libs/bloom for library home page. + */ + +#ifndef BOOST_BLOOM_DETAIL_BLOCK_OPS_HPP +#define BOOST_BLOOM_DETAIL_BLOCK_OPS_HPP + +#include +#include +#include + +namespace boost{ +namespace bloom{ +namespace detail{ + +#if defined(BOOST_MSVC) +#pragma warning(push) +#pragma warning(disable:4714) /* marked as __forceinline not inlined */ +#endif + +template +struct block_ops +{ + using is_extended_block=std::false_type; + using value_type=Block; + + static BOOST_FORCEINLINE void zero(Block& x) + { + x=0; + } + + static BOOST_FORCEINLINE void set(value_type& x,std::uint64_t n) + { + x|=Block(1)<(x>>n); + } + + static BOOST_FORCEINLINE void reduce( + int& res,const value_type& x,std::uint64_t n) + { + res&=get_at_lsb(x,n); + } + + static BOOST_FORCEINLINE bool testc(const value_type& x,const value_type& y) + { + return (x&y)==y; + } +}; + +template +struct block_ops +{ + using is_extended_block=std::true_type; + using value_type=Block[N]; + + static BOOST_FORCEINLINE void zero(value_type& x) + { + for(std::size_t i=0;i(x[n%N]>>(n/N)); + } + + static BOOST_FORCEINLINE void reduce( + int& res,const value_type& x,std::uint64_t n) + { + res&=get_at_lsb(x,n); + } +}; + +#if defined(BOOST_MSVC) +#pragma warning(pop) /* C4714 */ +#endif + + +} /* namespace detail */ +} /* namespace bloom */ +} /* namespace boost */ + +#endif diff --git a/include/boost/bloom/detail/constexpr_bit_width.hpp b/include/boost/bloom/detail/constexpr_bit_width.hpp index 0a9c0fe..88adf4d 100644 --- a/include/boost/bloom/detail/constexpr_bit_width.hpp +++ b/include/boost/bloom/detail/constexpr_bit_width.hpp @@ -17,7 +17,7 @@ namespace detail{ /* boost::core::bit_width is not always C++11 constexpr */ -inline constexpr std::size_t constexpr_bit_width(std::size_t x) +constexpr std::size_t constexpr_bit_width(std::size_t x) { return x?1+constexpr_bit_width(x>>1):0; } diff --git a/include/boost/bloom/detail/core.hpp b/include/boost/bloom/detail/core.hpp index c8367b7..d819360 100644 --- a/include/boost/bloom/detail/core.hpp +++ b/include/boost/bloom/detail/core.hpp @@ -19,9 +19,10 @@ #include #include #include -#include #include +#include #include +#include #include #include #include @@ -60,45 +61,46 @@ namespace detail{ #pragma warning(disable:4714) /* marked as __forceinline not inlined */ #endif -/* mcg_and_fastrange produces (pos,hash') from hash, where - * - m=mulx64(hash,range), mulx64 denotes extended multiplication - * - pos=high(m) - * - hash'=low(m) - * pos is uniformly distributed in [0,range) (see - * https://arxiv.org/pdf/1805.10941), whereas hash'<-hash is a multiplicative - * congruential generator of the form hash'<-hash*rng mod 2^64. This MCG - * generates long cycles when the initial value of hash is odd and - * rng = +-3 (mod 8), which is why we adjust hash and rng as seen below. As a - * result, the low bits of hash' are of poor quality, and the least - * significant bit in particular is always one. +/* fastrange_and_mcg produces (pos,hash') from hash as follows: + * - pos=high(mulx64(hash,range)) + * - hash'=c*m + * pos is uniformly distributed in [0,range) (see Lemire 2018 + * https://arxiv.org/pdf/1805.10941), whereas hash'<-hash is a multiplicative + * congruential generator using well-behaved multipliers c from Steele and + * Vigna 2021 https://arxiv.org/pdf/2001.05304 . To ensure the MCG generates + * long cycles the initial value of hash is adjusted to be odd, which implies + * that the least significant of hash' is always one. In general, the low bits + * of MCG-produced values are of low quality and we don't use them downstream. */ -struct mcg_and_fastrange +struct fastrange_and_mcg { - constexpr mcg_and_fastrange(std::size_t m)noexcept: - rng{ - m+( - (m%8<=3)?3-(m%8): - (m%8<=5)?5-(m%8): - 8-(m%8)+3) - } - {} + constexpr fastrange_and_mcg(std::size_t m)noexcept:rng{m}{} + /* NOLINTNEXTLINE(readability-redundant-inline-specifier) */ inline constexpr std::size_t range()const noexcept{return (std::size_t)rng;} - inline void prepare_hash(boost::uint64_t& hash)const noexcept + /* NOLINTNEXTLINE(readability-redundant-inline-specifier) */ + inline void prepare_hash(std::uint64_t& hash)const noexcept { hash|=1u; } - inline std::size_t next_position(boost::uint64_t& hash)const noexcept + /* NOLINTNEXTLINE(readability-redundant-inline-specifier) */ + inline std::size_t next_position(std::uint64_t& hash)const noexcept { boost::uint64_t hi; - hash=umul128(hash,rng,hi); + umul128(hash,rng,hi); + +#if ((((SIZE_MAX>>16)>>16)>>16)>>15)!=0 /* 64-bit mode (or higher) */ + hash*=0xf1357aea2e62a9c5ull; +#else /* 32-bit mode */ + hash*=0xe817fb2d; +#endif return (std::size_t)hi; } - boost::uint64_t rng; + std::uint64_t rng; }; /* used_value_size::value is Subfilter::used_value_size if it @@ -124,7 +126,7 @@ struct used_value_size< /* GCD with x,p > 1, p a power of two */ -inline constexpr std::size_t gcd_pow2(std::size_t x,std::size_t p) +constexpr std::size_t gcd_pow2(std::size_t x,std::size_t p) { /* x&-x: maximum power of two dividing x */ return (x&(0-x))::type* =nullptr> void swap_if(T&,T&){} template< - std::size_t K,typename Subfilter,std::size_t BucketSize,typename Allocator + std::size_t K,typename Subfilter,std::size_t Stride,typename Allocator > class filter_core:empty_value { @@ -192,23 +194,22 @@ private: detail::used_value_size::value; public: - static constexpr std::size_t bucket_size= - BucketSize?BucketSize:used_value_size; + static constexpr std::size_t stride=Stride?Stride:used_value_size; static_assert( - bucket_size<=used_value_size,"BucketSize can't exceed the block size"); + stride<=used_value_size,"Stride can't exceed the block size"); private: - static constexpr std::size_t tail_size=sizeof(block_type)-bucket_size; + static constexpr std::size_t tail_size=sizeof(block_type)-stride; static constexpr bool are_blocks_aligned= - (bucket_size%alignof(block_type)==0); + (stride%alignof(block_type)==0); static constexpr std::size_t cacheline=64; /* unknown at compile time */ static constexpr std::size_t initial_alignment= are_blocks_aligned? alignof(block_type)>cacheline?alignof(block_type):cacheline: 1; static constexpr std::size_t prefetched_cachelines= - 1+(block_size+cacheline-1-gcd_pow2(bucket_size,cacheline))/cacheline; - using hash_strategy=detail::mcg_and_fastrange; + 1+(block_size+cacheline-1-gcd_pow2(stride,cacheline))/cacheline; + using hash_strategy=detail::fastrange_and_mcg; public: using allocator_type=Allocator; @@ -362,15 +363,15 @@ public: boost::span array()noexcept { - return {ar.data?ar.buckets:nullptr,capacity()/CHAR_BIT}; + return {ar.data?ar.array:nullptr,capacity()/CHAR_BIT}; } boost::span array()const noexcept { - return {ar.data?ar.buckets:nullptr,capacity()/CHAR_BIT}; + return {ar.data?ar.array:nullptr,capacity()/CHAR_BIT}; } - BOOST_FORCEINLINE void insert(boost::uint64_t hash) + BOOST_FORCEINLINE void insert(std::uint64_t hash) { hs.prepare_hash(hash); for(auto n=k;n--;){ @@ -438,7 +439,7 @@ public: return *this; } - BOOST_FORCEINLINE bool may_contain(boost::uint64_t hash)const + BOOST_FORCEINLINE bool may_contain(std::uint64_t hash)const { hs.prepare_hash(hash); #if 1 @@ -464,7 +465,7 @@ public: { if(x.range()!=y.range())return false; else if(!x.ar.data)return true; - else return std::memcmp(x.ar.buckets,y.ar.buckets,x.used_array_size())==0; + else return std::memcmp(x.ar.array,y.ar.array,x.used_array_size())==0; } private: @@ -475,25 +476,25 @@ private: static std::size_t requested_range(std::size_t m) { - if(m>(used_value_size-bucket_size)*CHAR_BIT){ + if(m>(used_value_size-stride)*CHAR_BIT){ /* ensures filter_core{f.capacity()}.capacity()==f.capacity() */ - m-=(used_value_size-bucket_size)*CHAR_BIT; + m-=(used_value_size-stride)*CHAR_BIT; } return - (std::numeric_limits::max)()-m>=bucket_size*CHAR_BIT-1? - (m+bucket_size*CHAR_BIT-1)/(bucket_size*CHAR_BIT): - m/(bucket_size*CHAR_BIT); + (std::numeric_limits::max)()-m>=stride*CHAR_BIT-1? + (m+stride*CHAR_BIT-1)/(stride*CHAR_BIT): + m/(stride*CHAR_BIT); } static filter_array new_array(allocator_type& al,std::size_t rng) { if(rng){ auto p=allocator_allocate(al,space_for(rng)); - return {p,buckets_for(p)}; + return {p,array_for(p)}; } else{ /* To avoid dynamic allocation for zero capacity or moved-from filters, - * we point buckets to a statically allocated dummy array with all bits + * we point array to a statically allocated dummy array with all bits * set to one. This is good for read operations but not so for write * operations, where we need to resort to a null check on * filter_array::data. @@ -502,7 +503,7 @@ private: static struct {unsigned char x=-1;} dummy[space_for(hash_strategy{0}.range())]; - return {nullptr,buckets_for(reinterpret_cast(&dummy))}; + return {nullptr,array_for(reinterpret_cast(&dummy))}; } } @@ -513,13 +514,13 @@ private: void clear_bytes()noexcept { - std::memset(ar.buckets,0,used_array_size()); + std::memset(ar.array,0,used_array_size()); } void copy_bytes(const filter_core& x) { BOOST_ASSERT(range()==x.range()); - std::memcpy(ar.buckets,x.ar.buckets,used_array_size()); + std::memcpy(ar.array,x.ar.array,used_array_size()); } std::size_t range()const noexcept @@ -529,14 +530,14 @@ private: static constexpr std::size_t space_for(std::size_t rng)noexcept { - return (initial_alignment-1)+rng*bucket_size+tail_size; + return (initial_alignment-1)+rng*stride+tail_size; } - static unsigned char* buckets_for(unsigned char* p)noexcept + static unsigned char* array_for(unsigned char* p)noexcept { return p+ - (boost::uintptr_t(initial_alignment)- - boost::uintptr_t(p))%initial_alignment; + (std::uintptr_t(initial_alignment)- + std::uintptr_t(p))%initial_alignment; } std::size_t used_array_size()const noexcept @@ -546,7 +547,7 @@ private: static std::size_t used_array_size(std::size_t rng)noexcept { - return rng?rng*bucket_size+(used_value_size-bucket_size):0; + return rng?rng*stride+(used_value_size-stride):0; } static std::size_t unadjusted_capacity_for(std::size_t n,double fpr) @@ -609,7 +610,7 @@ private: static double fpr_for_c(double c) { - constexpr std::size_t w=(2*used_value_size-bucket_size)*CHAR_BIT; + constexpr std::size_t w=(2*used_value_size-stride)*CHAR_BIT; const double lambda=w*k/c; const double loglambda=std::log(lambda); double res=0.0; @@ -639,20 +640,20 @@ private: std::pow(1.0-std::exp(-(double)k_total/c),(double)k_total)); } - BOOST_FORCEINLINE bool get(const unsigned char* p,boost::uint64_t hash)const + BOOST_FORCEINLINE bool get(const unsigned char* p,std::uint64_t hash)const { return get(p,hash,std::integral_constant{}); } BOOST_FORCEINLINE bool get( - const unsigned char* p,boost::uint64_t hash, + const unsigned char* p,std::uint64_t hash, std::true_type /* blocks aligned */)const { return subfilter::check(*reinterpret_cast(p),hash); } BOOST_FORCEINLINE bool get( - const unsigned char* p,boost::uint64_t hash, + const unsigned char* p,std::uint64_t hash, std::false_type /* blocks not aligned */)const { block_type x; @@ -660,20 +661,20 @@ private: return subfilter::check(x,hash); } - BOOST_FORCEINLINE void set(unsigned char* p,boost::uint64_t hash) + BOOST_FORCEINLINE void set(unsigned char* p,std::uint64_t hash) { return set(p,hash,std::integral_constant{}); } BOOST_FORCEINLINE void set( - unsigned char* p,boost::uint64_t hash, + unsigned char* p,std::uint64_t hash, std::true_type /* blocks aligned */) { subfilter::mark(*reinterpret_cast(p),hash); } BOOST_FORCEINLINE void set( - unsigned char* p,boost::uint64_t hash, + unsigned char* p,std::uint64_t hash, std::false_type /* blocks not aligned */) { block_type x; @@ -683,9 +684,9 @@ private: } BOOST_FORCEINLINE - unsigned char* next_element(boost::uint64_t& h)noexcept + unsigned char* next_element(std::uint64_t& h)noexcept { - auto p=ar.buckets+hs.next_position(h)*bucket_size; + auto p=ar.array+hs.next_position(h)*stride; for(std::size_t i=0;i #include #include -#include #include +#include namespace boost{ namespace bloom{ @@ -29,9 +29,9 @@ struct fast_multiblock32:detail::multiblock_fpr_base { static constexpr std::size_t k=K; using value_type=__m256i[(k+7)/8]; - static constexpr std::size_t used_value_size=sizeof(boost::uint32_t)*k; + static constexpr std::size_t used_value_size=sizeof(std::uint32_t)*k; - static BOOST_FORCEINLINE void mark(value_type& x,boost::uint64_t hash) + static BOOST_FORCEINLINE void mark(value_type& x,std::uint64_t hash) { for(std::size_t i=0;i } } - static BOOST_FORCEINLINE bool check(const value_type& x,boost::uint64_t hash) + static BOOST_FORCEINLINE bool check(const value_type& x,std::uint64_t hash) { for(std::size_t i=0;i private: static BOOST_FORCEINLINE __m256i make_m256i( - boost::uint64_t hash,std::size_t kp) + std::uint64_t hash,std::size_t kp) { const __m256i ones[8]={ _mm256_set_epi32(0,0,0,0,0,0,0,1), @@ -76,14 +76,14 @@ private: } static BOOST_FORCEINLINE void mark_m256i( - __m256i& x,boost::uint64_t hash,std::size_t kp) + __m256i& x,std::uint64_t hash,std::size_t kp) { __m256i h=make_m256i(hash,kp); x=_mm256_or_si256(x,h); } static BOOST_FORCEINLINE bool check_m256i( - const __m256i& x,boost::uint64_t hash,std::size_t kp) + const __m256i& x,std::uint64_t hash,std::size_t kp) { __m256i h=make_m256i(hash,kp); return _mm256_testc_si256(x,h); diff --git a/include/boost/bloom/detail/fast_multiblock32_neon.hpp b/include/boost/bloom/detail/fast_multiblock32_neon.hpp index 327ed67..f9a0311 100644 --- a/include/boost/bloom/detail/fast_multiblock32_neon.hpp +++ b/include/boost/bloom/detail/fast_multiblock32_neon.hpp @@ -13,8 +13,8 @@ #include #include #include -#include #include +#include namespace boost{ namespace bloom{ @@ -28,11 +28,11 @@ namespace bloom{ #ifdef _MSC_VER #define BOOST_BLOOM_INIT_U32X4(w,x,y,z) \ -{(boost::uint32_t(w)+(unsigned long long(x)<<32)), \ - (boost::uint32_t(y)+(unsigned long long(z)<<32))} +{(std::uint32_t(w)+(unsigned long long(x)<<32)), \ + (std::uint32_t(y)+(unsigned long long(z)<<32))} #else #define BOOST_BLOOM_INIT_U32X4(w,x,y,z) \ -{boost::uint32_t(w),boost::uint32_t(x),boost::uint32_t(y),boost::uint32_t(z)} +{std::uint32_t(w),std::uint32_t(x),std::uint32_t(y),std::uint32_t(z)} #endif #define BOOST_BLOOM_INIT_U32X4X2(w0,x0,y0,z0,w1,x1,y1,z1) \ @@ -43,9 +43,9 @@ struct fast_multiblock32:detail::multiblock_fpr_base { static constexpr std::size_t k=K; using value_type=uint32x4x2_t[(k+7)/8]; - static constexpr std::size_t used_value_size=sizeof(boost::uint32_t)*k; + static constexpr std::size_t used_value_size=sizeof(std::uint32_t)*k; - static BOOST_FORCEINLINE void mark(value_type& x,boost::uint64_t hash) + static BOOST_FORCEINLINE void mark(value_type& x,std::uint64_t hash) { for(std::size_t i=0;i } } - static BOOST_FORCEINLINE bool check(const value_type& x,boost::uint64_t hash) + static BOOST_FORCEINLINE bool check(const value_type& x,std::uint64_t hash) { for(std::size_t i=0;i private: static BOOST_FORCEINLINE uint32x4x2_t make_uint32x4x2_t( - boost::uint64_t hash,std::size_t kp) + std::uint64_t hash,std::size_t kp) { static const uint32x4x2_t ones[8]={ BOOST_BLOOM_INIT_U32X4X2(1,0,0,0,0,0,0,0), @@ -101,7 +101,7 @@ private: } static BOOST_FORCEINLINE void mark_uint32x4x2_t( - uint32x4x2_t& x,boost::uint64_t hash,std::size_t kp) + uint32x4x2_t& x,std::uint64_t hash,std::size_t kp) { uint32x4x2_t h=make_uint32x4x2_t(hash,kp); x.val[0]=vorrq_u32(x.val[0],h.val[0]); @@ -109,7 +109,7 @@ private: } static BOOST_FORCEINLINE bool check_uint32x4x2_t( - const uint32x4x2_t& x,boost::uint64_t hash,std::size_t kp) + const uint32x4x2_t& x,std::uint64_t hash,std::size_t kp) { uint32x4x2_t h=make_uint32x4x2_t(hash,kp); uint32x4_t lo=vtstq_u32(x.val[0],h.val[0]); diff --git a/include/boost/bloom/detail/fast_multiblock32_sse2.hpp b/include/boost/bloom/detail/fast_multiblock32_sse2.hpp index 09f64c5..fab6d01 100644 --- a/include/boost/bloom/detail/fast_multiblock32_sse2.hpp +++ b/include/boost/bloom/detail/fast_multiblock32_sse2.hpp @@ -13,8 +13,8 @@ #include #include #include -#include #include +#include #ifdef __SSE4_1__ #include @@ -35,6 +35,7 @@ struct m128ix2 __m128i lo,hi; }; +/* NOLINTNEXTLINE(readability-redundant-inline-specifier) */ static inline int mm_testc_si128(__m128i x,__m128i y) { #ifdef __SSE4_1__ @@ -51,9 +52,9 @@ struct fast_multiblock32:detail::multiblock_fpr_base { static constexpr std::size_t k=K; using value_type=detail::m128ix2[(k+7)/8]; - static constexpr std::size_t used_value_size=sizeof(boost::uint32_t)*k; + static constexpr std::size_t used_value_size=sizeof(std::uint32_t)*k; - static BOOST_FORCEINLINE void mark(value_type& x,boost::uint64_t hash) + static BOOST_FORCEINLINE void mark(value_type& x,std::uint64_t hash) { for(std::size_t i=0;i } } - static BOOST_FORCEINLINE bool check(const value_type& x,boost::uint64_t hash) + static BOOST_FORCEINLINE bool check(const value_type& x,std::uint64_t hash) { for(std::size_t i=0;i private: static BOOST_FORCEINLINE detail::m128ix2 make_m128ix2( - boost::uint64_t hash,std::size_t kp) + std::uint64_t hash,std::size_t kp) { - const boost::uint32_t mask=boost::uint32_t(31)<<23, - exp=boost::uint32_t(127)<<23; + const std::uint32_t mask=std::uint32_t(31)<<23, + exp=std::uint32_t(127)<<23; const __m128i exps[4]={ _mm_set_epi32( 0 , 0 , 0 ,exp), _mm_set_epi32( 0 , 0 ,exp,exp), @@ -113,7 +114,7 @@ private: } static BOOST_FORCEINLINE void mark_m128ix2( - detail::m128ix2& x,boost::uint64_t hash,std::size_t kp) + detail::m128ix2& x,std::uint64_t hash,std::size_t kp) { detail::m128ix2 h=make_m128ix2(hash,kp); x.lo=_mm_or_si128(x.lo,h.lo); @@ -121,7 +122,7 @@ private: } static BOOST_FORCEINLINE bool check_m128ix2( - const detail::m128ix2& x,boost::uint64_t hash,std::size_t kp) + const detail::m128ix2& x,std::uint64_t hash,std::size_t kp) { detail::m128ix2 h=make_m128ix2(hash,kp); auto res=detail::mm_testc_si128(x.lo,h.lo); diff --git a/include/boost/bloom/detail/fast_multiblock64_avx2.hpp b/include/boost/bloom/detail/fast_multiblock64_avx2.hpp index 6c5cf10..6843422 100644 --- a/include/boost/bloom/detail/fast_multiblock64_avx2.hpp +++ b/include/boost/bloom/detail/fast_multiblock64_avx2.hpp @@ -13,8 +13,8 @@ #include #include #include -#include #include +#include namespace boost{ namespace bloom{ @@ -38,9 +38,9 @@ struct fast_multiblock64:detail::multiblock_fpr_base { static constexpr std::size_t k=K; using value_type=detail::m256ix2[(k+7)/8]; - static constexpr std::size_t used_value_size=sizeof(boost::uint64_t)*k; + static constexpr std::size_t used_value_size=sizeof(std::uint64_t)*k; - static BOOST_FORCEINLINE void mark(value_type& x,boost::uint64_t hash) + static BOOST_FORCEINLINE void mark(value_type& x,std::uint64_t hash) { for(int i=0;i } } - static BOOST_FORCEINLINE bool check(const value_type& x,boost::uint64_t hash) + static BOOST_FORCEINLINE bool check(const value_type& x,std::uint64_t hash) { for(int i=0;i private: static BOOST_FORCEINLINE detail::m256ix2 make_m256ix2( - boost::uint64_t hash,std::size_t kp) + std::uint64_t hash,std::size_t kp) { const detail::m256ix2 ones[8]={ {_mm256_set_epi64x(0,0,0,1),_mm256_set_epi64x(0,0,0,0)}, @@ -92,7 +92,7 @@ private: } static BOOST_FORCEINLINE void mark_m256ix2( - detail::m256ix2& x,boost::uint64_t hash,std::size_t kp) + detail::m256ix2& x,std::uint64_t hash,std::size_t kp) { detail::m256ix2 h=make_m256ix2(hash,kp); x.lo=_mm256_or_si256(x.lo,h.lo); @@ -100,7 +100,7 @@ private: } static BOOST_FORCEINLINE bool check_m256ix2( - const detail::m256ix2& x,boost::uint64_t hash,std::size_t kp) + const detail::m256ix2& x,std::uint64_t hash,std::size_t kp) { detail::m256ix2 h=make_m256ix2(hash,kp); auto res=_mm256_testc_si256(x.lo,h.lo); diff --git a/include/boost/bloom/detail/mulx64.hpp b/include/boost/bloom/detail/mulx64.hpp index d2bff00..17f6b25 100644 --- a/include/boost/bloom/detail/mulx64.hpp +++ b/include/boost/bloom/detail/mulx64.hpp @@ -10,9 +10,9 @@ #ifndef BOOST_BLOOM_DETAIL_MULX64_HPP #define BOOST_BLOOM_DETAIL_MULX64_HPP -#include #include #include +#include #if defined(_MSC_VER)&&!defined(__clang__) #include @@ -24,16 +24,16 @@ namespace detail{ #if defined(_MSC_VER)&&defined(_M_X64)&&!defined(__clang__) -__forceinline boost::uint64_t umul128( - boost::uint64_t x,boost::uint64_t y,boost::uint64_t& hi) +__forceinline std::uint64_t umul128( + std::uint64_t x,std::uint64_t y,std::uint64_t& hi) { return _umul128(x,y,&hi); } #elif defined(_MSC_VER)&&defined(_M_ARM64)&&!defined(__clang__) -__forceinline boost::uint64_t umul128( - boost::uint64_t x,boost::uint64_t y,boost::uint64_t& hi) +__forceinline std::uint64_t umul128( + std::uint64_t x,std::uint64_t y,std::uint64_t& hi) { hi=__umulh(x,y); return x*y; @@ -41,40 +41,42 @@ __forceinline boost::uint64_t umul128( #elif defined(__SIZEOF_INT128__) -inline boost::uint64_t umul128( - boost::uint64_t x,boost::uint64_t y,boost::uint64_t& hi) +/* NOLINTNEXTLINE(readability-redundant-inline-specifier) */ +inline std::uint64_t umul128( + std::uint64_t x,std::uint64_t y,std::uint64_t& hi) { __uint128_t r=(__uint128_t)x*y; - hi=(boost::uint64_t)(r>>64); - return (boost::uint64_t)r; + hi=(std::uint64_t)(r>>64); + return (std::uint64_t)r; } #else -inline boost::uint64_t umul128( - boost::uint64_t x,boost::uint64_t y,boost::uint64_t& hi) +/* NOLINTNEXTLINE(readability-redundant-inline-specifier) */ +inline std::uint64_t umul128( + std::uint64_t x,std::uint64_t y,std::uint64_t& hi) { - boost::uint64_t x1=(boost::uint32_t)x; - boost::uint64_t x2=x >> 32; + std::uint64_t x1=(std::uint32_t)x; + std::uint64_t x2=x >> 32; - boost::uint64_t y1=(boost::uint32_t)y; - boost::uint64_t y2=y >> 32; + std::uint64_t y1=(std::uint32_t)y; + std::uint64_t y2=y >> 32; - boost::uint64_t r3=x2*y2; + std::uint64_t r3=x2*y2; - boost::uint64_t r2a=x1*y2; + std::uint64_t r2a=x1*y2; r3+=r2a>>32; - boost::uint64_t r2b=x2*y1; + std::uint64_t r2b=x2*y1; r3+=r2b>>32; - boost::uint64_t r1=x1*y1; + std::uint64_t r1=x1*y1; - boost::uint64_t r2=(r1>>32)+(boost::uint32_t)r2a+(boost::uint32_t)r2b; + std::uint64_t r2=(r1>>32)+(std::uint32_t)r2a+(std::uint32_t)r2b; - r1=(r2<<32)+(boost::uint32_t)r1; + r1=(r2<<32)+(std::uint32_t)r1; r3+=r2>>32; hi=r3; @@ -83,11 +85,12 @@ inline boost::uint64_t umul128( #endif -inline boost::uint64_t mulx64(boost::uint64_t x)noexcept +/* NOLINTNEXTLINE(readability-redundant-inline-specifier) */ +inline std::uint64_t mulx64(std::uint64_t x)noexcept { /* multiplier is 2^64/phi */ - boost::uint64_t hi; - boost::uint64_t lo=umul128(x,0x9E3779B97F4A7C15ull,hi); + std::uint64_t hi; + std::uint64_t lo=umul128(x,0x9E3779B97F4A7C15ull,hi); return hi^lo; } diff --git a/include/boost/bloom/detail/type_traits.hpp b/include/boost/bloom/detail/type_traits.hpp index 35b9b3a..1bda6e8 100644 --- a/include/boost/bloom/detail/type_traits.hpp +++ b/include/boost/bloom/detail/type_traits.hpp @@ -11,7 +11,9 @@ #ifndef BOOST_BLOOM_DETAIL_TYPE_TRAITS_HPP #define BOOST_BLOOM_DETAIL_TYPE_TRAITS_HPP +#include #include +#include #include #include @@ -86,6 +88,54 @@ template using enable_if_transparent_t= typename std::enable_if::value,Q>::type; +template +struct is_integral_or_extended_integral:std::is_integral{}; +template +struct is_unsigned_or_extended_unsigned:std::is_unsigned{}; + +#if defined(__SIZEOF_INT128__) + +#if defined(BOOST_GCC) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpedantic" +#endif + +template<> +struct is_integral_or_extended_integral<__int128>:std::true_type{}; +template<> +struct is_integral_or_extended_integral:std::true_type{}; +template<> +struct is_unsigned_or_extended_unsigned:std::true_type{}; + +#if defined(BOOST_GCC) +#pragma GCC diagnostic pop +#endif + +#endif + +template +struct is_unsigned_integral_or_extended_unsigned_integral: + std::integral_constant< + bool, + is_integral_or_extended_integral::value&& + is_unsigned_or_extended_unsigned::value + > +{}; + +template class Trait> +struct is_array_of:std::false_type{}; + +template class Trait> +struct is_array_of:Trait{}; + +template struct array_size: + std::integral_constant{}; +template struct array_size: + std::integral_constant{}; + +template +struct is_power_of_two:std::integral_constant{}; + } /* namespace detail */ } /* namespace bloom */ } /* namespace boost */ diff --git a/include/boost/bloom/fast_multiblock32.hpp b/include/boost/bloom/fast_multiblock32.hpp index 644fe36..dd0f3c0 100644 --- a/include/boost/bloom/fast_multiblock32.hpp +++ b/include/boost/bloom/fast_multiblock32.hpp @@ -21,14 +21,14 @@ #include #else /* fallback */ #include -#include #include +#include namespace boost{ namespace bloom{ template -using fast_multiblock32=multiblock; +using fast_multiblock32=multiblock; } /* namespace bloom */ } /* namespace boost */ diff --git a/include/boost/bloom/fast_multiblock64.hpp b/include/boost/bloom/fast_multiblock64.hpp index 539ec7a..753e37a 100644 --- a/include/boost/bloom/fast_multiblock64.hpp +++ b/include/boost/bloom/fast_multiblock64.hpp @@ -15,14 +15,14 @@ #include #else /* fallback */ #include -#include #include +#include namespace boost{ namespace bloom{ template -using fast_multiblock64=multiblock; +using fast_multiblock64=multiblock; } /* namespace bloom */ } /* namespace boost */ diff --git a/include/boost/bloom/filter.hpp b/include/boost/bloom/filter.hpp index 3b29a88..daf70d5 100644 --- a/include/boost/bloom/filter.hpp +++ b/include/boost/bloom/filter.hpp @@ -17,10 +17,10 @@ #include #include #include +#include #include #include -#include -#include // TODO: internalize? +#include #include #include #include @@ -37,57 +37,29 @@ namespace detail{ * filter mixes hash results with mulx64 if the hash is not marked as * avalanching, i.e. it's not of good quality (see * ), or if std::size_t is less than 64 bits - * (mixing policies promote to boost::uint64_t). + * (mixing policies promote to std::uint64_t). */ struct no_mix_policy { template - static inline boost::uint64_t mix(const Hash& h,const T& x) + /* NOLINTNEXTLINE(readability-redundant-inline-specifier) */ + static inline std::uint64_t mix(const Hash& h,const T& x) { - return (boost::uint64_t)h(x); + return (std::uint64_t)h(x); } }; struct mulx64_mix_policy { template - static inline boost::uint64_t mix(const Hash& h,const T& x) + /* NOLINTNEXTLINE(readability-redundant-inline-specifier) */ + static inline std::uint64_t mix(const Hash& h,const T& x) { - return mulx64((boost::uint64_t)h(x)); + return mulx64((std::uint64_t)h(x)); } }; -template -class allocator_constructed -{ -public: - template - allocator_constructed(const Allocator& al_,Args&&... args):al{al_} - { - allocator_construct(al,std::addressof(u.x),std::forward(args)...); - } - - ~allocator_constructed() - { - allocator_destroy(al,std::addressof(u.x)); - } - - const T& value()const noexcept{return u.x;} - -private: - union uninitialized_value - { - uninitialized_value(){} - ~uninitialized_value(){} - - T x; - }; - - uninitialized_value u; - Allocator al; -}; - } /* namespace detail */ #if defined(BOOST_MSVC) @@ -97,8 +69,8 @@ private: template< typename T,std::size_t K, - typename Subfilter=block,std::size_t BucketSize=0, - typename Hash=boost::hash,typename Allocator=std::allocator + typename Subfilter=block,std::size_t Stride=0, + typename Hash=boost::hash,typename Allocator=std::allocator > class @@ -108,20 +80,18 @@ __declspec(empty_bases) /* activate EBO with multiple inheritance */ filter: detail::filter_core< - K,Subfilter,BucketSize,allocator_rebind_t + K,Subfilter,Stride,allocator_rebind_t >, empty_value { BOOST_BLOOM_STATIC_ASSERT_IS_CV_UNQUALIFIED_OBJECT(T); static_assert( - std::is_same>::value, - "Allocator's value_type must be T"); - using super=detail::filter_core< - K,Subfilter,BucketSize,allocator_rebind_t - >; + std::is_same>::value, + "Allocator's value_type must be unsigned char"); + using super=detail::filter_core; using mix_policy=typename std::conditional< - unordered::hash_is_avalanching::value&& - sizeof(std::size_t)>=sizeof(boost::uint64_t), + boost::hash_is_avalanching::value&& + sizeof(std::size_t)>=sizeof(std::uint64_t), detail::no_mix_policy, detail::mulx64_mix_policy >::type; @@ -130,7 +100,7 @@ public: using value_type=T; using super::k; using subfilter=typename super::subfilter; - using super::bucket_size; + using super::stride; using hasher=Hash; using allocator_type=Allocator; using size_type=typename super::size_type; @@ -258,23 +228,6 @@ public: using super::fpr_for; using super::array; - template - BOOST_FORCEINLINE void emplace(Args&&... args) - { - insert(detail::allocator_constructed{ - get_allocator(),std::forward(args)...}.value()); - } - - template< - typename U, - typename std::enable_if< - std::is_same>::value>::type* =nullptr - > - BOOST_FORCEINLINE void emplace(U&& x) - { - insert(x); /* avoid value_type construction */ - } - BOOST_FORCEINLINE void insert(const T& x) { super::insert(hash_for(x)); @@ -292,7 +245,7 @@ public: template void insert(InputIterator first,InputIterator last) { - while(first!=last)emplace(*first++); + while(first!=last)insert(*first++); } void insert(std::initializer_list il) @@ -346,10 +299,10 @@ public: private: template< - typename T1,std::size_t K1,typename S,std::size_t B,typename H,typename A + typename T1,std::size_t K1,typename SF,std::size_t S,typename H,typename A > bool friend operator==( - const filter& x,const filter& y); + const filter& x,const filter& y); using hash_base=empty_value; @@ -357,33 +310,34 @@ private: Hash& h(){return hash_base::get();} template - inline boost::uint64_t hash_for(const U& x)const + /* NOLINTNEXTLINE(readability-redundant-inline-specifier) */ + inline std::uint64_t hash_for(const U& x)const { return mix_policy::mix(h(),x); } }; template< - typename T,std::size_t K,typename S,std::size_t B,typename H,typename A + typename T,std::size_t K,typename SF,std::size_t S,typename H,typename A > -bool operator==(const filter& x,const filter& y) +bool operator==(const filter& x,const filter& y) { - using super=typename filter::super; + using super=typename filter::super; return static_cast(x)==static_cast(y); } template< - typename T,std::size_t K,typename S,std::size_t B,typename H,typename A + typename T,std::size_t K,typename SF,std::size_t S,typename H,typename A > -bool operator!=(const filter& x,const filter& y) +bool operator!=(const filter& x,const filter& y) { return !(x==y); } template< - typename T,std::size_t K,typename S,std::size_t B,typename H,typename A + typename T,std::size_t K,typename SF,std::size_t S,typename H,typename A > -void swap(filter& x,filter& y) +void swap(filter& x,filter& y) noexcept(noexcept(x.swap(y))) { x.swap(y); diff --git a/include/boost/bloom/multiblock.hpp b/include/boost/bloom/multiblock.hpp index 10c6d24..ffd0386 100644 --- a/include/boost/bloom/multiblock.hpp +++ b/include/boost/bloom/multiblock.hpp @@ -10,31 +10,35 @@ #define BOOST_BLOOM_MULTIBLOCK_HPP #include +#include #include -#include #include +#include namespace boost{ namespace bloom{ template struct multiblock: - private detail::block_base,public detail::multiblock_fpr_base + public detail::multiblock_fpr_base, + private detail::block_base { static constexpr std::size_t k=K; using value_type=Block[k]; - static inline void mark(value_type& x,boost::uint64_t hash) + /* NOLINTNEXTLINE(readability-redundant-inline-specifier) */ + static inline void mark(value_type& x,std::uint64_t hash) { std::size_t i=0; - loop(hash,[&](boost::uint64_t h){x[i++]|=Block(1)<<(h&mask);}); + loop(hash,[&](std::uint64_t h){block_ops::set(x[i++],h&mask);}); } - static inline bool check(const value_type& x,boost::uint64_t hash) + /* NOLINTNEXTLINE(readability-redundant-inline-specifier) */ + static inline bool check(const value_type& x,std::uint64_t hash) { - Block res=1; + int res=1; std::size_t i=0; - loop(hash,[&](boost::uint64_t h){res&=(x[i++]>>(h&mask));}); + loop(hash,[&](std::uint64_t h){block_ops::reduce(res,x[i++],h&mask);}); return res; } @@ -42,6 +46,7 @@ private: using super=detail::block_base; using super::mask; using super::loop; + using block_ops=detail::block_ops; }; } /* namespace bloom */ diff --git a/index.html b/index.html index 6990ea7..589bba0 100644 --- a/index.html +++ b/index.html @@ -8,12 +8,12 @@ - + Boost.Bloom Documentation Automatic redirection failed, please go to -doc/index.html +doc/html/bloom.html \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..05149bc --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,13 @@ +# Copyright 2018, 2019, 2021, 2022 Peter Dimov +# Copyright 2025 Joaquin M Lopez Muñoz +# Distributed under the Boost Software License, Version 1.0. +# See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt + +include(BoostTestJamfile OPTIONAL RESULT_VARIABLE HAVE_BOOST_TEST) + +if(HAVE_BOOST_TEST) + +boost_test_jamfile(FILE Jamfile.v2 + LINK_LIBRARIES Boost::bloom Boost::core Boost::mp11) + +endif() \ No newline at end of file diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index 9c8ce92..641c44f 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -18,12 +18,11 @@ project msvc:-D_SCL_SECURE_NO_WARNINGS ; -test-suite "bloom" : - [ run test_array.cpp ] - [ run test_capacity.cpp ] - [ run test_combination.cpp ] - [ run test_comparison.cpp ] - [ run test_construction.cpp ] - [ run test_fpr.cpp ] - [ run test_insertion.cpp ] - ; +run test_array.cpp ; +run test_boost_bloom_hpp.cpp ; +run test_capacity.cpp ; +run test_combination.cpp ; +run test_comparison.cpp ; +run test_construction.cpp ; +run test_fpr.cpp ; +run test_insertion.cpp ; \ No newline at end of file diff --git a/test/test_boost_bloom_hpp.cpp b/test/test_boost_bloom_hpp.cpp new file mode 100644 index 0000000..370e1e5 --- /dev/null +++ b/test/test_boost_bloom_hpp.cpp @@ -0,0 +1,25 @@ +/* Copyright 2025 Joaquin M Lopez Munoz. + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * See https://www.boost.org/libs/bloom for library home page. + */ + +#include +#include + +struct use_types +{ + using type1=boost::bloom::filter; + using type2=boost::bloom::block; + using type3=boost::bloom::multiblock; + using type4=boost::bloom::fast_multiblock32<1>; + using type5=boost::bloom::fast_multiblock64<1>; +}; + +int main() +{ + (void)use_types{}; + return boost::report_errors(); +} diff --git a/test/test_capacity.cpp b/test/test_capacity.cpp index 7b1f0d8..396cb03 100644 --- a/test/test_capacity.cpp +++ b/test/test_capacity.cpp @@ -43,9 +43,7 @@ struct counting_allocator template void test_capacity() { - using filter=realloc_filter< - Filter,counting_allocator - >; + using filter=realloc_filter>; ValueFactory fac; diff --git a/test/test_construction.cpp b/test/test_construction.cpp index 33f6870..67030dd 100644 --- a/test/test_construction.cpp +++ b/test/test_construction.cpp @@ -88,7 +88,7 @@ void test_pocxx() static constexpr auto always_equal=AlwaysEqual::value; using filter=realloc_filter< rehash_filter>, - stateful_allocator + stateful_allocator >; using value_type=typename filter::value_type; using hasher=typename filter::hasher; @@ -170,7 +170,7 @@ void test_construction() { using filter=realloc_filter< rehash_filter>, - stateful_allocator + stateful_allocator >; using value_type=typename filter::value_type; using hasher=typename filter::hasher; @@ -443,65 +443,6 @@ void test_construction() } } -struct allocator_only_constructible -{ - allocator_only_constructible()=delete; - ~allocator_only_constructible()=delete; - - int n; -}; - -struct allocator_only_constructible_hash -{ - using is_transparent=void; - - std::size_t operator()(const allocator_only_constructible& x)const - { - return (*this)(x.n); - } - - std::size_t operator()(int n)const - { - return boost::hash{}(n); - } -}; - -template -struct constructing_allocator -{ - using value_type=T; - - constructing_allocator()=default; - template - constructing_allocator(const constructing_allocator&){} - - T* allocate(std::size_t n) - { - return static_cast(::operator new(n*sizeof(T))); - } - - void deallocate(T* p,std::size_t){::operator delete(p);} - - void construct(allocator_only_constructible* p,int n){p->n=n;} - void destroy(allocator_only_constructible* p){} - - bool operator==(const constructing_allocator& x)const{return true;} - bool operator!=(const constructing_allocator& x)const{return false;} -}; - -void test_allocator_aware_construction() -{ - using value_type=allocator_only_constructible; - using filter=boost::bloom::filter< - value_type,5,boost::bloom::block,0, - allocator_only_constructible_hash,constructing_allocator - >; - - filter f(1000); - f.emplace(42); - BOOST_TEST(f.may_contain(42)); -} - struct lambda { template @@ -517,6 +458,5 @@ struct lambda int main() { boost::mp11::mp_for_each(lambda{}); - test_allocator_aware_construction(); return boost::report_errors(); } diff --git a/test/test_fpr.cpp b/test/test_fpr.cpp index d057792..f6ee8fa 100644 --- a/test/test_fpr.cpp +++ b/test/test_fpr.cpp @@ -55,10 +55,7 @@ void test_fpr() { using filter=rehash_filter< revalue_filter< - realloc_filter< - Filter, - throwing_allocator - >, + realloc_filter>, std::string >, boost::hash diff --git a/test/test_insertion.cpp b/test/test_insertion.cpp index bbee266..f0bb43e 100644 --- a/test/test_insertion.cpp +++ b/test/test_insertion.cpp @@ -50,21 +50,6 @@ void test_insertion() filter f(10000); ValueFactory fac; - { - auto x=fac(); - f.emplace(x,0,"hello",3.1416); - BOOST_TEST(f.may_contain(value_type{x,1})); - } - { - auto x=fac(); - f.emplace(value_type{x,0,"boost"}); /* must avoid value_type move ctor */ - BOOST_TEST(f.may_contain(value_type{x,1})); - } - { - value_type x{fac(),0,"boost"}; - f.emplace(x); /* same with copy ctor */ - BOOST_TEST(f.may_contain(x)); - } { value_type x{fac(),0}; f.insert(const_cast(x)); diff --git a/test/test_types.hpp b/test/test_types.hpp index ecd1b08..61c721b 100644 --- a/test/test_types.hpp +++ b/test/test_types.hpp @@ -14,10 +14,10 @@ #include #include #include -#include #include #include #include +#include #include using test_types=boost::mp11::mp_list< @@ -25,10 +25,16 @@ using test_types=boost::mp11::mp_list< int,2 >, boost::bloom::filter< - std::string,1,boost::bloom::block,1 + std::string,1,boost::bloom::block,1 >, boost::bloom::filter< - std::size_t,1,boost::bloom::multiblock + int,1,boost::bloom::block + >, + boost::bloom::filter< + std::size_t,1,boost::bloom::multiblock + >, + boost::bloom::filter< + std::size_t,1,boost::bloom::multiblock,1 >, boost::bloom::filter< unsigned char,1,boost::bloom::fast_multiblock32<5>,2 diff --git a/test/test_utilities.hpp b/test/test_utilities.hpp index 9c1c2cc..3cb1960 100644 --- a/test/test_utilities.hpp +++ b/test/test_utilities.hpp @@ -10,7 +10,6 @@ #define BOOST_BLOOM_TEST_TEST_UTILITIES_HPP #include -#include #include #include #include @@ -44,7 +43,7 @@ template< > struct revalue_filter_impl,U> { - using type=boost::bloom::filter>; + using type=boost::bloom::filter; }; template