├── .bazelignore ├── .bazelrc ├── .bazelversion ├── .clang-format ├── .github └── workflows │ ├── bazel-macos.yml │ ├── bazel.yml │ ├── cmake-codecov.yml │ ├── cmake-macos.yml_fails │ ├── cmake-windows.yml │ ├── cmake.yml │ └── codcecov.yml_old ├── .gitignore ├── BUILD ├── CHANGELOG.md ├── CMakeLists.txt ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── TODO.txt ├── WORKSPACE ├── benchmark ├── BUILD ├── CMakeLists.txt ├── benchmark_util.h ├── bit_ops_benchmark.cc ├── bpt_erase_benchmark.cc ├── bpt_erase_it_benchmark.cc ├── bpt_insert_benchmark.cc ├── bpt_iter_benchmark.cc ├── bpt_lower_bound_benchmark.cc ├── bpt_results-2022-12-05.txt ├── bpt_results-2023-02.txt ├── count_mm_d_benchmark.cc ├── erase_benchmark.cc ├── erase_d_benchmark.cc ├── extent_benchmark.cc ├── extent_benchmark_weird.cc ├── find_benchmark.cc ├── hd_erase_d_benchmark.cc ├── hd_insert_d_benchmark.cc ├── hd_knn_d_benchmark.cc ├── hd_query_d_benchmark.cc ├── insert_benchmark.cc ├── insert_box_d_benchmark.cc ├── insert_d_benchmark.cc ├── knn_d_benchmark.cc ├── knn_mm_d_benchmark.cc ├── logging.h ├── query_benchmark.cc ├── query_box_d_benchmark.cc ├── query_d_benchmark.cc ├── query_mm_box_d_benchmark.cc ├── query_mm_d_benchmark.cc ├── query_mm_d_filter_benchmark.cc ├── update_box_d_benchmark.cc ├── update_d_benchmark.cc ├── update_mm_box_d_benchmark.cc └── update_mm_d_benchmark.cc ├── ci ├── includes │ └── os.sh └── linting │ ├── buildifier.sh │ └── clang-format.sh ├── cmake └── phtreeConfig.cmake.in ├── examples ├── BUILD ├── CMakeLists.txt └── example.cc ├── fuzzer ├── BUILD ├── README.md ├── b_plus_hash_map_fuzzer.cc ├── b_plus_map_fuzzer.cc ├── b_plus_multimap_fuzzer.cc └── phtree_mm_relocate_fuzzer.cc ├── include └── phtree │ ├── common │ ├── BUILD │ ├── README.md │ ├── b_plus_tree_base.h │ ├── b_plus_tree_hash_map.h │ ├── b_plus_tree_map.h │ ├── b_plus_tree_multimap.h │ ├── base_types.h │ ├── bits.h │ ├── bpt_fixed_vector.h │ ├── bpt_priority_queue.h │ ├── common.h │ ├── debug_helper.h │ ├── flat_array_map.h │ ├── flat_sparse_map.h │ └── tree_stats.h │ ├── converter.h │ ├── distance.h │ ├── filter.h │ ├── phtree.h │ ├── phtree_multimap.h │ └── v16 │ ├── BUILD │ ├── debug_helper_v16.h │ ├── entry.h │ ├── for_each.h │ ├── for_each_hc.h │ ├── iterator_base.h │ ├── iterator_full.h │ ├── iterator_hc.h │ ├── iterator_knn_hs.h │ ├── iterator_lower_bound.h │ ├── iterator_with_parent.h │ ├── node.h │ └── phtree_v16.h ├── test ├── BUILD ├── CMakeLists.txt ├── all_tests.cc ├── common │ ├── BUILD │ ├── CMakeLists.txt │ ├── b_plus_tree_hash_map_test.cc │ ├── b_plus_tree_map_test.cc │ ├── b_plus_tree_multimap_test.cc │ ├── b_priority_queue_test.cc │ ├── b_vector_test.cc │ ├── base_types_test.cc │ ├── bits_test.cc │ ├── common_test.cc │ ├── flat_array_map_test.cc │ ├── flat_sparse_map_test.cc │ └── scripts.cmake ├── converter_test.cc ├── distance_test.cc ├── filter_test.cc ├── phtree_box_d_test.cc ├── phtree_box_d_test_filter.cc ├── phtree_box_d_test_query_types.cc ├── phtree_box_f_test.cc ├── phtree_d_test.cc ├── phtree_d_test_copy_move.cc ├── phtree_d_test_custom_key.cc ├── phtree_d_test_filter.cc ├── phtree_d_test_preprocessor.cc ├── phtree_f_test.cc ├── phtree_multimap_box_d_test.cc ├── phtree_multimap_d_test.cc ├── phtree_multimap_d_test_copy_move.cc ├── phtree_multimap_d_test_filter.cc ├── phtree_multimap_d_test_unique_ptr_values.cc ├── phtree_test.cc ├── phtree_test_const_values.cc ├── phtree_test_issue_142.cc ├── phtree_test_issue_153.cc ├── phtree_test_issues.cc ├── phtree_test_ptr_values.cc └── phtree_test_unique_ptr_values.cc ├── third_party ├── BUILD ├── WORKSPACE ├── gtest │ └── BUILD └── spdlog │ └── BUILD └── tools ├── BUILD └── runners ├── BUILD └── sanitizers ├── asan ├── BUILD ├── asan-suppressions.txt ├── asan.sh └── lsan-suppressions.txt ├── msan ├── BUILD ├── msan-suppressions.txt └── msan.sh ├── tsan ├── BUILD ├── tsan-suppressions.txt └── tsan.sh ├── ubsan ├── BUILD ├── ubsan-suppressions.txt └── ubsan.sh └── valgrind-memcheck ├── BUILD ├── valgrind-memcheck.sh └── valgrind-suppressions.txt /.bazelignore: -------------------------------------------------------------------------------- 1 | #ignore typical cmake build folders 2 | build 3 | out 4 | cmake-build-debug 5 | -------------------------------------------------------------------------------- /.bazelrc: -------------------------------------------------------------------------------- 1 | # general build options 2 | build --compilation_mode=dbg 3 | build --host_compilation_mode=fastbuild 4 | build --verbose_failures 5 | build --experimental_strict_action_env 6 | build --experimental_guard_against_concurrent_changes 7 | # This causes bazel to pass --config=windows/macos/linux automatically for us. 8 | build --enable_platform_specific_config 9 | # We enable the nocopts flag because it's used in the OpenSSL rules 10 | build --noincompatible_disable_nocopts 11 | # Enable symlinking of runfiles on all platforms (including Windows, where it's disable by default). 12 | build --enable_runfiles --build_runfile_links 13 | # Avoids downloading any remote build outputs to the local machine, except the ones required by local actions. 14 | # Commenting out for now as it may be causing builds to flake. 15 | #build --remote_download_minimal 16 | 17 | # general test options - note that test inherits from build, so these are in addition to the ones 18 | # above 19 | test --test_output=errors 20 | 21 | build:release --compilation_mode=opt 22 | build:release --host_compilation_mode=opt 23 | 24 | build:ci --keep_going 25 | build:ci --announce_rc 26 | 27 | #build:linux --copt="-O1" 28 | #build:linux --copt="-march=skylake" 29 | #build:linux --copt="-march=haswell" 30 | #build:linux --copt="-march=native" 31 | build:linux --copt="-fvisibility=hidden" 32 | build:linux --copt="-fno-omit-frame-pointer" # for friendlier stack traces 33 | build:linux --copt="-Wno-error" 34 | build:linux --copt="-Wall" 35 | build:linux --copt="-Wextra" 36 | build:linux --copt="-Werror=return-type" 37 | build:linux --copt="-Werror=switch" 38 | build:linux --copt="-mavx" 39 | # Enable CLZ (count leading zeros). This is equivalent to "-march=haswell" 40 | build:linux --copt="-mbmi2" 41 | build:linux --copt="-Wsequence-point" 42 | build:linux --copt="-Wsign-compare" 43 | build:linux --cxxopt="-std=c++17" 44 | 45 | build:linux-release --config=release 46 | build:linux-release --config=linux 47 | build:linux-release --copt="-O3" 48 | 49 | build:benchmark --config=linux-release 50 | build:benchmark --copt="-g" # To get code references in vtune 51 | 52 | build:macos --copt="-fvisibility=hidden" 53 | build:macos --copt="-Wno-error" 54 | build:macos --cxxopt="-std=c++17" 55 | 56 | build:windows --cxxopt="/std:c++17" 57 | # Disables wingdi.h, which avoids defining a macro called ERROR. 58 | build:windows --cxxopt="/DNOGDI" 59 | build:windows --features=static_link_msvcrt 60 | # We fix the temp directory, as otherwise it is different across different Windows BK agents, which causes 61 | # the remote cache to never be hit due to differing build graph hashes. 62 | build:windows --action_env TMP=C:/Windows/Temp 63 | build:windows --action_env TEMP=C:/Windows/Temp 64 | build:windows --cxxopt="/DWIN32_LEAN_AND_MEAN" 65 | 66 | # Valgrind config. 67 | build:valgrind-memcheck --config=linux 68 | build:valgrind-memcheck --define valgrind-memcheck=true 69 | test:valgrind-memcheck --run_under=//tools/runners/sanitizers/valgrind-memcheck 70 | run:valgrind-memcheck --run_under=//tools/runners/sanitizers/valgrind-memcheck 71 | 72 | # Sanitizer configs; for an overview of the sanitizers, see https://github.com/google/sanitizers/wiki 73 | # For more specific information on sanitizers: 74 | # - https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html 75 | # - https://clang.llvm.org/docs/AddressSanitizer.html 76 | # - https://clang.llvm.org/docs/ThreadSanitizer.html 77 | build:base-sanitizer --copt="-fno-omit-frame-pointer" # for friendlier stack traces 78 | build:base-sanitiser -strip=never 79 | 80 | build:asan --config=base-sanitizer 81 | build:asan --copt="-O1" 82 | build:asan --copt="-fno-optimize-sibling-calls" 83 | build:asan --copt="-fsanitize=address" 84 | build:asan --linkopt="-fsanitize=address" 85 | test:asan --test_env="ASAN_SYMBOLIZER_PATH=/usr/lib/llvm-9/bin/llvm-symbolizer" 86 | test:asan --run_under=//tools/runners/sanitizers/asan 87 | 88 | build:tsan --config=base-sanitizer 89 | build:tsan --copt="-O1" 90 | build:tsan --copt="-fno-optimize-sibling-calls" 91 | build:tsan --copt="-fsanitize=thread" 92 | build:tsan --linkopt="-fsanitize=thread" 93 | #test:tsan --test_env="TSAN_SYMBOLIZER_PATH=/usr/lib/llvm-9/bin/llvm-symbolizer" 94 | test:tsan --run_under=//tools/runners/sanitizers/tsan 95 | 96 | build:ubsan --config=base-sanitizer 97 | build:ubsan --copt="-O1" 98 | build:ubsan --copt="-fsanitize=undefined" 99 | build:ubsan --copt="-fno-sanitize-recover=all" 100 | # Since Bazel uses clang instead of clang++, enabling -fsanitize=vptr would 101 | # require extra linkopts that cause segmentation faults on pure C code. 102 | build:ubsan --copt="-fno-sanitize=function" 103 | build:ubsan --linkopt="-fsanitize=undefined" 104 | build:ubsan --linkopt="-lubsan" 105 | test:ubsan --run_under=//tools/runners/sanitizers/ubsan 106 | 107 | # MSAN is disabled for now, as there are false positives and we can't suppress them easily. 108 | build:msan --config=base-sanitizer 109 | build:msan --copt="-fsanitize=memory" 110 | build:msan --linkopt="-fsanitize=memory" 111 | test:msan --run_under=//tools/runners/sanitizers/msan 112 | 113 | build:lint --define linting_only=true 114 | 115 | build:fuzz --action_env=CC=clang 116 | build:fuzz --action_env=CXX=clang++ 117 | build:fuzz --config=base-sanitizer 118 | build:fuzz --copt="-g" 119 | build:fuzz --copt="-fsanitize=fuzzer" 120 | build:fuzz --linkopt="-fsanitize=fuzzer" 121 | -------------------------------------------------------------------------------- /.bazelversion: -------------------------------------------------------------------------------- 1 | 7.x 2 | 3 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: Google 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: AlwaysBreak 6 | AlignConsecutiveAssignments: false 7 | AlignConsecutiveDeclarations: false 8 | AlignEscapedNewlines: Left 9 | AlignOperands: false 10 | AlignTrailingComments: true 11 | AllowAllParametersOfDeclarationOnNextLine: true 12 | AllowShortBlocksOnASingleLine: false 13 | AllowShortCaseLabelsOnASingleLine: false 14 | AllowShortFunctionsOnASingleLine: Empty 15 | AllowShortIfStatementsOnASingleLine: false 16 | AllowShortLoopsOnASingleLine: false 17 | AlwaysBreakAfterDefinitionReturnType: None 18 | AlwaysBreakAfterReturnType: None 19 | AlwaysBreakBeforeMultilineStrings: true 20 | AlwaysBreakTemplateDeclarations: true 21 | BinPackArguments: false 22 | BinPackParameters: false 23 | BraceWrapping: 24 | AfterClass: false 25 | AfterControlStatement: false 26 | AfterEnum: false 27 | AfterFunction: false 28 | AfterNamespace: false 29 | AfterObjCDeclaration: false 30 | AfterStruct: false 31 | AfterUnion: false 32 | AfterExternBlock: false 33 | BeforeCatch: false 34 | BeforeElse: false 35 | IndentBraces: false 36 | SplitEmptyFunction: true 37 | SplitEmptyRecord: true 38 | SplitEmptyNamespace: true 39 | BreakBeforeBinaryOperators: None 40 | BreakBeforeBraces: Attach 41 | BreakBeforeInheritanceComma: false 42 | BreakBeforeTernaryOperators: true 43 | BreakConstructorInitializersBeforeComma: true 44 | BreakConstructorInitializers: BeforeComma 45 | BreakAfterJavaFieldAnnotations: false 46 | BreakStringLiterals: true 47 | ColumnLimit: 100 48 | CompactNamespaces: false 49 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 50 | ConstructorInitializerIndentWidth: 0 51 | ContinuationIndentWidth: 4 52 | Cpp11BracedListStyle: true 53 | DerivePointerAlignment: false 54 | DisableFormat: false 55 | ExperimentalAutoDetectBinPacking: false 56 | FixNamespaceComments: true 57 | ForEachMacros: 58 | - foreach 59 | - Q_FOREACH 60 | - BOOST_FOREACH 61 | IncludeBlocks: Preserve 62 | IncludeCategories: 63 | - Regex: '".*"' 64 | Priority: 1 65 | - Regex: '^' 70 | Priority: 3 71 | - Regex: '^<.*>' 72 | Priority: 5 73 | IncludeIsMainRegex: '([-_](test|unittest))?$' 74 | IndentCaseLabels: false 75 | IndentPPDirectives: None 76 | IndentWidth: 4 77 | IndentWrappedFunctionNames: false 78 | KeepEmptyLinesAtTheStartOfBlocks: false 79 | MacroBlockBegin: '' 80 | MacroBlockEnd: '' 81 | MaxEmptyLinesToKeep: 1 82 | NamespaceIndentation: None 83 | PenaltyBreakAssignment: 1 84 | PenaltyBreakBeforeFirstCallParameter: 10 85 | PenaltyBreakComment: 400 86 | PenaltyBreakFirstLessLess: 200 87 | PenaltyBreakString: 1000 88 | PenaltyExcessCharacter: 1000000 89 | PenaltyReturnTypeOnItsOwnLine: 1000000 90 | PointerAlignment: Left 91 | RawStringFormats: 92 | - Language: TextProto 93 | Delimiters: 94 | - 'pb' 95 | BasedOnStyle: google 96 | ReflowComments: true 97 | SortIncludes: true 98 | SortUsingDeclarations: true 99 | SpaceAfterCStyleCast: false 100 | SpaceAfterTemplateKeyword: true 101 | SpaceBeforeAssignmentOperators: true 102 | SpaceBeforeParens: ControlStatements 103 | SpaceInEmptyParentheses: false 104 | SpacesBeforeTrailingComments: 2 105 | SpacesInAngles: false 106 | SpacesInContainerLiterals: true 107 | SpacesInCStyleCastParentheses: false 108 | SpacesInParentheses: false 109 | SpacesInSquareBrackets: false 110 | Standard: Cpp11 111 | TabWidth: 4 112 | UseTab: Never 113 | --- 114 | Language: Proto 115 | BasedOnStyle: Google 116 | ColumnLimit: 100 117 | --- 118 | Language: Java 119 | BasedOnStyle: Google 120 | AlignAfterOpenBracket: Align 121 | AlignConsecutiveAssignments: false 122 | AlignConsecutiveDeclarations: false 123 | AlignEscapedNewlines: Left 124 | AlignOperands: false 125 | AlignTrailingComments: true 126 | AllowAllParametersOfDeclarationOnNextLine: true 127 | AllowShortBlocksOnASingleLine: false 128 | AllowShortCaseLabelsOnASingleLine: false 129 | AllowShortFunctionsOnASingleLine: None 130 | AllowShortIfStatementsOnASingleLine: false 131 | AllowShortLoopsOnASingleLine: false 132 | AlwaysBreakAfterDefinitionReturnType: None 133 | AlwaysBreakAfterReturnType: None 134 | AlwaysBreakBeforeMultilineStrings: true 135 | BinPackArguments: true 136 | BinPackParameters: true 137 | BraceWrapping: 138 | AfterClass: false 139 | AfterControlStatement: false 140 | AfterEnum: false 141 | AfterFunction: false 142 | AfterExternBlock: false 143 | BeforeCatch: false 144 | BeforeElse: false 145 | IndentBraces: false 146 | SplitEmptyFunction: true 147 | SplitEmptyRecord: true 148 | SplitEmptyNamespace: true 149 | BreakBeforeBinaryOperators: None 150 | BreakBeforeBraces: Attach 151 | BreakBeforeInheritanceComma: false 152 | BreakBeforeTernaryOperators: true 153 | BreakAfterJavaFieldAnnotations: true 154 | BreakStringLiterals: true 155 | ColumnLimit: 120 156 | ContinuationIndentWidth: 4 157 | ExperimentalAutoDetectBinPacking: false 158 | ... 159 | -------------------------------------------------------------------------------- /.github/workflows/bazel-macos.yml: -------------------------------------------------------------------------------- 1 | name: Bazel MacOS AppleClang build 2 | 3 | on: [ push ] 4 | 5 | env: 6 | CC: clang 7 | CXX: clang++ 8 | 9 | jobs: 10 | build: 11 | name: Run bazel 12 | runs-on: macos-latest 13 | 14 | defaults: 15 | run: 16 | shell: bash 17 | 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v3 21 | 22 | # This causes build failures 23 | # - name: Mount bazel cache # Optional 24 | # uses: actions/cache@v3 25 | # with: 26 | # path: "~/.cache/bazel" 27 | # key: bazel 28 | 29 | # This is (sometimes?!?!) extremely slow on MacOS 30 | # - name: Clang format 31 | # shell: bash 32 | # run: ./ci/linting/clang-format.sh 33 | 34 | # - name: Bazel format 35 | # shell: bash 36 | # run: ./ci/linting/buildifier.sh 37 | 38 | - name: Build 39 | shell: bash 40 | run: bazel build ... 41 | 42 | - name: Test 43 | shell: bash 44 | run: bazel test ... 45 | 46 | - name: Test 47 | shell: bash 48 | run: bazel test //test:phtree_test --config=asan 49 | -------------------------------------------------------------------------------- /.github/workflows/bazel.yml: -------------------------------------------------------------------------------- 1 | name: Bazel build 2 | 3 | on: [ push ] 4 | 5 | jobs: 6 | build: 7 | name: Run bazel 8 | runs-on: ubuntu-latest 9 | 10 | defaults: 11 | run: 12 | shell: bash 13 | 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v3 17 | 18 | - name: Setup bazel 19 | uses: bazelbuild/setup-bazelisk@v2 20 | 21 | # This causes build failures 22 | # - name: Mount bazel cache # Optional 23 | # uses: actions/cache@v3 24 | # with: 25 | # path: "~/.cache/bazel" 26 | # key: bazel 27 | 28 | - name: Clang format 29 | shell: bash 30 | run: ./ci/linting/clang-format.sh 31 | 32 | - name: Bazel format 33 | shell: bash 34 | run: ./ci/linting/buildifier.sh 35 | 36 | - name: Build 37 | shell: bash 38 | run: bazel build ... 39 | 40 | - name: Test 41 | shell: bash 42 | run: bazel test ... 43 | 44 | - name: Test 45 | shell: bash 46 | run: bazel test //test:phtree_test --config=asan 47 | -------------------------------------------------------------------------------- /.github/workflows/cmake-codecov.yml: -------------------------------------------------------------------------------- 1 | name: CMake Codecov 2 | 3 | on: [ push ] 4 | 5 | env: 6 | BUILD_TYPE: Debug 7 | 8 | defaults: 9 | run: 10 | shell: bash 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v3 18 | 19 | - uses: hendrikmuhs/ccache-action@v1.2 20 | 21 | - name: Install lcov 22 | run: sudo apt-get install lcov -y 23 | 24 | - name: Create Build Environment 25 | run: | 26 | cmake -E make_directory ${{github.workspace}}/build 27 | cd build 28 | 29 | - name: Configure CMake 30 | working-directory: ${{github.workspace}}/build 31 | run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DPHTREE_CODE_COVERAGE=ON 32 | 33 | - name: Build 34 | working-directory: ${{github.workspace}}/build 35 | run: cmake --build . --config $BUILD_TYPE -j2 36 | 37 | - name: Run tests 38 | working-directory: ${{github.workspace}}/build 39 | run: ctest -C $BUILD_TYPE 40 | 41 | - name: Create and upload coverage 42 | working-directory: ${{github.workspace}}/build 43 | run: | 44 | cd test/CMakeFiles/all_tests.dir/ 45 | lcov --directory . --capture -o coverage.info 46 | lcov -r coverage.info */build/* */test/* */c++/* */gtest/* -o coverageFiltered.info 47 | lcov --list coverageFiltered.info 48 | bash <(curl -s https://codecov.io/bash) -f coverageFiltered.info || echo "Upload failed" 49 | 50 | -------------------------------------------------------------------------------- /.github/workflows/cmake-macos.yml_fails: -------------------------------------------------------------------------------- 1 | name: CMake MacOS Clang build 2 | 3 | on: [ push ] 4 | 5 | env: 6 | CC: clang 7 | CXX: clang++ 8 | 9 | jobs: 10 | build: 11 | runs-on: macos-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | 16 | # - uses: hendrikmuhs/ccache-action@v1.2 17 | 18 | - name: Build Version 19 | run: $CC --version 20 | 21 | - name: Create Build Environment 22 | run: cmake -E make_directory ${{github.workspace}}/out 23 | 24 | - name: Configure CMake 25 | working-directory: ${{github.workspace}}/out 26 | run: cmake $GITHUB_WORKSPACE -S ${{github.workspace}} -B ${{github.workspace}}/out -DPHTREE_BUILD_EXAMPLES=ON -DPHTREE_BUILD_TESTS=ON 27 | 28 | # This keeps failing with obscure linker failure: 29 | # [ 10%] Linking CXX executable phtree_test 30 | # CMake Error at /usr/local/Cellar/cmake/3.26.3/share/cmake/Modules/GoogleTestAddTests.cmake:112 (message): 31 | # Error running test executable. 32 | # 33 | # Path: '/Users/runner/work/phtree-cpp/phtree-cpp/out/test/phtree_test' 34 | # Result: Illegal instruction 35 | # Output: 36 | # 37 | # 38 | # Call Stack (most recent call first): 39 | # /usr/local/Cellar/cmake/3.26.3/share/cmake/Modules/GoogleTestAddTests.cmake:225 (gtest_discover_tests_impl) 40 | 41 | 42 | - name: Build 43 | working-directory: ${{github.workspace}}/out 44 | run: cmake --build . 45 | 46 | - name: Test 47 | working-directory: ${{github.workspace}}/out 48 | run: ctest -C $BUILD_TYPE 49 | -------------------------------------------------------------------------------- /.github/workflows/cmake-windows.yml: -------------------------------------------------------------------------------- 1 | name: CMake Windows build 2 | 3 | on: [ push ] 4 | 5 | env: 6 | BUILD_TYPE: Release 7 | 8 | jobs: 9 | build: 10 | runs-on: windows-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v3 14 | 15 | - uses: hendrikmuhs/ccache-action@v1.2 16 | 17 | - uses: ilammy/msvc-dev-cmd@v1 18 | 19 | - name: Create Build Environment 20 | run: cmake -E make_directory ${{github.workspace}}\out 21 | 22 | - name: Configure CMake 23 | working-directory: ${{github.workspace}}\out 24 | run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -S ${{github.workspace}} -B ${{github.workspace}}\out -DPHTREE_BUILD_EXAMPLES=ON -DPHTREE_BUILD_TESTS=ON 25 | 26 | - name: Build 27 | working-directory: ${{github.workspace}}\out 28 | run: cmake --build . --config ${env:BUILD_TYPE} -j2 29 | 30 | - name: Test 31 | working-directory: ${{github.workspace}}\out 32 | run: ctest -C ${env:BUILD_TYPE} 33 | -------------------------------------------------------------------------------- /.github/workflows/cmake.yml: -------------------------------------------------------------------------------- 1 | name: CMake build 2 | 3 | on: [ push ] 4 | 5 | env: 6 | BUILD_TYPE: Release 7 | 8 | defaults: 9 | run: 10 | shell: bash 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v3 18 | 19 | - uses: hendrikmuhs/ccache-action@v1.2 20 | 21 | - name: Create Build Environment 22 | run: cmake -E make_directory ${{github.workspace}}/build 23 | 24 | - name: Configure CMake 25 | working-directory: ${{github.workspace}}/build 26 | # Note the current convention is to use the -S and -B options here to specify source 27 | # and build directories, but this is only available with CMake 3.13 and higher. 28 | # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 29 | run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DPHTREE_BUILD_ALL=ON 30 | 31 | - name: Build 32 | working-directory: ${{github.workspace}}/build 33 | run: cmake --build . --config $BUILD_TYPE -j2 34 | 35 | - name: Test 36 | working-directory: ${{github.workspace}}/build 37 | run: ctest -C $BUILD_TYPE 38 | 39 | - name: Example 40 | working-directory: ${{github.workspace}}/build 41 | run: examples/Example 42 | -------------------------------------------------------------------------------- /.github/workflows/codcecov.yml_old: -------------------------------------------------------------------------------- 1 | name: Upload CodeCov Report 2 | on: [ push ] 3 | jobs: 4 | run: 5 | runs-on: windows-latest 6 | name: Build, Test , Upload Code Coverage Report 7 | steps: 8 | - name: Checkout code 9 | uses: actions/checkout@v2 10 | with: 11 | fetch-depth: ‘2’ 12 | id: checkout_code 13 | - name: Setup MSBuild and add to PATH 14 | uses: microsoft/setup-msbuild@v1.0.2 15 | id: setup_msbuild 16 | 17 | - name: Generate Solution 18 | run: cmake -G "Visual Studio 17 2022" -A x64 . -DPHTREE_CODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug 19 | 20 | - name: Run MSBuild 21 | id: run_msbuild 22 | run: msbuild /p:Configuration=Debug /p:Platform=x64 /p:gtest_force_shared_crt=on phtree.sln 23 | - name: Setup VSTest and add to PATH 24 | uses: darenm/Setup-VSTest@v1 25 | id: setup_vstest 26 | 27 | - name: Setup OpenCppCoverage and add to PATH 28 | id: setup_opencppcoverage 29 | run: | 30 | choco install OpenCppCoverage -y 31 | echo "C:\Program Files\OpenCppCoverage" >> $env:GITHUB_PATH 32 | 33 | - name: Generate Report 34 | id: generate_test_report 35 | shell: cmd 36 | run: OpenCppCoverage.exe --modules phtree --export_type cobertura:phtree.xml -- "vstest.console.exe" test\Debug\all_tests.exe 37 | - name: Upload Report to Codecov 38 | uses: codecov/codecov-action@v3 39 | with: 40 | files: ./phtree.xml 41 | fail_ci_if_error: true 42 | functionalities: fix 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .* 2 | !.bazelrc 3 | !.clang-format 4 | !.gitignore 5 | !.github 6 | !*.yml 7 | bazel-* 8 | !bazel-*.sh 9 | compile_commands.json 10 | perf.data* 11 | build 12 | out 13 | cygwin 14 | CMakeSettings.json 15 | 16 | **/cmake-build-debug/ 17 | -------------------------------------------------------------------------------- /BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | licenses(["notice"]) # Apache 2.0 4 | 5 | # Expose license for external usage through bazel. 6 | exports_files([ 7 | "LICENSE", 8 | ]) 9 | 10 | # Platform configuration definitions for select() 11 | 12 | config_setting( 13 | name = "linux", 14 | constraint_values = ["@platforms//os:linux"], 15 | ) 16 | 17 | config_setting( 18 | name = "macos", 19 | constraint_values = ["@platforms//os:osx"], 20 | ) 21 | 22 | config_setting( 23 | name = "macos_not_ios", 24 | constraint_values = ["@platforms//os:osx"], 25 | ) 26 | 27 | config_setting( 28 | name = "windows", 29 | constraint_values = ["@platforms//os:windows"], 30 | ) 31 | 32 | config_setting( 33 | name = "windows_debug", 34 | constraint_values = ["@platforms//os:windows"], 35 | values = { 36 | "compilation_mode": "dbg", 37 | }, 38 | ) 39 | 40 | config_setting( 41 | name = "windows_release", 42 | constraint_values = ["@platforms//os:windows"], 43 | values = { 44 | "compilation_mode": "opt", 45 | }, 46 | ) 47 | 48 | config_setting( 49 | name = "windows-x86_64", 50 | constraint_values = ["@platforms//os:windows"], 51 | ) 52 | 53 | # Buildifier 54 | 55 | sh_binary( 56 | name = "buildifier", 57 | srcs = select( 58 | { 59 | ":linux": ["@buildifier_linux//file"], 60 | ":macos": ["@buildifier_macos//file"], 61 | ":windows": ["@buildifier_windows//file"], 62 | }, 63 | ), 64 | ) 65 | 66 | # Aspect-based clang-format 67 | 68 | filegroup( 69 | name = "dot_clang_format", 70 | srcs = [".clang-format"], 71 | ) 72 | 73 | cc_library( 74 | name = "phtree", 75 | srcs = glob( 76 | include = [ 77 | "include/**/*.h", 78 | ], 79 | ), 80 | hdrs = [ 81 | "include/phtree/converter.h", 82 | "include/phtree/distance.h", 83 | "include/phtree/filter.h", 84 | "include/phtree/phtree.h", 85 | "include/phtree/phtree_multimap.h", 86 | ], 87 | includes = [ 88 | "include", 89 | ], 90 | linkstatic = True, 91 | visibility = [ 92 | "//visibility:public", 93 | ], 94 | deps = [ 95 | "//include/phtree/common", 96 | "//include/phtree/v16", 97 | ], 98 | ) 99 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at xxx@improbable.io. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | We would really like to have people help and contribute back to the project. 4 | 5 | We hope you do not mind signing our CLA first. 6 | 7 | 8 | ## Style Guide 9 | 10 | This project folows mostly the [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html). 11 | -------------------------------------------------------------------------------- /TODO.txt: -------------------------------------------------------------------------------- 1 | Ideas that didn't work 2 | ====================== 3 | #39 Store nodes flat in Entries. 4 | Some improvement (5-10%), but it doesn work for flat_array_map, because that 5 | is already a "flat" std::array and would cause the whole tree to materialize during compilation time. 6 | Lesson: Try to mak flat_sparse_map "flat" -> see #86 7 | #88 Using PQ for upper part of WQ. This had absolutely no effect (testing with query_mm_d_benchmark with 100K-10M). 8 | Counting showed that PQ would go 3-5 nodes deep (100K:3, 10M: 5) but that had no effect. 9 | Lesson: Look at WQ initialization, it may be too expensive. Why is WQ traversal so slow??? 10 | Non-flat keys: Use e.g. vector as keys 11 | Tested in: https://github.com/tzaeschke/phtree-cpp/tree/yolo/vector-keys 12 | The implementation actually uses a pointer-to-array instead of a vector (this allows constexpr SIZE for 13 | loop unrolling etc). 14 | Results: 15 | - There is no problem for PhPointLD using an inherited std::array -> No effect on performance 16 | - Using a delegated array (with unique_ptr) has an effect, 17 | - For insert, it starts paying of for 30D, being up to 20% faster for 60D 18 | - For query and kNN, it never pays off. 19 | -> See RESULTS.txt 20 | 21 | 22 | Fix const-ness 23 | ============== 24 | - operator[] should have a const overload 25 | - find() should have a non-const overload 26 | - test: 27 | 28 | TEST(PhTreeTest, SmokeTestConstTree) { 29 | // Test edge case: only one entry in tree 30 | PhPoint<3> p{1, 2, 3}; 31 | TestTree<3, Id> tree1; 32 | tree1.emplace(p, Id{1}); 33 | tree1.emplace(p, Id{2}); 34 | Id id3{3}; 35 | tree1.insert(p, id3); 36 | Id id4{4}; 37 | tree1.insert(p, id4); 38 | const auto& tree = tree1; 39 | ASSERT_EQ(tree.size(), 1); 40 | ASSERT_EQ(tree.find(p).second()._i, 1); 41 | ASSERT_EQ(tree[p]._i, 1); 42 | 43 | auto q_window = tree.begin_query({p, p}); 44 | ASSERT_EQ(1, q_window->_i); 45 | ++q_window; 46 | ASSERT_EQ(q_window, tree.end()); 47 | 48 | auto q_extent = tree.begin(); 49 | ASSERT_EQ(1, q_extent->_i); 50 | ++q_extent; 51 | ASSERT_EQ(q_extent, tree.end()); 52 | 53 | auto q_knn = tree.begin_knn_query(10, p, DistanceEuclidean<3>()); 54 | ASSERT_EQ(1, q_knn->_i); 55 | ++q_knn; 56 | ASSERT_EQ(q_knn, tree.end()); 57 | 58 | ASSERT_EQ(1, tree1.erase(p)); 59 | ASSERT_EQ(0, tree.size()); 60 | ASSERT_EQ(0, tree1.erase(p)); 61 | ASSERT_EQ(0, tree.size()); 62 | ASSERT_TRUE(tree.empty()); 63 | } 64 | 65 | 66 | b_plus_tree_map - binary search 67 | =============== 68 | Use custom binary search: 69 | 70 | // return BptEntry* ?!?!? 71 | template 72 | [[nodiscard]] auto lower_bound(key_t key, std::vector& data) noexcept { 73 | return std::lower_bound(data.begin(), data.end(), key, [](E& left, const key_t key) { 74 | return left.first < key; 75 | }); 76 | // auto pos = __lower_bound(&*data_leaf_.begin(), &*data_leaf_.end(), key); 77 | // return data_leaf_.begin() + pos; 78 | } 79 | 80 | template 81 | inline auto __lower_bound(const TT* __first, const TT* __last, key_t __val) const noexcept { 82 | const TT* const_first = __first; 83 | auto __len = __last - __first; 84 | 85 | while (__len > 0) { 86 | auto __half = __len >> 1; 87 | const TT* __middle = __first + __half; 88 | if (__middle->first < __val) { 89 | __first = __middle; 90 | ++__first; 91 | __len = __len - __half - 1; 92 | } else 93 | __len = __half; 94 | } 95 | return __first - const_first; 96 | } 97 | 98 | -------------------------------------------------------------------------------- /WORKSPACE: -------------------------------------------------------------------------------- 1 | # Bazel bootstrapping 2 | 3 | load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive", "http_file") 4 | 5 | # NOTE: We make third_party/ its own bazel workspace because it allows to run `bazel build ...` without 6 | # having all targets defined in third-party BUILD files in that directory buildable. 7 | local_repository( 8 | name = "third_party", 9 | path = "third_party", 10 | ) 11 | 12 | # External PH-Tree dependencies 13 | 14 | http_archive( 15 | name = "spdlog", 16 | build_file = "@third_party//spdlog:BUILD", 17 | sha256 = "b38e0bbef7faac2b82fed550a0c19b0d4e7f6737d5321d4fd8f216b80f8aee8a", 18 | strip_prefix = "spdlog-1.5.0", 19 | url = "https://github.com/gabime/spdlog/archive/v1.5.0.tar.gz", 20 | ) 21 | 22 | http_archive( 23 | name = "gbenchmark", 24 | sha256 = "6132883bc8c9b0df5375b16ab520fac1a85dc9e4cf5be59480448ece74b278d4", 25 | strip_prefix = "benchmark-1.6.1", 26 | url = "https://github.com/google/benchmark/archive/v1.6.1.tar.gz", 27 | ) 28 | 29 | http_archive( 30 | name = "gtest", 31 | sha256 = "ad7fdba11ea011c1d925b3289cf4af2c66a352e18d4c7264392fead75e919363", 32 | strip_prefix = "googletest-1.13.0", 33 | url = "https://github.com/google/googletest/archive/v1.13.0.tar.gz", 34 | ) 35 | 36 | # Development environment tooling 37 | 38 | BUILDIFIER_VERSION = "0.29.0" 39 | 40 | http_file( 41 | name = "buildifier_linux", 42 | executable = True, 43 | sha256 = "4c985c883eafdde9c0e8cf3c8595b8bfdf32e77571c369bf8ddae83b042028d6", 44 | urls = ["https://github.com/bazelbuild/buildtools/releases/download/{version}/buildifier".format(version = BUILDIFIER_VERSION)], 45 | ) 46 | 47 | http_file( 48 | name = "buildifier_macos", 49 | executable = True, 50 | sha256 = "9b108decaa9a624fbac65285e529994088c5d15fecc1a30866afc03a48619245", 51 | urls = ["https://github.com/bazelbuild/buildtools/releases/download/{version}/buildifier.mac".format(version = BUILDIFIER_VERSION)], 52 | ) 53 | 54 | http_file( 55 | name = "buildifier_windows", 56 | executable = True, 57 | sha256 = "dc5d6ed5e3e0dbe9955f7606939c627af5a2be7f9bdd8814e77a22109164394f", 58 | urls = ["https://github.com/bazelbuild/buildtools/releases/download/{version}/buildifier.exe".format(version = BUILDIFIER_VERSION)], 59 | ) 60 | 61 | http_archive( 62 | name = "bazel_compilation_database", 63 | sha256 = "bb1b812396e2ee36a50a13b03ae6833173ce643e8a4bd50731067d0b4e5c6e86", 64 | strip_prefix = "bazel-compilation-database-0.3.5", 65 | url = "https://github.com/grailbio/bazel-compilation-database/archive/0.3.5.tar.gz", 66 | ) 67 | -------------------------------------------------------------------------------- /benchmark/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | project(phtree-benchmarks) 3 | 4 | set(BENCHMARK_ENABLE_TESTING OFF) 5 | 6 | include(FetchContent) 7 | 8 | FetchContent_Declare( 9 | googlebenchmark 10 | GIT_REPOSITORY https://github.com/google/benchmark.git 11 | GIT_TAG v1.7.0 12 | ) 13 | FetchContent_MakeAvailable(googlebenchmark) 14 | 15 | FetchContent_Declare( 16 | spdlog 17 | GIT_REPOSITORY https://github.com/gabime/spdlog.git 18 | GIT_TAG v1.10.0 19 | ) 20 | FetchContent_MakeAvailable(spdlog) 21 | 22 | macro(package_add_benchmark TESTNAME) 23 | add_executable(${TESTNAME} ${ARGN} benchmark_util.h logging.h) 24 | target_link_libraries(${TESTNAME} PRIVATE benchmark::benchmark) 25 | target_link_libraries(${TESTNAME} PRIVATE spdlog::spdlog) 26 | target_link_libraries(${TESTNAME} PRIVATE phtree::phtree) 27 | endmacro() 28 | 29 | add_compile_definitions(RUN_HAVE_STD_REGEX=0 RUN_HAVE_POSIX_REGEX=0 COMPILE_HAVE_GNU_POSIX_REGEX=0) 30 | 31 | package_add_benchmark(bpt_erase_benchmark bpt_erase_benchmark.cc) 32 | package_add_benchmark(bpt_erase_it_benchmark bpt_erase_it_benchmark.cc) 33 | package_add_benchmark(bpt_insert_benchmark bpt_insert_benchmark.cc) 34 | package_add_benchmark(bpt_iter_benchmark bpt_iter_benchmark.cc) 35 | package_add_benchmark(bpt_lower_bound_benchmark bpt_lower_bound_benchmark.cc) 36 | 37 | package_add_benchmark(count_mm_d_benchmark count_mm_d_benchmark.cc) 38 | package_add_benchmark(erase_benchmark erase_benchmark.cc) 39 | package_add_benchmark(erase_d_benchmark erase_d_benchmark.cc) 40 | package_add_benchmark(extent_benchmark extent_benchmark.cc) 41 | package_add_benchmark(extent_benchmark_weird extent_benchmark_weird.cc) 42 | package_add_benchmark(find_benchmark find_benchmark.cc) 43 | package_add_benchmark(hd_erase_d_benchmark hd_erase_d_benchmark.cc) 44 | package_add_benchmark(hd_insert_d_benchmark hd_insert_d_benchmark.cc) 45 | package_add_benchmark(hd_knn_d_benchmark hd_knn_d_benchmark.cc) 46 | package_add_benchmark(hd_query_d_benchmark hd_query_d_benchmark.cc) 47 | package_add_benchmark(insert_benchmark insert_benchmark.cc) 48 | package_add_benchmark(insert_box_d_benchmark insert_box_d_benchmark.cc) 49 | package_add_benchmark(insert_d_benchmark insert_d_benchmark.cc) 50 | package_add_benchmark(knn_d_benchmark knn_d_benchmark.cc) 51 | package_add_benchmark(query_benchmark query_benchmark.cc) 52 | package_add_benchmark(query_box_d_benchmark query_box_d_benchmark.cc) 53 | package_add_benchmark(query_d_benchmark query_d_benchmark.cc) 54 | package_add_benchmark(query_mm_box_d_benchmark query_mm_box_d_benchmark.cc) 55 | package_add_benchmark(query_mm_d_benchmark query_mm_d_benchmark.cc) 56 | package_add_benchmark(query_mm_d_filter_benchmark query_mm_d_filter_benchmark.cc) 57 | package_add_benchmark(update_box_d_benchmark update_box_d_benchmark.cc) 58 | package_add_benchmark(update_d_benchmark update_d_benchmark.cc) 59 | package_add_benchmark(update_mm_box_d_benchmark update_mm_box_d_benchmark.cc) 60 | package_add_benchmark(update_mm_d_benchmark update_mm_d_benchmark.cc) 61 | -------------------------------------------------------------------------------- /benchmark/erase_benchmark.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Improbable Worlds Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #include "benchmark_util.h" 17 | #include "logging.h" 18 | #include "phtree/phtree.h" 19 | #include 20 | #include 21 | 22 | using namespace improbable; 23 | using namespace improbable::phtree; 24 | using namespace improbable::phtree::phbenchmark; 25 | 26 | namespace { 27 | 28 | const int GLOBAL_MAX = 10000; 29 | using payload_t = std::uint32_t; 30 | 31 | /* 32 | * Benchmark for removing entries. 33 | */ 34 | template 35 | class IndexBenchmark { 36 | public: 37 | IndexBenchmark(benchmark::State& state, TestGenerator data_type, int num_entities); 38 | 39 | void Benchmark(benchmark::State& state); 40 | 41 | private: 42 | void SetupWorld(benchmark::State& state); 43 | void Insert(benchmark::State& state, PhTree& tree); 44 | void Remove(benchmark::State& state, PhTree& tree); 45 | 46 | const TestGenerator data_type_; 47 | const size_t num_entities_; 48 | 49 | std::default_random_engine random_engine_; 50 | std::uniform_int_distribution<> cube_distribution_; 51 | std::vector> points_; 52 | }; 53 | 54 | template 55 | IndexBenchmark::IndexBenchmark( 56 | benchmark::State& state, TestGenerator data_type, int num_entities) 57 | : data_type_{data_type} 58 | , num_entities_(num_entities) 59 | , random_engine_{1} 60 | , cube_distribution_{0, GLOBAL_MAX} 61 | , points_(num_entities) { 62 | logging::SetupDefaultLogging(); 63 | SetupWorld(state); 64 | } 65 | 66 | template 67 | void IndexBenchmark::Benchmark(benchmark::State& state) { 68 | for (auto _ : state) { 69 | state.PauseTiming(); 70 | auto* tree = new PhTree(); 71 | Insert(state, *tree); 72 | state.ResumeTiming(); 73 | 74 | Remove(state, *tree); 75 | 76 | state.PauseTiming(); 77 | // avoid measuring deallocation 78 | delete tree; 79 | state.ResumeTiming(); 80 | } 81 | } 82 | 83 | template 84 | void IndexBenchmark::SetupWorld(benchmark::State& state) { 85 | logging::info("Setting up world with {} entities and {} dimensions.", num_entities_, DIM); 86 | CreatePointData(points_, data_type_, num_entities_, 0, GLOBAL_MAX); 87 | 88 | state.counters["total_remove_count"] = benchmark::Counter(0); 89 | state.counters["remove_rate"] = benchmark::Counter(0, benchmark::Counter::kIsRate); 90 | 91 | logging::info("World setup complete."); 92 | } 93 | 94 | template 95 | void IndexBenchmark::Insert(benchmark::State&, PhTree& tree) { 96 | for (size_t i = 0; i < num_entities_; ++i) { 97 | tree.emplace(points_[i], (int)i); 98 | } 99 | } 100 | 101 | template 102 | void IndexBenchmark::Remove(benchmark::State& state, PhTree& tree) { 103 | size_t n = 0; 104 | for (size_t i = 0; i < num_entities_; ++i) { 105 | n += tree.erase(points_[i]); 106 | } 107 | 108 | state.counters["total_remove_count"] += n; 109 | state.counters["remove_rate"] += n; 110 | } 111 | 112 | } // namespace 113 | 114 | template 115 | void PhTree3D(benchmark::State& state, Arguments&&... arguments) { 116 | IndexBenchmark<3> benchmark{state, arguments...}; 117 | benchmark.Benchmark(state); 118 | } 119 | 120 | // index type, scenario name, data_type, num_entities 121 | // PhTree 3D CUBE 122 | BENCHMARK_CAPTURE(PhTree3D, REM_CU_1K, TestGenerator::CUBE, 1000)->Unit(benchmark::kMillisecond); 123 | 124 | BENCHMARK_CAPTURE(PhTree3D, REM_CU_10K, TestGenerator::CUBE, 10000)->Unit(benchmark::kMillisecond); 125 | 126 | BENCHMARK_CAPTURE(PhTree3D, REM_CU_100K, TestGenerator::CUBE, 100000) 127 | ->Unit(benchmark::kMillisecond); 128 | 129 | BENCHMARK_CAPTURE(PhTree3D, REM_CU_1M, TestGenerator::CUBE, 1000000)->Unit(benchmark::kMillisecond); 130 | 131 | BENCHMARK_CAPTURE(PhTree3D, REM_CU_10M, TestGenerator::CUBE, 10000000) 132 | ->Unit(benchmark::kMillisecond); 133 | 134 | // PhTree 3D CLUSTER 135 | BENCHMARK_CAPTURE(PhTree3D, REM_CL_1K, TestGenerator::CLUSTER, 1000)->Unit(benchmark::kMillisecond); 136 | 137 | BENCHMARK_CAPTURE(PhTree3D, REM_CL_10K, TestGenerator::CLUSTER, 10000) 138 | ->Unit(benchmark::kMillisecond); 139 | 140 | BENCHMARK_CAPTURE(PhTree3D, REM_CL_100K, TestGenerator::CLUSTER, 100000) 141 | ->Unit(benchmark::kMillisecond); 142 | 143 | BENCHMARK_CAPTURE(PhTree3D, REM_CL_1M, TestGenerator::CLUSTER, 1000000) 144 | ->Unit(benchmark::kMillisecond); 145 | 146 | BENCHMARK_CAPTURE(PhTree3D, REM_CL_10M, TestGenerator::CLUSTER, 10000000) 147 | ->Unit(benchmark::kMillisecond); 148 | 149 | BENCHMARK_MAIN(); 150 | -------------------------------------------------------------------------------- /benchmark/erase_d_benchmark.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Improbable Worlds Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #include "benchmark_util.h" 17 | #include "logging.h" 18 | #include "phtree/phtree.h" 19 | #include 20 | #include 21 | 22 | using namespace improbable; 23 | using namespace improbable::phtree; 24 | using namespace improbable::phtree::phbenchmark; 25 | 26 | namespace { 27 | 28 | const int GLOBAL_MAX = 10000; 29 | using payload_t = std::uint32_t; 30 | 31 | /* 32 | * Benchmark for removing entries. 33 | */ 34 | template 35 | class IndexBenchmark { 36 | public: 37 | IndexBenchmark(benchmark::State& state, TestGenerator data_type, int num_entities); 38 | 39 | void Benchmark(benchmark::State& state); 40 | 41 | private: 42 | void SetupWorld(benchmark::State& state); 43 | void Insert(benchmark::State& state, PhTreeD& tree); 44 | void Remove(benchmark::State& state, PhTreeD& tree); 45 | 46 | const TestGenerator data_type_; 47 | const size_t num_entities_; 48 | 49 | std::default_random_engine random_engine_; 50 | std::uniform_real_distribution<> cube_distribution_; 51 | std::vector> points_; 52 | }; 53 | 54 | template 55 | IndexBenchmark::IndexBenchmark( 56 | benchmark::State& state, TestGenerator data_type, int num_entities) 57 | : data_type_{data_type} 58 | , num_entities_(num_entities) 59 | , random_engine_{1} 60 | , cube_distribution_{0, GLOBAL_MAX} 61 | , points_(num_entities) { 62 | logging::SetupDefaultLogging(); 63 | SetupWorld(state); 64 | } 65 | 66 | template 67 | void IndexBenchmark::Benchmark(benchmark::State& state) { 68 | for (auto _ : state) { 69 | state.PauseTiming(); 70 | auto* tree = new PhTreeD(); 71 | Insert(state, *tree); 72 | state.ResumeTiming(); 73 | 74 | Remove(state, *tree); 75 | 76 | state.PauseTiming(); 77 | // avoid measuring deallocation 78 | delete tree; 79 | state.ResumeTiming(); 80 | } 81 | } 82 | 83 | template 84 | void IndexBenchmark::SetupWorld(benchmark::State& state) { 85 | logging::info("Setting up world with {} entities and {} dimensions.", num_entities_, DIM); 86 | CreatePointData(points_, data_type_, num_entities_, 0, GLOBAL_MAX); 87 | 88 | state.counters["total_remove_count"] = benchmark::Counter(0); 89 | state.counters["remove_rate"] = benchmark::Counter(0, benchmark::Counter::kIsRate); 90 | 91 | logging::info("World setup complete."); 92 | } 93 | 94 | template 95 | void IndexBenchmark::Insert(benchmark::State&, PhTreeD& tree) { 96 | for (payload_t i = 0; i < num_entities_; ++i) { 97 | tree.emplace(points_[i], i); 98 | } 99 | } 100 | 101 | template 102 | void IndexBenchmark::Remove(benchmark::State& state, PhTreeD& tree) { 103 | size_t n = 0; 104 | for (size_t i = 0; i < num_entities_; ++i) { 105 | n += tree.erase(points_[i]); 106 | } 107 | 108 | state.counters["total_remove_count"] += n; 109 | state.counters["remove_rate"] += n; 110 | } 111 | 112 | } // namespace 113 | 114 | template 115 | void PhTree3D(benchmark::State& state, Arguments&&... arguments) { 116 | IndexBenchmark<3> benchmark{state, arguments...}; 117 | benchmark.Benchmark(state); 118 | } 119 | 120 | // index type, scenario name, data_generator, num_entities 121 | // PhTree 3D CUBE 122 | BENCHMARK_CAPTURE(PhTree3D, REM_CU_1K, TestGenerator::CUBE, 1000)->Unit(benchmark::kMillisecond); 123 | 124 | BENCHMARK_CAPTURE(PhTree3D, REM_CU_10K, TestGenerator::CUBE, 10000)->Unit(benchmark::kMillisecond); 125 | 126 | BENCHMARK_CAPTURE(PhTree3D, REM_CU_100K, TestGenerator::CUBE, 100000) 127 | ->Unit(benchmark::kMillisecond); 128 | 129 | BENCHMARK_CAPTURE(PhTree3D, REM_CU_1M, TestGenerator::CUBE, 1000000)->Unit(benchmark::kMillisecond); 130 | 131 | BENCHMARK_CAPTURE(PhTree3D, REM_CU_10M, TestGenerator::CUBE, 10000000) 132 | ->Unit(benchmark::kMillisecond); 133 | 134 | // index type, scenario name, data_generator, num_entities 135 | // PhTree 3D CLUSTER 136 | BENCHMARK_CAPTURE(PhTree3D, REM_CL_1K, TestGenerator::CLUSTER, 1000)->Unit(benchmark::kMillisecond); 137 | 138 | BENCHMARK_CAPTURE(PhTree3D, REM_CL_10K, TestGenerator::CLUSTER, 10000) 139 | ->Unit(benchmark::kMillisecond); 140 | 141 | BENCHMARK_CAPTURE(PhTree3D, REM_CL_100K, TestGenerator::CLUSTER, 100000) 142 | ->Unit(benchmark::kMillisecond); 143 | 144 | BENCHMARK_CAPTURE(PhTree3D, REM_CL_1M, TestGenerator::CLUSTER, 1000000) 145 | ->Unit(benchmark::kMillisecond); 146 | 147 | BENCHMARK_CAPTURE(PhTree3D, REM_CL_10M, TestGenerator::CLUSTER, 10000000) 148 | ->Unit(benchmark::kMillisecond); 149 | 150 | BENCHMARK_MAIN(); 151 | -------------------------------------------------------------------------------- /benchmark/extent_benchmark.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Improbable Worlds Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #include "benchmark_util.h" 17 | #include "logging.h" 18 | #include "phtree/phtree.h" 19 | #include 20 | #include 21 | 22 | using namespace improbable; 23 | using namespace improbable::phtree; 24 | using namespace improbable::phtree::phbenchmark; 25 | 26 | namespace { 27 | 28 | const int GLOBAL_MAX = 10000; 29 | 30 | /* 31 | * Benchmark for iterating over all entries in the tree. 32 | */ 33 | template 34 | class IndexBenchmark { 35 | public: 36 | IndexBenchmark(benchmark::State& state, TestGenerator data_type, int num_entities); 37 | 38 | void Benchmark(benchmark::State& state); 39 | 40 | private: 41 | void SetupWorld(benchmark::State& state); 42 | void QueryWorld(benchmark::State& state); 43 | 44 | const TestGenerator data_type_; 45 | const size_t num_entities_; 46 | 47 | PhTree tree_; 48 | std::default_random_engine random_engine_; 49 | std::uniform_int_distribution<> cube_distribution_; 50 | std::vector> points_; 51 | }; 52 | 53 | template 54 | IndexBenchmark::IndexBenchmark( 55 | benchmark::State& state, TestGenerator data_type, int num_entities) 56 | : data_type_{data_type} 57 | , num_entities_(num_entities) 58 | , random_engine_{1} 59 | , cube_distribution_{0, GLOBAL_MAX} 60 | , points_(num_entities) { 61 | logging::SetupDefaultLogging(); 62 | SetupWorld(state); 63 | } 64 | 65 | template 66 | void IndexBenchmark::Benchmark(benchmark::State& state) { 67 | for (auto _ : state) { 68 | QueryWorld(state); 69 | } 70 | } 71 | 72 | template 73 | void IndexBenchmark::SetupWorld(benchmark::State& state) { 74 | logging::info("Setting up world with {} entities and {} dimensions.", num_entities_, DIM); 75 | CreatePointData(points_, data_type_, num_entities_, 0, GLOBAL_MAX); 76 | for (size_t i = 0; i < num_entities_; ++i) { 77 | tree_.emplace(points_[i], (int)i); 78 | } 79 | 80 | state.counters["total_result_count"] = benchmark::Counter(0); 81 | state.counters["query_rate"] = benchmark::Counter(0, benchmark::Counter::kIsRate); 82 | state.counters["result_rate"] = benchmark::Counter(0, benchmark::Counter::kIsRate); 83 | state.counters["avg_result_count"] = benchmark::Counter(0, benchmark::Counter::kAvgIterations); 84 | 85 | logging::info("World setup complete."); 86 | } 87 | 88 | template 89 | void IndexBenchmark::QueryWorld(benchmark::State& state) { 90 | int n = 0; 91 | auto q = tree_.begin(); 92 | while (q != tree_.end()) { 93 | // just read the entry 94 | ++q; 95 | ++n; 96 | } 97 | state.counters["total_result_count"] += n; 98 | state.counters["query_rate"] += 1; 99 | state.counters["result_rate"] += n; 100 | state.counters["avg_result_count"] += n; 101 | } 102 | 103 | } // namespace 104 | 105 | template 106 | void PhTree3D(benchmark::State& state, Arguments&&... arguments) { 107 | IndexBenchmark<3> benchmark{state, arguments...}; 108 | benchmark.Benchmark(state); 109 | } 110 | 111 | // index type, scenario name, data_generator, num_entities 112 | // PhTree 3D CUBE 113 | BENCHMARK_CAPTURE(PhTree3D, EXT_CU_1K, TestGenerator::CUBE, 1000)->Unit(benchmark::kMillisecond); 114 | 115 | BENCHMARK_CAPTURE(PhTree3D, EXT_CU_10K, TestGenerator::CUBE, 10000)->Unit(benchmark::kMillisecond); 116 | 117 | BENCHMARK_CAPTURE(PhTree3D, EXT_CU_100K, TestGenerator::CUBE, 100000) 118 | ->Unit(benchmark::kMillisecond); 119 | 120 | BENCHMARK_CAPTURE(PhTree3D, EXT_CU_1M, TestGenerator::CUBE, 1000000)->Unit(benchmark::kMillisecond); 121 | 122 | // index type, scenario name, data_generator, num_entities 123 | // PhTree 3D CLUSTER 124 | BENCHMARK_CAPTURE(PhTree3D, EXT_CL_1K, TestGenerator::CLUSTER, 1000)->Unit(benchmark::kMillisecond); 125 | 126 | BENCHMARK_CAPTURE(PhTree3D, EXT_CL_10K, TestGenerator::CLUSTER, 10000) 127 | ->Unit(benchmark::kMillisecond); 128 | 129 | BENCHMARK_CAPTURE(PhTree3D, EXT_CL_100K, TestGenerator::CLUSTER, 100000) 130 | ->Unit(benchmark::kMillisecond); 131 | 132 | BENCHMARK_CAPTURE(PhTree3D, EXT_CL_1M, TestGenerator::CLUSTER, 1000000) 133 | ->Unit(benchmark::kMillisecond); 134 | 135 | BENCHMARK_MAIN(); 136 | -------------------------------------------------------------------------------- /benchmark/hd_erase_d_benchmark.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Improbable Worlds Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #include "benchmark_util.h" 17 | #include "logging.h" 18 | #include "phtree/phtree.h" 19 | #include 20 | #include 21 | 22 | using namespace improbable; 23 | using namespace improbable::phtree; 24 | using namespace improbable::phtree::phbenchmark; 25 | 26 | namespace { 27 | 28 | const int GLOBAL_MAX = 10000; 29 | using payload_t = std::uint32_t; 30 | 31 | /* 32 | * Benchmark for removing entries. 33 | */ 34 | template 35 | class IndexBenchmark { 36 | public: 37 | explicit IndexBenchmark(benchmark::State& state); 38 | void Benchmark(benchmark::State& state); 39 | 40 | private: 41 | void SetupWorld(benchmark::State& state); 42 | void Insert(benchmark::State& state, PhTreeD& tree); 43 | void Remove(benchmark::State& state, PhTreeD& tree); 44 | 45 | const TestGenerator data_type_; 46 | const size_t num_entities_; 47 | 48 | std::default_random_engine random_engine_; 49 | std::uniform_real_distribution<> cube_distribution_; 50 | std::vector> points_; 51 | }; 52 | 53 | template 54 | IndexBenchmark::IndexBenchmark(benchmark::State& state) 55 | : data_type_{static_cast(state.range(1))} 56 | , num_entities_(state.range(0)) 57 | , random_engine_{1} 58 | , cube_distribution_{0, GLOBAL_MAX} 59 | , points_(state.range(0)) { 60 | logging::SetupDefaultLogging(); 61 | SetupWorld(state); 62 | } 63 | 64 | template 65 | void IndexBenchmark::Benchmark(benchmark::State& state) { 66 | for (auto _ : state) { 67 | state.PauseTiming(); 68 | auto* tree = new PhTreeD(); 69 | Insert(state, *tree); 70 | state.ResumeTiming(); 71 | 72 | Remove(state, *tree); 73 | 74 | state.PauseTiming(); 75 | // avoid measuring deallocation 76 | delete tree; 77 | state.ResumeTiming(); 78 | } 79 | } 80 | 81 | template 82 | void IndexBenchmark::SetupWorld(benchmark::State& state) { 83 | logging::info("Setting up world with {} entities and {} dimensions.", num_entities_, DIM); 84 | CreatePointData(points_, data_type_, num_entities_, 0, GLOBAL_MAX); 85 | 86 | state.counters["total_remove_count"] = benchmark::Counter(0); 87 | state.counters["remove_rate"] = benchmark::Counter(0, benchmark::Counter::kIsRate); 88 | 89 | logging::info("World setup complete."); 90 | } 91 | 92 | template 93 | void IndexBenchmark::Insert(benchmark::State&, PhTreeD& tree) { 94 | for (size_t i = 0; i < num_entities_; ++i) { 95 | tree.emplace(points_[i], (int)i); 96 | } 97 | } 98 | 99 | template 100 | void IndexBenchmark::Remove(benchmark::State& state, PhTreeD& tree) { 101 | size_t n = 0; 102 | for (size_t i = 0; i < num_entities_; ++i) { 103 | n += tree.erase(points_[i]); 104 | } 105 | 106 | state.counters["total_remove_count"] += n; 107 | state.counters["remove_rate"] += n; 108 | } 109 | 110 | } // namespace 111 | 112 | template 113 | void PhTree6D(benchmark::State& state, Arguments&&...) { 114 | IndexBenchmark<6> benchmark{state}; 115 | benchmark.Benchmark(state); 116 | } 117 | 118 | template 119 | void PhTree10D(benchmark::State& state, Arguments&&...) { 120 | IndexBenchmark<10> benchmark{state}; 121 | benchmark.Benchmark(state); 122 | } 123 | 124 | template 125 | void PhTree20D(benchmark::State& state, Arguments&&...) { 126 | IndexBenchmark<20> benchmark{state}; 127 | benchmark.Benchmark(state); 128 | } 129 | 130 | // index type, scenario name, data_generator, num_entities 131 | BENCHMARK_CAPTURE(PhTree6D, ERASE, 0) 132 | ->RangeMultiplier(10) 133 | ->Ranges({{1000, 1000 * 1000}, {TestGenerator::CLUSTER, TestGenerator::CUBE}}) 134 | ->Unit(benchmark::kMillisecond); 135 | 136 | BENCHMARK_CAPTURE(PhTree10D, ERASE, 0) 137 | ->RangeMultiplier(10) 138 | ->Ranges({{1000, 1000 * 1000}, {TestGenerator::CLUSTER, TestGenerator::CUBE}}) 139 | ->Unit(benchmark::kMillisecond); 140 | 141 | BENCHMARK_CAPTURE(PhTree20D, ERASE, 0) 142 | ->RangeMultiplier(10) 143 | ->Ranges({{1000, 1000 * 1000}, {TestGenerator::CLUSTER, TestGenerator::CUBE}}) 144 | ->Unit(benchmark::kMillisecond); 145 | 146 | BENCHMARK_MAIN(); 147 | -------------------------------------------------------------------------------- /benchmark/hd_insert_d_benchmark.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Improbable Worlds Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #include "benchmark_util.h" 17 | #include "logging.h" 18 | #include "phtree/phtree.h" 19 | #include 20 | 21 | using namespace improbable; 22 | using namespace improbable::phtree; 23 | using namespace improbable::phtree::phbenchmark; 24 | 25 | namespace { 26 | 27 | const double GLOBAL_MAX = 10000; 28 | 29 | /* 30 | * Benchmark for adding entries to the index. 31 | */ 32 | template 33 | class IndexBenchmark { 34 | using Index = PhTreeD; 35 | 36 | public: 37 | explicit IndexBenchmark(benchmark::State& state); 38 | void Benchmark(benchmark::State& state); 39 | 40 | private: 41 | void SetupWorld(benchmark::State& state); 42 | void Insert(benchmark::State& state, Index& tree); 43 | 44 | const TestGenerator data_type_; 45 | const size_t num_entities_; 46 | std::vector> points_; 47 | }; 48 | 49 | template 50 | IndexBenchmark::IndexBenchmark(benchmark::State& state) 51 | : data_type_{static_cast(state.range(1))} 52 | , num_entities_(state.range(0)) 53 | , points_(state.range(0)) { 54 | logging::SetupDefaultLogging(); 55 | SetupWorld(state); 56 | } 57 | 58 | template 59 | void IndexBenchmark::Benchmark(benchmark::State& state) { 60 | for (auto _ : state) { 61 | state.PauseTiming(); 62 | auto* tree = new Index(); 63 | state.ResumeTiming(); 64 | 65 | Insert(state, *tree); 66 | 67 | // we do this top avoid measuring deallocation 68 | state.PauseTiming(); 69 | delete tree; 70 | state.ResumeTiming(); 71 | } 72 | } 73 | 74 | template 75 | void IndexBenchmark::SetupWorld(benchmark::State& state) { 76 | logging::info("Setting up world with {} entities and {} dimensions.", num_entities_, DIM); 77 | CreatePointData(points_, data_type_, num_entities_, 0, GLOBAL_MAX); 78 | 79 | state.counters["total_put_count"] = benchmark::Counter(0); 80 | state.counters["put_rate"] = benchmark::Counter(0, benchmark::Counter::kIsRate); 81 | 82 | logging::info("World setup complete."); 83 | } 84 | 85 | template 86 | void IndexBenchmark::Insert(benchmark::State& state, Index& tree) { 87 | for (size_t i = 0; i < num_entities_; ++i) { 88 | PhPointD& p = points_[i]; 89 | tree.emplace(p, (int)i); 90 | } 91 | 92 | state.counters["total_put_count"] += num_entities_; 93 | state.counters["put_rate"] += num_entities_; 94 | } 95 | 96 | } // namespace 97 | 98 | template 99 | void PhTree6D(benchmark::State& state, Arguments&&...) { 100 | IndexBenchmark<6> benchmark{state}; 101 | benchmark.Benchmark(state); 102 | } 103 | 104 | template 105 | void PhTree10D(benchmark::State& state, Arguments&&...) { 106 | IndexBenchmark<10> benchmark{state}; 107 | benchmark.Benchmark(state); 108 | } 109 | 110 | template 111 | void PhTree20D(benchmark::State& state, Arguments&&...) { 112 | IndexBenchmark<20> benchmark{state}; 113 | benchmark.Benchmark(state); 114 | } 115 | 116 | // index type, scenario name, data_generator, num_entities 117 | BENCHMARK_CAPTURE(PhTree6D, INSERT, 0) 118 | ->RangeMultiplier(10) 119 | ->Ranges({{1000, 1000 * 1000}, {TestGenerator::CLUSTER, TestGenerator::CUBE}}) 120 | ->Unit(benchmark::kMillisecond); 121 | 122 | BENCHMARK_CAPTURE(PhTree10D, INSERT, 0) 123 | ->RangeMultiplier(10) 124 | ->Ranges({{1000, 1000 * 1000}, {TestGenerator::CLUSTER, TestGenerator::CUBE}}) 125 | ->Unit(benchmark::kMillisecond); 126 | 127 | BENCHMARK_CAPTURE(PhTree20D, INSERT, 0) 128 | ->RangeMultiplier(10) 129 | ->Ranges({{1000, 1000 * 1000}, {TestGenerator::CLUSTER, TestGenerator::CUBE}}) 130 | ->Unit(benchmark::kMillisecond); 131 | 132 | BENCHMARK_MAIN(); 133 | -------------------------------------------------------------------------------- /benchmark/hd_knn_d_benchmark.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Improbable Worlds Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #include "benchmark_util.h" 17 | #include "logging.h" 18 | #include "phtree/phtree.h" 19 | #include 20 | #include 21 | 22 | using namespace improbable; 23 | using namespace improbable::phtree; 24 | using namespace improbable::phtree::phbenchmark; 25 | 26 | namespace { 27 | 28 | const double GLOBAL_MAX = 10000; 29 | using payload_t = std::uint32_t; 30 | 31 | /* 32 | * Benchmark for k-nearest-neighbour queries. 33 | */ 34 | template 35 | class IndexBenchmark { 36 | public: 37 | IndexBenchmark(benchmark::State& state); 38 | 39 | void Benchmark(benchmark::State& state); 40 | 41 | private: 42 | void SetupWorld(benchmark::State& state); 43 | void QueryWorld(benchmark::State& state, PhPointD& center); 44 | void CreateQuery(PhPointD& center); 45 | 46 | const TestGenerator data_type_; 47 | const size_t num_entities_; 48 | const size_t knn_result_size_; 49 | 50 | PhTreeD tree_; 51 | std::default_random_engine random_engine_; 52 | std::uniform_real_distribution<> cube_distribution_; 53 | std::vector> points_; 54 | }; 55 | 56 | template 57 | IndexBenchmark::IndexBenchmark(benchmark::State& state) 58 | : data_type_{static_cast(state.range(2))} 59 | , num_entities_(state.range(0)) 60 | , knn_result_size_(state.range(1)) 61 | , random_engine_{1} 62 | , cube_distribution_{0, GLOBAL_MAX} 63 | , points_(state.range(0)) { 64 | logging::SetupDefaultLogging(); 65 | SetupWorld(state); 66 | } 67 | 68 | template 69 | void IndexBenchmark::Benchmark(benchmark::State& state) { 70 | for (auto _ : state) { 71 | state.PauseTiming(); 72 | PhPointD center; 73 | CreateQuery(center); 74 | state.ResumeTiming(); 75 | 76 | QueryWorld(state, center); 77 | } 78 | } 79 | 80 | template 81 | void IndexBenchmark::SetupWorld(benchmark::State& state) { 82 | logging::info("Setting up world with {} entities and {} dimensions.", num_entities_, DIM); 83 | CreatePointData(points_, data_type_, num_entities_, 0, GLOBAL_MAX); 84 | for (size_t i = 0; i < num_entities_; ++i) { 85 | tree_.emplace(points_[i], (int)i); 86 | } 87 | 88 | state.counters["total_query_count"] = benchmark::Counter(0); 89 | state.counters["query_rate"] = benchmark::Counter(0, benchmark::Counter::kIsRate); 90 | state.counters["avg_result_count"] = benchmark::Counter(0, benchmark::Counter::kAvgIterations); 91 | 92 | logging::info("World setup complete."); 93 | } 94 | 95 | template 96 | void IndexBenchmark::QueryWorld(benchmark::State& state, PhPointD& center) { 97 | size_t n = 0; 98 | for (auto q = tree_.begin_knn_query(knn_result_size_, center, DistanceEuclidean()); 99 | q != tree_.end(); 100 | ++q) { 101 | ++n; 102 | } 103 | 104 | state.counters["total_query_count"] += 1; 105 | state.counters["query_rate"] += 1; 106 | state.counters["avg_result_count"] += n; 107 | } 108 | 109 | template 110 | void IndexBenchmark::CreateQuery(PhPointD& center) { 111 | for (dimension_t d = 0; d < DIM; ++d) { 112 | center[d] = cube_distribution_(random_engine_) * GLOBAL_MAX; 113 | } 114 | } 115 | 116 | } // namespace 117 | 118 | template 119 | void PhTree6D(benchmark::State& state, Arguments&&...) { 120 | IndexBenchmark<6> benchmark{state}; 121 | benchmark.Benchmark(state); 122 | } 123 | 124 | template 125 | void PhTree10D(benchmark::State& state, Arguments&&...) { 126 | IndexBenchmark<10> benchmark{state}; 127 | benchmark.Benchmark(state); 128 | } 129 | 130 | template 131 | void PhTree20D(benchmark::State& state, Arguments&&...) { 132 | IndexBenchmark<20> benchmark{state}; 133 | benchmark.Benchmark(state); 134 | } 135 | 136 | // index type, scenario name, data_type, num_entities, query_result_size 137 | BENCHMARK_CAPTURE(PhTree6D, KNN, 0) 138 | ->RangeMultiplier(10) 139 | ->Ranges({{1000, 1000 * 1000}, {1, 10}, {TestGenerator::CLUSTER, TestGenerator::CUBE}}) 140 | ->Unit(benchmark::kMillisecond); 141 | 142 | BENCHMARK_CAPTURE(PhTree10D, KNN, 0) 143 | ->RangeMultiplier(10) 144 | ->Ranges({{1000, 1000 * 1000}, {1, 10}, {TestGenerator::CLUSTER, TestGenerator::CUBE}}) 145 | ->Unit(benchmark::kMillisecond); 146 | 147 | BENCHMARK_CAPTURE(PhTree20D, KNN, 0) 148 | ->RangeMultiplier(10) 149 | ->Ranges({{1000, 1000 * 1000}, {1, 10}, {TestGenerator::CLUSTER, TestGenerator::CUBE}}) 150 | ->Unit(benchmark::kMillisecond); 151 | 152 | BENCHMARK_MAIN(); 153 | -------------------------------------------------------------------------------- /benchmark/insert_benchmark.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Improbable Worlds Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #include "benchmark_util.h" 17 | #include "logging.h" 18 | #include "phtree/phtree.h" 19 | #include 20 | 21 | using namespace improbable; 22 | using namespace improbable::phtree; 23 | using namespace improbable::phtree::phbenchmark; 24 | 25 | namespace { 26 | 27 | const int GLOBAL_MAX = 10000; 28 | 29 | enum InsertionType { 30 | INSERT, 31 | EMPLACE, 32 | SQUARE_BR, 33 | }; 34 | 35 | /* 36 | * Benchmark for adding entries to the index. 37 | */ 38 | template 39 | class IndexBenchmark { 40 | using Index = PhTree; 41 | 42 | public: 43 | explicit IndexBenchmark(benchmark::State& state); 44 | 45 | void Benchmark(benchmark::State& state); 46 | 47 | private: 48 | void SetupWorld(benchmark::State& state); 49 | 50 | void Insert(benchmark::State& state, Index& tree); 51 | 52 | const TestGenerator data_type_; 53 | const size_t num_entities_; 54 | std::vector> points_; 55 | }; 56 | 57 | template 58 | IndexBenchmark::IndexBenchmark(benchmark::State& state) 59 | : data_type_{static_cast(state.range(1))} 60 | , num_entities_(state.range(0)) 61 | , points_(state.range(0)) { 62 | logging::SetupDefaultLogging(); 63 | SetupWorld(state); 64 | } 65 | 66 | template 67 | void IndexBenchmark::Benchmark(benchmark::State& state) { 68 | for (auto _ : state) { 69 | state.PauseTiming(); 70 | auto* tree = new Index(); 71 | state.ResumeTiming(); 72 | 73 | Insert(state, *tree); 74 | 75 | // we do this top avoid measuring deallocation 76 | state.PauseTiming(); 77 | delete tree; 78 | state.ResumeTiming(); 79 | } 80 | } 81 | 82 | template 83 | void IndexBenchmark::SetupWorld(benchmark::State& state) { 84 | logging::info("Setting up world with {} entities and {} dimensions.", num_entities_, DIM); 85 | CreatePointData(points_, data_type_, num_entities_, 0, GLOBAL_MAX); 86 | 87 | state.counters["total_put_count"] = benchmark::Counter(0); 88 | state.counters["put_rate"] = benchmark::Counter(0, benchmark::Counter::kIsRate); 89 | 90 | logging::info("World setup complete."); 91 | } 92 | 93 | template 94 | void IndexBenchmark::Insert(benchmark::State& state, Index& tree) { 95 | switch (TYPE) { 96 | case INSERT: { 97 | for (size_t i = 0; i < num_entities_; ++i) { 98 | tree.insert(points_[i], (int)i); 99 | } 100 | break; 101 | } 102 | case EMPLACE: { 103 | for (size_t i = 0; i < num_entities_; ++i) { 104 | tree.emplace(points_[i], (int)i); 105 | } 106 | break; 107 | } 108 | case SQUARE_BR: { 109 | for (size_t i = 0; i < num_entities_; ++i) { 110 | tree[points_[i]] = (int)i; 111 | } 112 | break; 113 | } 114 | } 115 | 116 | state.counters["total_put_count"] += num_entities_; 117 | state.counters["put_rate"] += num_entities_; 118 | } 119 | 120 | } // namespace 121 | 122 | template 123 | void PhTree3D_INS(benchmark::State& state, Arguments&&...) { 124 | IndexBenchmark<3, INSERT> benchmark{state}; 125 | benchmark.Benchmark(state); 126 | } 127 | 128 | template 129 | void PhTree3D_EMP(benchmark::State& state, Arguments&&...) { 130 | IndexBenchmark<3, EMPLACE> benchmark{state}; 131 | benchmark.Benchmark(state); 132 | } 133 | 134 | template 135 | void PhTree3D_SQB(benchmark::State& state, Arguments&&...) { 136 | IndexBenchmark<3, SQUARE_BR> benchmark{state}; 137 | benchmark.Benchmark(state); 138 | } 139 | 140 | // index type, scenario name, data_generator, num_entities, function_to_call 141 | BENCHMARK_CAPTURE(PhTree3D_INS, INSERT, 0) 142 | ->RangeMultiplier(10) 143 | ->Ranges({{1000, 10 * 1000 * 1000}, {TestGenerator::CLUSTER, TestGenerator::CUBE}}) 144 | ->Unit(benchmark::kMillisecond); 145 | 146 | BENCHMARK_CAPTURE(PhTree3D_EMP, EMPLACE, 0) 147 | ->RangeMultiplier(10) 148 | ->Ranges({{1000, 10 * 1000 * 1000}, {TestGenerator::CLUSTER, TestGenerator::CUBE}}) 149 | ->Unit(benchmark::kMillisecond); 150 | 151 | BENCHMARK_CAPTURE(PhTree3D_SQB, SQUARE_BR, 0) 152 | ->RangeMultiplier(10) 153 | ->Ranges({{1000, 10 * 1000 * 1000}, {TestGenerator::CLUSTER, TestGenerator::CUBE}}) 154 | ->Unit(benchmark::kMillisecond); 155 | 156 | BENCHMARK_MAIN(); 157 | -------------------------------------------------------------------------------- /benchmark/insert_box_d_benchmark.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Improbable Worlds Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #include "benchmark_util.h" 17 | #include "logging.h" 18 | #include "phtree/phtree.h" 19 | #include 20 | 21 | using namespace improbable; 22 | using namespace improbable::phtree; 23 | using namespace improbable::phtree::phbenchmark; 24 | 25 | namespace { 26 | 27 | const double GLOBAL_MAX = 10000; 28 | const double BOX_LEN = 10; 29 | 30 | /* 31 | * Benchmark for adding entries to the index. 32 | */ 33 | template 34 | class IndexBenchmark { 35 | using Index = PhTreeBoxD; 36 | 37 | public: 38 | explicit IndexBenchmark(benchmark::State& state); 39 | 40 | void Benchmark(benchmark::State& state); 41 | 42 | private: 43 | void SetupWorld(benchmark::State& state); 44 | 45 | void Insert(benchmark::State& state, Index& tree); 46 | 47 | const TestGenerator data_type_; 48 | const size_t num_entities_; 49 | std::vector> boxes_; 50 | }; 51 | 52 | template 53 | IndexBenchmark::IndexBenchmark(benchmark::State& state) 54 | : data_type_{static_cast(state.range(1))} 55 | , num_entities_(state.range(0)) 56 | , boxes_(state.range(0)) { 57 | logging::SetupDefaultLogging(); 58 | SetupWorld(state); 59 | } 60 | 61 | template 62 | void IndexBenchmark::Benchmark(benchmark::State& state) { 63 | for (auto _ : state) { 64 | state.PauseTiming(); 65 | auto* tree = new Index(); 66 | state.ResumeTiming(); 67 | 68 | Insert(state, *tree); 69 | 70 | // we do this top avoid measuring deallocation 71 | state.PauseTiming(); 72 | delete tree; 73 | state.ResumeTiming(); 74 | } 75 | } 76 | 77 | template 78 | void IndexBenchmark::SetupWorld(benchmark::State& state) { 79 | logging::info("Setting up world with {} entities and {} dimensions.", num_entities_, DIM); 80 | CreateBoxData(boxes_, data_type_, num_entities_, 0, GLOBAL_MAX, BOX_LEN); 81 | 82 | state.counters["total_put_count"] = benchmark::Counter(0); 83 | state.counters["put_rate"] = benchmark::Counter(0, benchmark::Counter::kIsRate); 84 | 85 | logging::info("World setup complete."); 86 | } 87 | 88 | template 89 | void IndexBenchmark::Insert(benchmark::State& state, Index& tree) { 90 | for (size_t i = 0; i < num_entities_; ++i) { 91 | PhBoxD& p = boxes_[i]; 92 | tree.emplace(p, (int)i); 93 | } 94 | 95 | state.counters["total_put_count"] += num_entities_; 96 | state.counters["put_rate"] += num_entities_; 97 | } 98 | 99 | } // namespace 100 | 101 | template 102 | void PhTree3D(benchmark::State& state, Arguments&&...) { 103 | IndexBenchmark<3> benchmark{state}; 104 | benchmark.Benchmark(state); 105 | } 106 | 107 | // index type, scenario name, data_generator, num_entities 108 | BENCHMARK_CAPTURE(PhTree3D, INSERT, 0) 109 | ->RangeMultiplier(10) 110 | ->Ranges({{1000, 10 * 1000 * 1000}, {TestGenerator::CUBE, TestGenerator::CLUSTER}}) 111 | ->Unit(benchmark::kMillisecond); 112 | 113 | BENCHMARK_MAIN(); 114 | -------------------------------------------------------------------------------- /benchmark/insert_d_benchmark.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Improbable Worlds Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #include "benchmark_util.h" 17 | #include "logging.h" 18 | #include "phtree/phtree.h" 19 | #include 20 | 21 | using namespace improbable; 22 | using namespace improbable::phtree; 23 | using namespace improbable::phtree::phbenchmark; 24 | 25 | namespace { 26 | 27 | const double GLOBAL_MAX = 10000; 28 | 29 | enum InsertionType { 30 | INSERT, 31 | EMPLACE, 32 | SQUARE_BR, 33 | }; 34 | 35 | /* 36 | * Benchmark for adding entries to the index. 37 | */ 38 | template 39 | class IndexBenchmark { 40 | using Index = PhTreeD; 41 | 42 | public: 43 | explicit IndexBenchmark(benchmark::State& state); 44 | 45 | void Benchmark(benchmark::State& state); 46 | 47 | private: 48 | void SetupWorld(benchmark::State& state); 49 | 50 | void Insert(benchmark::State& state, Index& tree); 51 | 52 | const TestGenerator data_type_; 53 | const size_t num_entities_; 54 | std::vector> points_; 55 | }; 56 | 57 | template 58 | IndexBenchmark::IndexBenchmark(benchmark::State& state) 59 | : data_type_{static_cast(state.range(1))} 60 | , num_entities_(state.range(0)) 61 | , points_(state.range(0)) { 62 | logging::SetupDefaultLogging(); 63 | SetupWorld(state); 64 | } 65 | 66 | template 67 | void IndexBenchmark::Benchmark(benchmark::State& state) { 68 | for (auto _ : state) { 69 | state.PauseTiming(); 70 | auto* tree = new Index(); 71 | state.ResumeTiming(); 72 | 73 | Insert(state, *tree); 74 | 75 | // we do this top avoid measuring deallocation 76 | state.PauseTiming(); 77 | delete tree; 78 | state.ResumeTiming(); 79 | } 80 | } 81 | 82 | template 83 | void IndexBenchmark::SetupWorld(benchmark::State& state) { 84 | logging::info("Setting up world with {} entities and {} dimensions.", num_entities_, DIM); 85 | CreatePointData(points_, data_type_, num_entities_, 0, GLOBAL_MAX); 86 | 87 | state.counters["total_put_count"] = benchmark::Counter(0); 88 | state.counters["put_rate"] = benchmark::Counter(0, benchmark::Counter::kIsRate); 89 | 90 | logging::info("World setup complete."); 91 | } 92 | 93 | template 94 | void IndexBenchmark::Insert(benchmark::State& state, Index& tree) { 95 | for (size_t i = 0; i < num_entities_; ++i) { 96 | PhPointD& p = points_[i]; 97 | tree.emplace(p, (int)i); 98 | } 99 | 100 | state.counters["total_put_count"] += num_entities_; 101 | state.counters["put_rate"] += num_entities_; 102 | } 103 | 104 | } // namespace 105 | 106 | template 107 | void PhTree3D(benchmark::State& state, Arguments&&...) { 108 | IndexBenchmark<3> benchmark{state}; 109 | benchmark.Benchmark(state); 110 | } 111 | 112 | template 113 | void PhTree6D(benchmark::State& state, Arguments&&...) { 114 | IndexBenchmark<6> benchmark{state}; 115 | benchmark.Benchmark(state); 116 | } 117 | 118 | template 119 | void PhTree10D(benchmark::State& state, Arguments&&...) { 120 | IndexBenchmark<10> benchmark{state}; 121 | benchmark.Benchmark(state); 122 | } 123 | 124 | template 125 | void PhTree20D(benchmark::State& state, Arguments&&...) { 126 | IndexBenchmark<20> benchmark{state}; 127 | benchmark.Benchmark(state); 128 | } 129 | 130 | // index type, scenario name, data_generator, num_entities 131 | // BENCHMARK_CAPTURE(PhTree3D, INSERT, 0) 132 | //->RangeMultiplier(10) 133 | //->Ranges({{1000, 10 * 1000 * 1000}, {TestGenerator::CLUSTER, TestGenerator::CUBE}}) 134 | //->Unit(benchmark::kMillisecond); 135 | 136 | BENCHMARK_CAPTURE(PhTree3D, INSERT, 0) 137 | ->RangeMultiplier(10) 138 | ->Ranges({{1000, 10 * 1000 * 1000}, {TestGenerator::CUBE, TestGenerator::CLUSTER}}) 139 | ->Unit(benchmark::kMillisecond); 140 | 141 | BENCHMARK_CAPTURE(PhTree6D, INSERT, 0) 142 | ->RangeMultiplier(10) 143 | ->Ranges({{100 * 1000, 1000 * 1000}, {TestGenerator::CLUSTER, TestGenerator::CUBE}}) 144 | ->Unit(benchmark::kMillisecond); 145 | 146 | BENCHMARK_CAPTURE(PhTree10D, INSERT, 0) 147 | ->RangeMultiplier(10) 148 | ->Ranges({{100 * 1000, 1000 * 1000}, {TestGenerator::CLUSTER, TestGenerator::CUBE}}) 149 | ->Unit(benchmark::kMillisecond); 150 | 151 | BENCHMARK_CAPTURE(PhTree20D, INSERT, 0) 152 | ->RangeMultiplier(10) 153 | ->Ranges({{100 * 1000, 1000 * 1000}, {TestGenerator::CLUSTER, TestGenerator::CUBE}}) 154 | ->Unit(benchmark::kMillisecond); 155 | 156 | BENCHMARK_MAIN(); 157 | -------------------------------------------------------------------------------- /benchmark/logging.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Improbable Worlds Ltd, All Rights Reserved 2 | #ifndef PHTREE_BENCHMARK_LOGGING_H 3 | #define PHTREE_BENCHMARK_LOGGING_H 4 | 5 | #include 6 | #include 7 | #ifdef _WIN32 8 | #include 9 | #else 10 | #include 11 | #endif 12 | 13 | namespace improbable::phtree::phbenchmark::logging { 14 | 15 | #ifdef _WIN32 16 | using ConsoleSpdlogSink = spdlog::sinks::wincolor_stdout_sink_mt; 17 | #else 18 | using ConsoleSpdlogSink = spdlog::sinks::ansicolor_stdout_sink_mt; 19 | #endif 20 | 21 | constexpr auto kInternalLoggerName = "internal"; 22 | 23 | // Sets up spdlog for internal and external. If you need to do some logging before doing this 24 | // call, use instead CaptureLogMessagesToBufferSink()/SetupLoggingAndFlushBuffer. 25 | void SetupLogging(std::vector sinks, spdlog::level::level_enum log_level) { 26 | auto& console_sink = sinks.emplace_back(std::make_shared()); 27 | console_sink->set_level(log_level); 28 | 29 | // Find the minimum log level, in case one of the sinks passed to us has a lower log level. 30 | const auto& sink_with_lowest_log_level = *std::min_element( 31 | sinks.begin(), 32 | sinks.end(), 33 | [](const spdlog::sink_ptr& a, const spdlog::sink_ptr& b) -> bool { 34 | return a->level() < b->level(); 35 | }); 36 | spdlog::level::level_enum min_log_level = 37 | std::min(sink_with_lowest_log_level->level(), log_level); 38 | 39 | // Create the external logger, worker logger and the internal (default) logger from the same log 40 | // sinks. Each logsink can use `GetLoggerTypeFromMessage` to determine which logger a message 41 | // was logged to. 42 | spdlog::set_default_logger( 43 | std::make_shared(kInternalLoggerName, sinks.begin(), sinks.end())); 44 | spdlog::set_level(min_log_level); 45 | spdlog::flush_on(min_log_level); 46 | } 47 | 48 | // Sets up default logging typically used for tests/benchmarks. Also used for default 49 | // initialization if the logging hasn't been initialized before the first logging line. 50 | void SetupDefaultLogging() { 51 | SetupLogging({}, spdlog::level::warn); 52 | } 53 | 54 | template 55 | inline void log( 56 | spdlog::source_loc source, 57 | spdlog::level::level_enum lvl, 58 | spdlog::string_view_t fmt, 59 | const Args&... args) { 60 | spdlog::log(source, lvl, fmt, args...); 61 | } 62 | 63 | template 64 | inline void log(spdlog::level::level_enum lvl, spdlog::string_view_t fmt, const Args&... args) { 65 | spdlog::log(spdlog::source_loc{}, lvl, fmt, args...); 66 | } 67 | 68 | template 69 | inline void trace(spdlog::string_view_t fmt, const Args&... args) { 70 | log(spdlog::level::level_enum::trace, fmt, args...); 71 | } 72 | 73 | template 74 | inline void debug(spdlog::string_view_t fmt, const Args&... args) { 75 | log(spdlog::level::level_enum::debug, fmt, args...); 76 | } 77 | 78 | template 79 | inline void info(spdlog::string_view_t fmt, const Args&... args) { 80 | log(spdlog::level::level_enum::info, fmt, args...); 81 | } 82 | 83 | template 84 | inline void warn(spdlog::string_view_t fmt, const Args&... args) { 85 | log(spdlog::level::level_enum::warn, fmt, args...); 86 | } 87 | 88 | template 89 | inline void error(spdlog::string_view_t fmt, const Args&... args) { 90 | log(spdlog::level::level_enum::err, fmt, args...); 91 | } 92 | 93 | template 94 | inline void critical(spdlog::string_view_t fmt, const Args&... args) { 95 | log(spdlog::level::level_enum::critical, fmt, args...); 96 | } 97 | 98 | template 99 | inline void log(spdlog::source_loc source, spdlog::level::level_enum lvl, const T& msg) { 100 | spdlog::log(source, lvl, msg); 101 | } 102 | 103 | template 104 | inline void log(spdlog::level::level_enum lvl, const T& msg) { 105 | spdlog::log(lvl, msg); 106 | } 107 | 108 | template 109 | inline void trace(const T& msg) { 110 | log(spdlog::level::level_enum::trace, msg); 111 | } 112 | 113 | template 114 | inline void debug(const T& msg) { 115 | log(spdlog::level::level_enum::debug, msg); 116 | } 117 | 118 | template 119 | inline void info(const T& msg) { 120 | log(spdlog::level::level_enum::info, msg); 121 | } 122 | 123 | template 124 | inline void warn(const T& msg) { 125 | log(spdlog::level::level_enum::warn, msg); 126 | } 127 | 128 | template 129 | inline void error(const T& msg) { 130 | log(spdlog::level::level_enum::err, msg); 131 | } 132 | 133 | template 134 | inline void critical(const T& msg) { 135 | log(spdlog::level::level_enum::critical, msg); 136 | } 137 | 138 | } // namespace improbable::phtree::phbenchmark::logging 139 | 140 | #endif // PHTREE_BENCHMARK_LOGGING_H 141 | -------------------------------------------------------------------------------- /benchmark/query_benchmark.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Improbable Worlds Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #include "benchmark_util.h" 17 | #include "logging.h" 18 | #include "phtree/phtree.h" 19 | #include 20 | #include 21 | 22 | using namespace improbable; 23 | using namespace improbable::phtree; 24 | using namespace improbable::phtree::phbenchmark; 25 | 26 | namespace { 27 | 28 | const int GLOBAL_MAX = 10000; 29 | 30 | /* 31 | * Benchmark for window queries. 32 | */ 33 | template 34 | class IndexBenchmark { 35 | public: 36 | IndexBenchmark(benchmark::State& state, double avg_query_result_size_); 37 | 38 | void Benchmark(benchmark::State& state); 39 | 40 | private: 41 | void SetupWorld(benchmark::State& state); 42 | void QueryWorld(benchmark::State& state, PhBox& query); 43 | void CreateQuery(PhBox& query); 44 | 45 | const TestGenerator data_type_; 46 | const size_t num_entities_; 47 | const double avg_query_result_size_; 48 | 49 | constexpr int query_edge_length() { 50 | return ( 51 | int)(GLOBAL_MAX * pow(avg_query_result_size_ / (double)num_entities_, 1. / (double)DIM)); 52 | }; 53 | 54 | PhTree tree_; 55 | std::default_random_engine random_engine_; 56 | std::uniform_int_distribution<> cube_distribution_; 57 | std::vector> points_; 58 | }; 59 | 60 | template 61 | IndexBenchmark::IndexBenchmark(benchmark::State& state, double avg_query_result_size) 62 | : data_type_{static_cast(state.range(1))} 63 | , num_entities_(state.range(0)) 64 | , avg_query_result_size_(avg_query_result_size) 65 | , tree_{} 66 | , random_engine_{1} 67 | , cube_distribution_{0, GLOBAL_MAX} 68 | , points_(state.range(0)) { 69 | logging::SetupDefaultLogging(); 70 | SetupWorld(state); 71 | } 72 | 73 | template 74 | void IndexBenchmark::Benchmark(benchmark::State& state) { 75 | for (auto _ : state) { 76 | state.PauseTiming(); 77 | PhBox query_box; 78 | CreateQuery(query_box); 79 | state.ResumeTiming(); 80 | 81 | QueryWorld(state, query_box); 82 | } 83 | } 84 | 85 | template 86 | void IndexBenchmark::SetupWorld(benchmark::State& state) { 87 | logging::info("Setting up world with {} entities and {} dimensions.", num_entities_, DIM); 88 | CreatePointData(points_, data_type_, num_entities_, 0, GLOBAL_MAX); 89 | for (size_t i = 0; i < num_entities_; ++i) { 90 | tree_.emplace(points_[i], (int)i); 91 | } 92 | 93 | state.counters["query_rate"] = benchmark::Counter(0, benchmark::Counter::kIsRate); 94 | state.counters["result_rate"] = benchmark::Counter(0, benchmark::Counter::kIsRate); 95 | state.counters["avg_result_count"] = benchmark::Counter(0, benchmark::Counter::kAvgIterations); 96 | logging::info("World setup complete."); 97 | } 98 | 99 | template 100 | void IndexBenchmark::QueryWorld(benchmark::State& state, PhBox& query_box) { 101 | int n = 0; 102 | for (auto q = tree_.begin_query(query_box); q != tree_.end(); ++q) { 103 | ++n; 104 | } 105 | 106 | state.counters["query_rate"] += 1; 107 | state.counters["result_rate"] += n; 108 | state.counters["avg_result_count"] += n; 109 | } 110 | 111 | template 112 | void IndexBenchmark::CreateQuery(PhBox& query_box) { 113 | int length = query_edge_length(); 114 | // shift to ensure query lies within boundary 115 | double shift = (GLOBAL_MAX - (double)length) / GLOBAL_MAX; 116 | for (dimension_t d = 0; d < DIM; ++d) { 117 | scalar_64_t s = (scalar_64_t)(shift * cube_distribution_(random_engine_)); 118 | query_box.min()[d] = s; 119 | query_box.max()[d] = s + length; 120 | } 121 | } 122 | 123 | } // namespace 124 | 125 | template 126 | void PhTree3D(benchmark::State& state, Arguments&&... arguments) { 127 | IndexBenchmark<3> benchmark{state, arguments...}; 128 | benchmark.Benchmark(state); 129 | } 130 | 131 | // index type, scenario name, data_type, num_entities, query_result_size 132 | BENCHMARK_CAPTURE(PhTree3D, WQ_100, 100.0) 133 | ->RangeMultiplier(10) 134 | ->Ranges({{1000, 1000 * 1000}, {TestGenerator::CUBE, TestGenerator::CLUSTER}}) 135 | ->Unit(benchmark::kMillisecond); 136 | 137 | BENCHMARK_MAIN(); 138 | -------------------------------------------------------------------------------- /ci/includes/os.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | function isLinux() { 4 | [[ "$(uname -s)" == "Linux" ]]; 5 | } 6 | 7 | function isMacOS() { 8 | [[ "$(uname -s)" == "Darwin" ]]; 9 | } 10 | 11 | function isWindows() { 12 | ! ( isLinux || isMacOS ); 13 | } 14 | -------------------------------------------------------------------------------- /ci/linting/buildifier.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -x -e -u -o pipefail 4 | 5 | cd "$(dirname "$0")/../../" 6 | 7 | source ci/includes/os.sh 8 | 9 | MAYBEARG='-mode=check' 10 | if [ $# -eq 1 ]; then 11 | if [ "$1" = "-fix" ]; then 12 | echo -e "\033[0;34mAttempting to fix linting errors automatically as '-fix' is specified.\033[0m" 13 | MAYBEARG='' 14 | fi 15 | fi 16 | 17 | # Ensure Bazel is installed. 18 | bazel version 19 | 20 | if bazel run buildifier -- ${MAYBEARG} -v $(find "$(pwd)/" \( -name BUILD -o -name WORKSPACE \) -type f); then 21 | echo -e "\033[0;32mAll BUILD and WORKSPACE files passed buildifier linting check.\033[0m" 22 | else 23 | echo -e "\033[0;31mThe above listed BUILD and WORKSPACE file(s) didn't pass the buildifier linting check!\033[0m" 24 | echo -e "\033[0;34mYou can run 'ci/linting/buildifier.sh -fix' to fix them automatically.\033[0m" 25 | exit 1 26 | fi 27 | 28 | -------------------------------------------------------------------------------- /ci/linting/clang-format.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e -u -o pipefail 4 | 5 | source ci/includes/os.sh 6 | 7 | TARGETS="//..." 8 | EXCLUDED_TARGETS="" 9 | 10 | # NOTE: We need to set the 'MSYS_ARG_CONV_EXCL' environment variable in various places, because 11 | # otherwise on Windows, MSYS2 does weird things with Bazel target paths (see 12 | # http://www.mingw.org/wiki/Posix_path_conversion). 13 | export MSYS2_ARG_CONV_EXCL="//..." 14 | 15 | # Function to join arrays with a specified string. 16 | # Taken from: https://stackoverflow.com/questions/1527049/how-can-i-join-elements-of-an-array-in-bash#comment37571340_17841619 17 | function joinBy { perl -e '$s = shift @ARGV; print join($s, @ARGV);' "$@"; } 18 | 19 | function clangFormatLocation() { 20 | local CLANG_FORMAT_VERSION=10.0.0 21 | # Use find to get the path for either clang-format (macOS / Linux) or clang-format.exe (Windows) 22 | local CLANG_FORMAT_EXE=clang-format 23 | local CLANG_FORMAT_SHIM=clang-format 24 | if isWindows; then 25 | CLANG_FORMAT_EXE=clang-format.exe 26 | fi 27 | 28 | CLANG_FORMAT_VERSION_CALL="$("${CLANG_FORMAT_SHIM}" -version)" # Ensures that the binary is downloaded. 29 | 30 | local CLANG_FORMAT=CLANG_FORMAT_EXE 31 | if [[ ${CLANG_FORMAT} == "" ]]; then 32 | echo "ERROR: could not locate clang-format" 33 | echo "clang-format -version: ${CLANG_FORMAT_VERSION_CALL}" 34 | echo "which clang-format: $(which clang-format)" 35 | exit 1 36 | fi 37 | echo "${CLANG_FORMAT}" 38 | } 39 | 40 | # Generates a string of the form `... -...` 41 | function generateBuildTargetString() { 42 | TARGET_STRING="${TARGETS}" 43 | 44 | # Append target exclusions only if there are excluded targets. 45 | if ! [[ -z "${EXCLUDED_TARGETS}" ]]; then 46 | TARGET_STRING="${TARGET_STRING} -$(joinBy " -" ${EXCLUDED_TARGETS})" 47 | fi 48 | 49 | echo "${TARGET_STRING}" 50 | } 51 | 52 | # Generates a string of the form ` [union ]* except ( [union ]*)` 53 | # i.e. worker_sdk/... union applications except (worker_sdk/common:some_target union worker_sdk/common:some_other_target) 54 | function generateAqueryTargetString() { 55 | TARGET_STRING="$(joinBy " union " ${TARGETS})" 56 | 57 | # Append target exclusions only if there are excluded targets. 58 | if ! [[ -z "${EXCLUDED_TARGETS}" ]]; then 59 | TARGET_STRING="${TARGET_STRING} except ($(joinBy " union " ${EXCLUDED_TARGETS}))" 60 | fi 61 | 62 | echo "${TARGET_STRING}" 63 | } 64 | 65 | function bazelLintTest() { 66 | # Use bazel to create patch files for all eligible source files. 67 | # Fail if any of the patch files are non-empty (i.e. lint was detected). 68 | CLANG_FORMAT="$(clangFormatLocation)" bazel build --config lint --output_groups=clang_format_test -- $(generateBuildTargetString) 69 | } 70 | 71 | function bazelLintFix() { 72 | # Use bazel to create patch files for all eligible source files. 73 | CLANG_FORMAT="$(clangFormatLocation)" bazel build --config lint --output_groups=clang_format_patches_only -- $(generateBuildTargetString) 74 | 75 | # Find bazel-bin prefix. 76 | BAZEL_BIN=$(bazel info bazel-bin) 77 | # I.e. on Linux, this is `bazel-out/k8-gcc-opt/bin`. 78 | PREFIX=${BAZEL_BIN#$(bazel info execution_root)/} 79 | 80 | # Use aquery to get the list of output files of the `CreatePatch` action, 81 | # Then strip the patch path down to that of its source file, and apply 82 | # the patch file generated by Bazel to the original source file. 83 | CLANG_FORMAT="$(clangFormatLocation)" bazel aquery --config lint --include_aspects --output_groups clang_format_patches_only "mnemonic(\"CreatePatch\", $(generateAqueryTargetString))" --output textproto \ 84 | `# Get relative paths to source files` \ 85 | `# perl used instead of grep --perl-regexp since grep macOS doesnt support it` \ 86 | | perl -ne "while(/(?<=exec_path: \"${PREFIX//\//\\/}\/).*\.patch_.+(?=\")/g){print \"\$&\n\";}" \ 87 | `# Create the patch commands which, when executed patch the source files.` \ 88 | `# --binary flag used to correctly handle line endings on Windows.` \ 89 | | xargs -L1 -I 'PATH_TO_SOURCE_FILE' echo 'FILE_PATH="PATH_TO_SOURCE_FILE"; echo patch --binary "${FILE_PATH%.patch_*}" "'${BAZEL_BIN}'/${FILE_PATH}"' \ 90 | `# De-duplicate the patch commands, such that there is at most one patch command for each source file.` \ 91 | `# There are N patch files per source files, where N is the number of bazel targets directly including the target.` \ 92 | `# The format of the commands being de-duplicated is: patch --binary -source file- -patch file-` \ 93 | | sh `# Calls the echo command generated by xargs, resulting in the patch command to be run by the next sh invocation below`\ 94 | | sort --unique --field-separator=" " --key=3,3 `# Remove duplicate patch commands to the same source file` \ 95 | | sh `# Execute patch commands` 96 | } 97 | 98 | MAYBEARG='-mode=check' 99 | if [ $# -eq 1 ]; then 100 | if [ "$1" = "-fix" ]; then 101 | MAYBEARG='' 102 | fi 103 | fi 104 | 105 | if [[ -z "$MAYBEARG" ]]; then 106 | echo -e "\033[0;34mAttempting to fix linting errors automatically as '-fix' is specified.\033[0m" 107 | bazelLintFix 108 | elif bazelLintTest; then 109 | echo -e "\033[0;32mAll source files passed clang-format linting check.\033[0m" 110 | else 111 | echo -e "\033[0;31mThe above listed source file(s) didn't pass the clang-format linting check!\033[0m" 112 | echo -e "\033[0;34mYou can run 'ci/linting/clang-format.sh -fix' to fix them automatically.\033[0m" 113 | exit 1 114 | fi 115 | -------------------------------------------------------------------------------- /cmake/phtreeConfig.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | 3 | include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") 4 | check_required_components("@PROJECT_NAME@") 5 | -------------------------------------------------------------------------------- /examples/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:private"]) 2 | 3 | cc_binary( 4 | name = "example", 5 | srcs = ["example.cc"], 6 | visibility = [ 7 | "//visibility:public", 8 | ], 9 | deps = [ 10 | "//:phtree", 11 | ], 12 | ) 13 | -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | project(phtree-examples) 3 | 4 | add_executable(Example example.cc) 5 | target_link_libraries(Example phtree) 6 | -------------------------------------------------------------------------------- /examples/example.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Improbable Worlds Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "phtree/phtree.h" 18 | #include "phtree/phtree_multimap.h" 19 | #include 20 | #include 21 | #include 22 | 23 | using namespace improbable::phtree; 24 | 25 | int relocate_example() { 26 | //auto tree = PhTreeMultiMapD<2, int, ConverterIEEE<2>, std::unordered_set>(); 27 | auto tree = PhTreeMultiMapD<2, int, ConverterMultiply<2, 1, 200>, std::unordered_set>(); 28 | std::vector> vecPos; 29 | int dim = 1000; 30 | 31 | int num = 30000; 32 | for (int i = 0; i < num; ++i) { 33 | PhPointD<2> p = {(double)(rand() % dim), (double)(rand() % dim)}; 34 | vecPos.push_back(p); 35 | tree.emplace(p, i); 36 | } 37 | 38 | long T = 0; 39 | int nT = 0; 40 | while (true) { 41 | auto t1 = std::chrono::high_resolution_clock::now(); 42 | for (int i = 0; i < num; ++i) { 43 | PhPointD<2>& p = vecPos[i]; 44 | PhPointD<2> newp = {p[0] + 1, p[1] + 1}; 45 | tree.relocate(p, newp, i, false); 46 | p = newp; 47 | } 48 | auto t2 = std::chrono::high_resolution_clock::now(); 49 | auto s = std::chrono::duration_cast(t2 - t1); 50 | ++nT; 51 | T += (long)s.count() / 1000; 52 | std::cout << s.count() << " " << (T / nT) 53 | << " msec/num= " << (s.count() / (double)num) << std::endl; 54 | } 55 | 56 | return 0; 57 | } 58 | 59 | int main() { 60 | std::cout << "PH-Tree example with 3D `double` coordinates." << std::endl; 61 | PhPointD<3> p1({1, 1, 1}); 62 | PhPointD<3> p2({2, 2, 2}); 63 | PhPointD<3> p3({3, 3, 3}); 64 | PhPointD<3> p4({4, 4, 4}); 65 | 66 | PhTreeD<3, int> tree; 67 | tree.emplace(p1, 1); 68 | tree.emplace(p2, 2); 69 | tree.emplace(p3, 3); 70 | tree.emplace(p4, 4); 71 | 72 | std::cout << "All values:" << std::endl; 73 | for (auto it : tree) { 74 | std::cout << " id=" << it << std::endl; 75 | } 76 | std::cout << std::endl; 77 | 78 | std::cout << "All points in range:" << p2 << "/" << p4 << std::endl; 79 | for (auto it = tree.begin_query({p2, p4}); it != tree.end(); ++it) { 80 | std::cout << " " << it.second() << " -> " << it.first() << std::endl; 81 | } 82 | std::cout << std::endl; 83 | 84 | std::cout << "PH-Tree is a MAP which means that, like std::map, every position " << std::endl; 85 | std::cout << " (=key) can have only ONE value." << std::endl; 86 | std::cout << "Storing multiple values for a single coordinate requires storing " << std::endl; 87 | std::cout << "lists or sets, for example using PhTree<3, std::vector>." << std::endl; 88 | 89 | PhPointD<3> p4b({4, 4, 4}); 90 | tree.emplace(p4b, 5); 91 | // Still showing '4' after emplace() 92 | std::cout << "ID at " << p4b << ": " << tree.find(p4b).second() << std::endl; 93 | 94 | std::cout << "Done." << std::endl; 95 | 96 | //relocate_example(); 97 | 98 | return 0; 99 | } 100 | -------------------------------------------------------------------------------- /fuzzer/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:private"]) 2 | 3 | #cc_binary( 4 | # name = "b_plus_multimap_fuzzer", 5 | # srcs = [ 6 | # "b_plus_multimap_fuzzer.cpp", 7 | # ], 8 | # linkstatic = True, 9 | # deps = [ 10 | # "//:phtree", 11 | # ], 12 | #) 13 | -------------------------------------------------------------------------------- /fuzzer/README.md: -------------------------------------------------------------------------------- 1 | # Fuzzing 2 | 3 | 4 | Requirements: 5 | * `clang`. 6 | * libFuzzer: https://github.com/google/fuzzing/blob/master/tutorial/libFuzzerTutorial.md 7 | 8 | Compile one of: 9 | * `clang++ -g -std=c++17 -fsanitize=fuzzer fuzzer/b_plus_multimap_fuzzer.cc -I.` 10 | * `clang++ -g -std=c++17 -fsanitize=fuzzer fuzzer/b_plus_map_fuzzer.cc -I.` 11 | * `clang++ -g -std=c++17 -fsanitize=fuzzer fuzzer/b_plus_hash_map_fuzzer.cc -I.` 12 | 13 | Execute: 14 | * `./a.out` 15 | * `./a.out -minimize_crash=1 -runs=10000 /tmp/tmp.b521097a4f49` 16 | * `./a.out /tmp/tmp.12345678/artifacts/minimized-from-185ecf42f208c2a7736a98ba0403f31868bcb681` 17 | 18 | To give an artifact path: 19 | * `-artifact_prefix=/home/my-name/tmp/fuzz/artifacts/` 20 | 21 | ## Bazel 22 | 23 | Fuzzing with bazel is possible but is currently disabled because it breaks `bazel build ...`. 24 | 25 | * We would need to set `clang`/`clang++` as compiler (`gcc` would not work anymore) 26 | * We would need to solve the problem that `bazel build ...` fails unless `-fsanitize=fuzzer` is set 27 | 28 | ### Using a simple executable 29 | Uncomment build rules in BUILD file, then: 30 | `CC=clang bazel run //fuzzer:b_plus_multimap_fuzzer --config=fuzz` 31 | 32 | ### Using the bazel cc_fuzz_test 33 | https://github.com/bazelbuild/rules_fuzzing/blob/master/docs/guide.md 34 | -------------------------------------------------------------------------------- /fuzzer/b_plus_hash_map_fuzzer.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Tilmann Zäschke 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include 24 | 25 | #include "include/phtree/common/b_plus_tree_hash_map.h" 26 | 27 | static volatile int Sink; 28 | 29 | using Instruction = std::uint8_t; 30 | using Key = std::uint8_t; 31 | using Value = std::uint8_t; 32 | 33 | constexpr bool PRINT = !true; 34 | 35 | void print() {} 36 | 37 | extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size) { 38 | assert(Data); 39 | 40 | if (PRINT) { 41 | std::cout << "TEST(PhTreeBptMapTest, FuzzTest1) {" << std::endl; 42 | std::cout << " using Key = std::uint8_t;" << std::endl; 43 | std::cout << " using Value = std::uint8_t;" << std::endl; 44 | std::cout << " b_plus_tree_map tree{};" << std::endl; 45 | } 46 | 47 | auto scopeguard = []() { std::cout << "};" << std::endl; }; 48 | 49 | improbable::phtree::b_plus_tree_hash_map tree; 50 | std::map map; 51 | 52 | size_t pos = 0; 53 | 54 | while (pos + 4 < Size) { 55 | Instruction inst = Data[pos++] % 4; 56 | Key key = Data[pos++]; 57 | Value value = Data[pos++]; 58 | switch (inst) { 59 | case 0: { 60 | if (PRINT) 61 | std::cout << " tree.emplace(" << (int)key << ", " << (int)value << ");" 62 | << std::endl; 63 | tree.emplace(key, value); 64 | map.emplace(key, value); 65 | break; 66 | } 67 | case 1: { 68 | if (PRINT) 69 | std::cout << " tree.erase(" << (int)key << ");" << std::endl; 70 | tree.erase(key); 71 | map.erase(key); 72 | break; 73 | } 74 | case 2: { 75 | if (PRINT) 76 | std::cout << " auto it = tree.find(" << (int)key << ");" << std::endl; 77 | auto it = tree.find(key); 78 | if (it != tree.end()) { 79 | if (PRINT) 80 | std::cout << " tree.erase(it);" << std::endl; 81 | tree.erase(it); 82 | } 83 | auto it2 = map.find(key); 84 | if (it2 != map.end()) { 85 | map.erase(it2); 86 | } 87 | break; 88 | } 89 | case 3: { 90 | if (PRINT) 91 | std::cout << " auto it = tree.lower_bound(" << (int)key << ");" << std::endl; 92 | auto it = tree.lower_bound(key); 93 | if (PRINT) 94 | std::cout << " tree.emplace_hint(it, " << (int)key << ", " << (int)value << ");" 95 | << std::endl; 96 | tree.emplace_hint(it, key, value); 97 | auto it2 = map.lower_bound(key); 98 | map.emplace_hint(it2, key, value); 99 | break; 100 | } 101 | default: 102 | std::cout << "Unexpected instruction: " << inst << std::endl; 103 | } 104 | } 105 | 106 | tree._check(); 107 | 108 | for (auto& entry : map) { 109 | const Key& vRef = entry.first; 110 | Key vMap = tree.find(vRef)->first; 111 | assert(vMap == vRef); 112 | } 113 | for (auto& entry : tree) { 114 | Key v = entry.first; 115 | const Key& vRef = map.find(v)->first; 116 | Key vMap = tree.find(v)->first; 117 | assert(vMap == vRef); 118 | } 119 | assert(tree.size() == map.size()); 120 | 121 | return 0; 122 | } 123 | -------------------------------------------------------------------------------- /fuzzer/b_plus_map_fuzzer.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Tilmann Zäschke 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include 24 | 25 | #include "include/phtree/common/b_plus_tree_map.h" 26 | 27 | static volatile int Sink; 28 | 29 | using Instruction = std::uint8_t; 30 | using Key = std::uint8_t; 31 | using Value = std::uint8_t; 32 | 33 | constexpr bool PRINT = !true; 34 | 35 | void print() {} 36 | 37 | extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size) { 38 | assert(Data); 39 | 40 | if (PRINT) { 41 | std::cout << "TEST(PhTreeBptMapTest, FuzzTest1) {" << std::endl; 42 | std::cout << " using Key = std::uint8_t;" << std::endl; 43 | std::cout << " using Value = std::uint8_t;" << std::endl; 44 | std::cout << " b_plus_tree_map tree{};" << std::endl; 45 | } 46 | 47 | auto scopeguard = []() { std::cout << "};" << std::endl; }; 48 | 49 | improbable::phtree::b_plus_tree_map tree; 50 | std::map map; 51 | 52 | size_t pos = 0; 53 | 54 | while (pos + 4 < Size) { 55 | Instruction inst = Data[pos++] % 4; 56 | Key key = Data[pos++]; 57 | Value value = Data[pos++]; 58 | switch (inst) { 59 | case 0: { 60 | if (PRINT) 61 | std::cout << " tree.emplace(" << (int)key << ", " << (int)value << ");" 62 | << std::endl; 63 | tree.emplace(key, value); 64 | map.emplace(key, value); 65 | break; 66 | } 67 | case 1: { 68 | if (PRINT) 69 | std::cout << " tree.erase(" << (int)key << ");" << std::endl; 70 | tree.erase(key); 71 | map.erase(key); 72 | break; 73 | } 74 | case 2: { 75 | if (PRINT) 76 | std::cout << " auto it = tree.find(" << (int)key << ");" << std::endl; 77 | auto it = tree.find(key); 78 | if (it != tree.end()) { 79 | if (PRINT) 80 | std::cout << " tree.erase(it);" << std::endl; 81 | tree.erase(it); 82 | } 83 | auto it2 = map.find(key); 84 | if (it2 != map.end()) { 85 | map.erase(it2); 86 | } 87 | break; 88 | } 89 | case 3: { 90 | if (PRINT) 91 | std::cout << " auto it = tree.lower_bound(" << (int)key << ");" << std::endl; 92 | auto it = tree.lower_bound(key); 93 | if (PRINT) 94 | std::cout << " tree.emplace_hint(it, " << (int)key << ", " << (int)value << ");" 95 | << std::endl; 96 | tree.emplace_hint(it, key, value); 97 | auto it2 = map.lower_bound(key); 98 | map.emplace_hint(it2, key, value); 99 | break; 100 | } 101 | default: 102 | std::cout << "Unexpected instruction: " << inst << std::endl; 103 | } 104 | } 105 | 106 | tree._check(); 107 | 108 | for (auto& entry : map) { 109 | const Key& vRef = entry.first; 110 | Key vMap = tree.find(vRef)->first; 111 | assert(vMap == vRef); 112 | } 113 | for (auto& entry : tree) { 114 | Key v = entry.first; 115 | const Key& vRef = map.find(v)->first; 116 | Key vMap = tree.find(v)->first; 117 | assert(vMap == vRef); 118 | } 119 | assert(tree.size() == map.size()); 120 | 121 | return 0; 122 | } 123 | -------------------------------------------------------------------------------- /fuzzer/b_plus_multimap_fuzzer.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Tilmann Zäschke 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include 24 | 25 | #include "include/phtree/common/b_plus_tree_multimap.h" 26 | 27 | static volatile int Sink; 28 | 29 | using Instruction = std::uint8_t; 30 | using Key = std::uint8_t; 31 | using Value = std::uint8_t; 32 | 33 | constexpr bool PRINT = !true; 34 | 35 | void print() {} 36 | 37 | extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size) { 38 | assert(Data); 39 | 40 | if (PRINT) { 41 | std::cout << "TEST(PhTreeBptMulitmapTest, FuzzTest1) {" << std::endl; 42 | std::cout << " using Key = std::uint8_t;" << std::endl; 43 | std::cout << " using Value = std::uint8_t;" << std::endl; 44 | std::cout << " b_plus_tree_multimap tree{};" << std::endl; 45 | } 46 | 47 | auto scopeguard = []() { std::cout << "};" << std::endl; }; 48 | 49 | improbable::phtree::b_plus_tree_multimap tree; 50 | std::multimap map; 51 | 52 | size_t pos = 0; 53 | 54 | while (pos + 4 < Size) { 55 | Instruction inst = Data[pos++] % 4; 56 | Key key = Data[pos++]; 57 | Value value = Data[pos++]; 58 | switch (inst) { 59 | case 0: { 60 | if (PRINT) 61 | std::cout << " tree.emplace(" << (int)key << ", " << (int)value << ");" 62 | << std::endl; 63 | tree.emplace(key, value); 64 | map.emplace(key, value); 65 | break; 66 | } 67 | case 1: { 68 | if (PRINT) 69 | std::cout << " tree.erase(" << (int)key << ");" << std::endl; 70 | tree.erase(key); 71 | map.erase(key); 72 | break; 73 | } 74 | case 2: { 75 | if (PRINT) 76 | std::cout << " auto it = tree.find(" << (int)key << ");" << std::endl; 77 | auto it = tree.find(key); 78 | if (it != tree.end()) { 79 | if (PRINT) 80 | std::cout << " tree.erase(it);" << std::endl; 81 | tree.erase(it); 82 | } 83 | auto it2 = map.find(key); 84 | if (it2 != map.end()) { 85 | map.erase(it2); 86 | } 87 | break; 88 | } 89 | case 3: { 90 | if (PRINT) 91 | std::cout << " auto it = tree.lower_bound(" << (int)key << ");" << std::endl; 92 | auto it = tree.lower_bound(key); 93 | if (PRINT) 94 | std::cout << " tree.emplace_hint(it, " << (int)key << ", " << (int)value << ");" 95 | << std::endl; 96 | tree.emplace_hint(it, key, value); 97 | auto it2 = map.lower_bound(key); 98 | map.emplace_hint(it2, key, value); 99 | break; 100 | } 101 | default: 102 | std::cout << "Unexpected instruction: " << inst << std::endl; 103 | } 104 | } 105 | 106 | tree._check(); 107 | 108 | for (auto& entry : map) { 109 | const Key& vRef = entry.first; 110 | Key vMap = tree.find(vRef)->first; 111 | assert(vMap == vRef); 112 | } 113 | for (auto& entry : tree) { 114 | Key v = entry.first; 115 | const Key& vRef = map.find(v)->first; 116 | Key vMap = tree.find(v)->first; 117 | assert(vMap == vRef); 118 | } 119 | assert(tree.size() == map.size()); 120 | 121 | return 0; 122 | } 123 | -------------------------------------------------------------------------------- /fuzzer/phtree_mm_relocate_fuzzer.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Tilmann Zäschke 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include 24 | 25 | #include "include/phtree/phtree_multimap.h" 26 | 27 | // clang++ -g -std=c++17 -fsanitize=address,fuzzer fuzzer/phtree_mm_relocate_fuzzer.cc -I. -I./include 28 | 29 | 30 | using namespace improbable::phtree; 31 | 32 | 33 | constexpr bool PRINT = !true; 34 | 35 | void print() {} 36 | 37 | extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size) { 38 | assert(Data); 39 | 40 | const dimension_t DIM = 1; 41 | 42 | if (PRINT) { 43 | std::cout << "TEST(PhTreeMMTest, FuzzTest1) {" << std::endl; 44 | std::cout << " const dimension_t DIM = 1;" << std::endl; 45 | std::cout << " using Key = PhPoint;" << std::endl; 46 | std::cout << " using Value = std::uint8_t;" << std::endl; 47 | std::cout << " PhTreeMultiMap> tree{};" << std::endl; 48 | } 49 | 50 | using Instruction = std::uint8_t; 51 | using Key = PhPoint<1>; 52 | using Value = std::uint8_t; 53 | 54 | PhTreeMultiMap> tree; 55 | std::multimap map; 56 | 57 | size_t pos = 0; 58 | 59 | while (pos + 4 < Size) { 60 | Instruction inst = Data[pos++] % 2; 61 | Key key{Data[pos++]}; 62 | Key key2{Data[pos++]}; 63 | Value value = Data[pos++]; 64 | switch (inst) { 65 | case 0: { 66 | if (PRINT) 67 | std::cout << " tree.emplace({" << key[0] << "}, " << (int)value << ");" 68 | << std::endl; 69 | tree.emplace({key[0]}, value); 70 | // map.emplace(key, value); 71 | break; 72 | } 73 | case 1: { 74 | if (PRINT) 75 | std::cout << " tree.relocate({" << key[0] << "}, {" << key2[0] << "}, " << (int)value << ");" << std::endl; 76 | // tree.erase(key); 77 | // map.erase(key); 78 | tree.relocate({key[0]}, {key2[0]}, value); 79 | break; 80 | } 81 | // case 2: { 82 | // if (PRINT) 83 | // std::cout << " auto it = tree.find(" << (int)key << ");" << std::endl; 84 | // auto it = tree.find(key); 85 | // if (it != tree.end()) { 86 | // if (PRINT) 87 | // std::cout << " tree.erase(it);" << std::endl; 88 | // tree.erase(it); 89 | // } 90 | // auto it2 = map.find(key); 91 | // if (it2 != map.end()) { 92 | // map.erase(it2); 93 | // } 94 | // break; 95 | // } 96 | // case 3: { 97 | // if (PRINT) 98 | // std::cout << " auto it = tree.lower_bound(" << (int)key << ");" << std::endl; 99 | // auto it = tree.lower_bound(key); 100 | // if (PRINT) 101 | // std::cout << " tree.emplace_hint(it, " << (int)key << ", " << (int)value << ");" 102 | // << std::endl; 103 | // tree.emplace_hint(it, key, value); 104 | // auto it2 = map.lower_bound(key); 105 | // map.emplace_hint(it2, key, value); 106 | // break; 107 | // } 108 | default: 109 | std::cout << "Unexpected instruction: " << inst << std::endl; 110 | } 111 | } 112 | 113 | //tree._check(); 114 | 115 | // for (auto& entry : map) { 116 | // const Key& vRef = entry.first; 117 | // Key vMap = tree.find(vRef)->first; 118 | // assert(vMap == vRef); 119 | // } 120 | // for (auto& entry : tree) { 121 | // Key v = entry.first; 122 | // const Key& vRef = map.find(v)->first; 123 | // Key vMap = tree.find(v)->first; 124 | // assert(vMap == vRef); 125 | // } 126 | // assert(tree.size() == map.size()); 127 | 128 | return 0; 129 | } 130 | -------------------------------------------------------------------------------- /include/phtree/common/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:private"]) 2 | 3 | cc_library( 4 | name = "common", 5 | hdrs = [ 6 | "b_plus_tree_base.h", 7 | "b_plus_tree_hash_map.h", 8 | "b_plus_tree_map.h", 9 | "b_plus_tree_multimap.h", 10 | "base_types.h", 11 | "bits.h", 12 | "bpt_fixed_vector.h", 13 | "bpt_priority_queue.h", 14 | "common.h", 15 | "debug_helper.h", 16 | "flat_array_map.h", 17 | "flat_sparse_map.h", 18 | "tree_stats.h", 19 | ], 20 | visibility = [ 21 | "//visibility:public", 22 | ], 23 | deps = [ 24 | ], 25 | ) 26 | -------------------------------------------------------------------------------- /include/phtree/common/README.md: -------------------------------------------------------------------------------- 1 | ### Utilities used by all PH-Tree implementations 2 | 3 | -------------------------------------------------------------------------------- /include/phtree/common/bits.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Improbable Worlds Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef PHTREE_COMMON_BITS_H 18 | #define PHTREE_COMMON_BITS_H 19 | 20 | #include "base_types.h" 21 | #include 22 | 23 | #if defined(_MSC_VER) 24 | // https://docs.microsoft.com/en-us/cpp/intrinsics/x64-amd64-intrinsics-list?view=vs-2019 25 | #include 26 | #endif 27 | 28 | /* 29 | * PLEASE do not include this file directly, it is included via common.h. 30 | * 31 | * This file defines how certain bit level operations are implemented, such as: 32 | * - count leading zeroes 33 | * - count trailing zeros 34 | */ 35 | namespace improbable::phtree::detail { 36 | 37 | #if defined(__clang__) || defined(__GNUC__) 38 | // See https://en.cppreference.com/w/cpp/language/types 39 | inline bit_width_t CountLeadingZeros64(std::uint64_t bit_string) { 40 | return bit_string == 0 ? 64U : __builtin_clzll(bit_string); 41 | } 42 | 43 | inline bit_width_t CountLeadingZeros(std::uint32_t bit_string) { 44 | return bit_string == 0 ? 32U : __builtin_clz(bit_string); 45 | } 46 | 47 | inline bit_width_t CountTrailingZeros64(std::uint64_t bit_string) { 48 | return bit_string == 0 ? 64U : __builtin_ctzll(bit_string); 49 | } 50 | 51 | inline bit_width_t CountTrailingZeros(std::uint32_t bit_string) { 52 | return bit_string == 0 ? 32U : __builtin_ctz(bit_string); 53 | } 54 | 55 | #elif defined(_MSC_VER) 56 | // https://docs.microsoft.com/en-us/cpp/intrinsics/x64-amd64-intrinsics-list?view=vs-2019 57 | inline bit_width_t CountLeadingZeros64(std::uint64_t bit_string) { 58 | unsigned long leading_zero = 0; 59 | return _BitScanReverse64(&leading_zero, bit_string) ? 63 - leading_zero : 64U; 60 | } 61 | 62 | inline bit_width_t CountLeadingZeros(std::uint32_t bit_string) { 63 | unsigned long leading_zero = 0; 64 | return _BitScanReverse(&leading_zero, bit_string) ? 31 - leading_zero : 32U; 65 | } 66 | 67 | inline bit_width_t CountTrailingZeros64(std::uint64_t bit_string) { 68 | unsigned long trailing_zero = 0; 69 | return _BitScanForward64(&trailing_zero, bit_string) ? trailing_zero : 64U; 70 | } 71 | 72 | inline bit_width_t CountTrailingZeros(std::uint32_t bit_string) { 73 | unsigned long trailing_zero = 0; 74 | return _BitScanForward(&trailing_zero, bit_string) ? trailing_zero : 32U; 75 | } 76 | #else 77 | inline bit_width_t CountLeadingZeros(std::uint64_t bit_string) { 78 | if (bit_string == 0) { 79 | return 64; 80 | } 81 | bit_width_t n = 1; 82 | std::uint32_t x = (bit_string >> 32); 83 | if (x == 0) { 84 | n += 32; 85 | x = (int)bit_string; 86 | } 87 | if (x >> 16 == 0) { 88 | n += 16; 89 | x <<= 16; 90 | } 91 | if (x >> 24 == 0) { 92 | n += 8; 93 | x <<= 8; 94 | } 95 | if (x >> 28 == 0) { 96 | n += 4; 97 | x <<= 4; 98 | } 99 | if (x >> 30 == 0) { 100 | n += 2; 101 | x <<= 2; 102 | } 103 | n -= x >> 31; 104 | return n; 105 | } 106 | 107 | inline bit_width_t CountLeadingZeros(std::uint32_t bit_string) { 108 | if (bit_string == 0) { 109 | return 32; 110 | } 111 | bit_width_t n = 1; 112 | if (bit_string >> 16 == 0) { 113 | n += 16; 114 | bit_string <<= 16; 115 | } 116 | if (bit_string >> 24 == 0) { 117 | n += 8; 118 | bit_string <<= 8; 119 | } 120 | if (bit_string >> 28 == 0) { 121 | n += 4; 122 | bit_string <<= 4; 123 | } 124 | if (bit_string >> 30 == 0) { 125 | n += 2; 126 | bit_string <<= 2; 127 | } 128 | n -= bit_string >> 31; 129 | return n; 130 | } 131 | 132 | inline bit_width_t CountTrailingZeros(std::uint64_t bit_string) { 133 | if (bit_string == 0) { 134 | return 64; 135 | } 136 | uint32_t x = 0; 137 | uint32_t y = 0; 138 | uint16_t n = 63; 139 | y = (std::uint32_t)bit_string; 140 | if (y != 0) { 141 | n = n - 32; 142 | x = y; 143 | } else { 144 | x = (std::uint32_t)(bit_string >> 32); 145 | } 146 | y = x << 16; 147 | if (y != 0) { 148 | n = n - 16; 149 | x = y; 150 | } 151 | y = x << 8; 152 | if (y != 0) { 153 | n = n - 8; 154 | x = y; 155 | } 156 | y = x << 4; 157 | if (y != 0) { 158 | n = n - 4; 159 | x = y; 160 | } 161 | y = x << 2; 162 | if (y != 0) { 163 | n = n - 2; 164 | x = y; 165 | } 166 | return n - ((x << 1) >> 31); 167 | } 168 | #endif 169 | 170 | } // namespace improbable::phtree::detail 171 | 172 | #endif // PHTREE_COMMON_BITS_H 173 | -------------------------------------------------------------------------------- /include/phtree/common/debug_helper.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Improbable Worlds Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef PHTREE_COMMON_DEBUG_HELPER_H 18 | #define PHTREE_COMMON_DEBUG_HELPER_H 19 | 20 | #include "tree_stats.h" 21 | 22 | namespace improbable::phtree { 23 | 24 | class PhTreeDebugHelper { 25 | public: 26 | enum class PrintDetail { name, entries, tree }; 27 | 28 | class DebugHelper { 29 | virtual void CheckConsistency() const = 0; 30 | 31 | [[nodiscard]] virtual PhTreeStats GetStats() const = 0; 32 | 33 | [[nodiscard]] virtual std::string ToString(const PrintDetail& detail) const = 0; 34 | }; 35 | 36 | /* 37 | * Checks the consistency of the tree. This function requires assertions to be enabled. 38 | */ 39 | template 40 | static void CheckConsistency(const TREE& tree) { 41 | tree.GetInternalTree().GetDebugHelper().CheckConsistency(); 42 | tree.CheckConsistencyExternal(); 43 | } 44 | 45 | /* 46 | * Collects some statistics about the tree, such as number of nodes, average depth, ... 47 | * 48 | * @return some statistics about the tree. 49 | */ 50 | template 51 | static PhTreeStats GetStats(const TREE& tree) { 52 | return tree.GetInternalTree().GetDebugHelper().GetStats(); 53 | } 54 | 55 | /* 56 | * Depending on the detail parameter this returns: 57 | * - "name" : a string that identifies the tree implementation type. 58 | * - "entries" : a string that lists all elements in the tree. 59 | * - "tree" : a string that lists all elements in the tree, pretty formatted to indicate tree 60 | * structure. 61 | * 62 | * @return a string as described above. 63 | */ 64 | template 65 | static std::string ToString(const TREE& tree, const PrintDetail& detail) { 66 | return tree.GetInternalTree().GetDebugHelper().ToString(detail); 67 | } 68 | }; 69 | 70 | } // namespace improbable::phtree 71 | #endif // PHTREE_COMMON_DEBUG_HELPER_H 72 | -------------------------------------------------------------------------------- /include/phtree/common/flat_sparse_map.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Improbable Worlds Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef PHTREE_COMMON_FLAT_SPARSE_MAP_H 18 | #define PHTREE_COMMON_FLAT_SPARSE_MAP_H 19 | 20 | #include "bits.h" 21 | #include 22 | #include 23 | #include 24 | 25 | /* 26 | * PLEASE do not include this file directly, it is included via common.h. 27 | * 28 | * This file contains the sparse_map implementation, which is used in medium-dimensional nodes in 29 | * the PH-Tree. 30 | */ 31 | namespace improbable::phtree::detail { 32 | 33 | /* 34 | * The sparse_map is a flat map implementation that uses an array of *at* *most* SIZE=2^DIM. 35 | * The array contains a list sorted by key. 36 | * 37 | * It has O(log n) lookup and O(n) insertion/removal time complexity, space complexity is O(n). 38 | */ 39 | template 40 | class sparse_map { 41 | using Entry = std::pair; 42 | using iterator = typename std::vector::iterator; 43 | 44 | public: 45 | explicit sparse_map() : data_{} { 46 | data_.reserve(4); 47 | } 48 | 49 | [[nodiscard]] auto find(KeyT key) { 50 | auto it = lower_bound(key); 51 | if (it != data_.end() && it->first == key) { 52 | return it; 53 | } 54 | return data_.end(); 55 | } 56 | 57 | [[nodiscard]] auto find(KeyT key) const { 58 | auto it = lower_bound(key); 59 | if (it != data_.end() && it->first == key) { 60 | return it; 61 | } 62 | return data_.end(); 63 | } 64 | 65 | [[nodiscard]] auto lower_bound(KeyT key) { 66 | return std::lower_bound(data_.begin(), data_.end(), key, [](Entry& left, const KeyT key) { 67 | return left.first < key; 68 | }); 69 | } 70 | 71 | [[nodiscard]] auto lower_bound(KeyT key) const { 72 | return std::lower_bound( 73 | data_.cbegin(), data_.cend(), key, [](const Entry& left, const KeyT key) { 74 | return left.first < key; 75 | }); 76 | } 77 | 78 | [[nodiscard]] auto begin() noexcept { 79 | return data_.begin(); 80 | } 81 | 82 | [[nodiscard]] auto begin() const noexcept { 83 | return cbegin(); 84 | } 85 | 86 | [[nodiscard]] auto cbegin() const noexcept { 87 | return data_.cbegin(); 88 | } 89 | 90 | [[nodiscard]] auto end() noexcept { 91 | return data_.end(); 92 | } 93 | 94 | [[nodiscard]] auto end() const noexcept { 95 | return data_.end(); 96 | } 97 | 98 | [[nodiscard]] auto cend() const noexcept { 99 | return data_.cend(); 100 | } 101 | 102 | template 103 | auto emplace(KeyT key, Args&&... args) { 104 | auto iter = lower_bound(key); 105 | return try_emplace_base(iter, key, std::forward(args)...); 106 | } 107 | 108 | template 109 | auto try_emplace(KeyT key, Args&&... args) { 110 | auto iter = lower_bound(key); 111 | return try_emplace_base(iter, key, std::forward(args)...); 112 | } 113 | 114 | template 115 | auto try_emplace(iterator iter, KeyT key, Args&&... args) { 116 | return try_emplace_base(iter, key, std::forward(args)...).first; 117 | } 118 | 119 | void erase(KeyT key) { 120 | auto it = lower_bound(key); 121 | if (it != end() && it->first == key) { 122 | data_.erase(it); 123 | } 124 | } 125 | 126 | void erase(const iterator& iter) { 127 | data_.erase(iter); 128 | } 129 | 130 | [[nodiscard]] size_t size() const { 131 | return data_.size(); 132 | } 133 | 134 | private: 135 | template 136 | auto try_emplace_base(const iterator& it, KeyT key, Args&&... args) { 137 | if (it != end() && it->first == key) { 138 | return std::make_pair(it, false); 139 | } else { 140 | auto x = data_.emplace( 141 | it, 142 | std::piecewise_construct, 143 | std::forward_as_tuple(key), 144 | std::forward_as_tuple(std::forward(args)...)); 145 | return std::make_pair(x, true); 146 | } 147 | } 148 | 149 | std::vector data_; 150 | }; 151 | 152 | } // namespace improbable::phtree 153 | 154 | #endif // PHTREE_COMMON_FLAT_SPARSE_MAP_H 155 | -------------------------------------------------------------------------------- /include/phtree/common/tree_stats.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Improbable Worlds Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef PHTREE_COMMON_TREE_STATS_H 18 | #define PHTREE_COMMON_TREE_STATS_H 19 | 20 | #include "base_types.h" 21 | #include 22 | #include 23 | 24 | /* 25 | * PLEASE do not include this file directly, it is included via common.h. 26 | * 27 | * This file defines the type returned by the getStats() method of the PH-Tree. 28 | * They provide various statistics on the PH-Tree instance that returns them. 29 | */ 30 | namespace improbable::phtree { 31 | 32 | class PhTreeStats { 33 | using SCALAR = scalar_64_t; 34 | 35 | public: 36 | std::string ToString() { 37 | std::ostringstream s; 38 | s << " nNodes = " << std::to_string(n_nodes_) << std::endl; 39 | s << " avgNodeDepth = " << ((double)q_total_depth_ / (double)n_nodes_) << std::endl; 40 | s << " AHC=" << n_AHC_ << " NI=" << n_nt_ << " nNtNodes_=" << n_nt_nodes_ << std::endl; 41 | double apl = GetAvgPostlen(); 42 | s << " avgPostLen = " << apl << " (" << (detail::MAX_BIT_WIDTH - apl) << ")" 43 | << std::endl; 44 | return s.str(); 45 | } 46 | 47 | std::string ToStringHist() { 48 | std::ostringstream s; 49 | s << " infix_len = "; 50 | to_string(s, infix_hist_) << std::endl; 51 | s << " nodeSizeLog = "; 52 | to_string(s, node_size_log_hist_) << std::endl; 53 | s << " node_depth_hist_ = "; 54 | to_string(s, node_depth_hist_) << std::endl; 55 | s << " depthHist = "; 56 | to_string(s, q_n_post_fix_n_) << std::endl; 57 | return s.str(); 58 | } 59 | 60 | /* 61 | * @return average postfix_len, including the HC/LHC bit. 62 | */ 63 | double GetAvgPostlen() { 64 | size_t total = 0; 65 | size_t num_entry = 0; 66 | for (detail::bit_width_t i = 0; i < detail::MAX_BIT_WIDTH; ++i) { 67 | total += (detail::MAX_BIT_WIDTH - i) * q_n_post_fix_n_[i]; 68 | num_entry += q_n_post_fix_n_[i]; 69 | } 70 | return (double)total / (double)num_entry; 71 | } 72 | 73 | size_t GetNodeCount() { 74 | return n_nodes_; 75 | } 76 | 77 | size_t GetCalculatedMemSize() { 78 | return size_; 79 | } 80 | 81 | private: 82 | static std::ostringstream& to_string(std::ostringstream& s, std::vector& data) { 83 | s << "["; 84 | for (size_t x : data) { 85 | s << x << ","; 86 | } 87 | s << "]"; 88 | return s; 89 | } 90 | 91 | public: 92 | size_t n_nodes_ = 0; 93 | size_t n_AHC_ = 0; // AHC nodes (formerly Nodes with AHC-postfix representation) 94 | size_t n_nt_nodes_ = 0; // NtNodes (formerly Nodes with sub-HC representation) 95 | size_t n_nt_ = 0; // nodes with NT representation 96 | size_t n_total_children_ = 0; 97 | size_t size_ = 0; // calculated size in bytes 98 | size_t q_total_depth_ = 0; 99 | // filled with x[current_depth] = nPost; 100 | std::vector q_n_post_fix_n_ = std::vector(detail::MAX_BIT_WIDTH, (size_t)0); 101 | // prefix len 102 | std::vector infix_hist_ = std::vector(detail::MAX_BIT_WIDTH, (size_t)0); 103 | // prefix len 104 | std::vector node_depth_hist_ = std::vector(detail::MAX_BIT_WIDTH, (size_t)0); 105 | // log (num_entries) 106 | std::vector node_size_log_hist_ = std::vector(32, (size_t)0); 107 | }; 108 | 109 | } // namespace improbable::phtree 110 | #endif // PHTREE_COMMON_TREE_STATS_H 111 | -------------------------------------------------------------------------------- /include/phtree/distance.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Improbable Worlds Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef PHTREE_COMMON_DISTANCES_H 18 | #define PHTREE_COMMON_DISTANCES_H 19 | 20 | #include "common/common.h" 21 | #include "converter.h" 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | namespace improbable::phtree { 29 | 30 | /* 31 | * The PH-Tree supports different distance functions. These can be used 32 | * by the kNN (k nearest neighbor) query facility. 33 | * 34 | * The implementations in this file are: 35 | * - DistanceEuclidean: Euclidean distance for PhPoint & PhPointD 36 | * - DistanceL1: L1 distance (Manhattan distance / taxi distance) for PhPoint & PhPointD 37 | */ 38 | 39 | template 40 | struct DistanceEuclidean { 41 | double operator()(const PhPoint& v1, const PhPoint& v2) const noexcept { 42 | double sum2 = 0; 43 | for (dimension_t i = 0; i < DIM; ++i) { 44 | assert( 45 | (v1[i] >= 0) != (v2[i] >= 0) || 46 | double(v1[i]) - double(v2[i]) < 47 | double(std::numeric_limits::max())); 48 | double d2 = double(v1[i] - v2[i]); 49 | sum2 += d2 * d2; 50 | } 51 | return sqrt(sum2); 52 | }; 53 | 54 | double operator()(const PhPointD& p1, const PhPointD& p2) const noexcept { 55 | double sum2 = 0; 56 | for (dimension_t i = 0; i < DIM; ++i) { 57 | double d2 = p1[i] - p2[i]; 58 | sum2 += d2 * d2; 59 | } 60 | return sqrt(sum2); 61 | }; 62 | 63 | double operator()(const PhPointF& v1, const PhPointF& v2) const noexcept { 64 | double sum2 = 0; 65 | for (dimension_t i = 0; i < DIM; i++) { 66 | double d2 = double(v1[i] - v2[i]); 67 | sum2 += d2 * d2; 68 | } 69 | return sqrt(sum2); 70 | }; 71 | }; 72 | 73 | template 74 | struct DistanceL1 { 75 | double operator()(const PhPoint& v1, const PhPoint& v2) const noexcept { 76 | double sum = 0; 77 | for (dimension_t i = 0; i < DIM; ++i) { 78 | assert( 79 | (v1[i] >= 0) != (v2[i] >= 0) || 80 | double(v1[i]) - double(v2[i]) < 81 | double(std::numeric_limits::max())); 82 | sum += std::abs(double(v1[i] - v2[i])); 83 | } 84 | return sum; 85 | }; 86 | 87 | double operator()(const PhPointD& v1, const PhPointD& v2) const noexcept { 88 | double sum = 0; 89 | for (dimension_t i = 0; i < DIM; ++i) { 90 | sum += std::abs(v1[i] - v2[i]); 91 | } 92 | return sum; 93 | }; 94 | 95 | float operator()(const PhPointF& v1, const PhPointF& v2) const noexcept { 96 | float sum = 0; 97 | for (dimension_t i = 0; i < DIM; ++i) { 98 | sum += std::abs(v1[i] - v2[i]); 99 | } 100 | return sum; 101 | }; 102 | }; 103 | 104 | template 105 | struct DistanceChebyshev { 106 | double operator()(const PhPoint& v1, const PhPoint& v2) const noexcept { 107 | double sum = 0; 108 | for (dimension_t i = 0; i < DIM; ++i) { 109 | assert( 110 | (v1[i] >= 0) != (v2[i] >= 0) || 111 | double(v1[i]) - double(v2[i]) < 112 | double(std::numeric_limits::max())); 113 | sum = std::max(sum, std::abs(double(v1[i] - v2[i]))); 114 | } 115 | return sum; 116 | }; 117 | 118 | double operator()(const PhPointD& v1, const PhPointD& v2) const noexcept { 119 | double sum = 0; 120 | for (dimension_t i = 0; i < DIM; ++i) { 121 | sum = std::max(sum, std::abs(v1[i] - v2[i])); 122 | } 123 | return sum; 124 | }; 125 | 126 | float operator()(const PhPointF& v1, const PhPointF& v2) const noexcept { 127 | float sum = 0; 128 | for (dimension_t i = 0; i < DIM; ++i) { 129 | sum = std::max(sum, std::abs(v1[i] - v2[i])); 130 | } 131 | return sum; 132 | }; 133 | }; 134 | 135 | } // namespace improbable::phtree 136 | 137 | #endif // PHTREE_COMMON_DISTANCES_H 138 | -------------------------------------------------------------------------------- /include/phtree/v16/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:private"]) 2 | 3 | cc_library( 4 | name = "v16", 5 | srcs = [ 6 | ], 7 | hdrs = [ 8 | "debug_helper_v16.h", 9 | "entry.h", 10 | "for_each.h", 11 | "for_each_hc.h", 12 | "iterator_base.h", 13 | "iterator_full.h", 14 | "iterator_hc.h", 15 | "iterator_knn_hs.h", 16 | "iterator_lower_bound.h", 17 | "iterator_with_parent.h", 18 | "node.h", 19 | "phtree_v16.h", 20 | ], 21 | visibility = [ 22 | "//visibility:public", 23 | ], 24 | deps = [ 25 | "//include/phtree/common", 26 | ], 27 | ) 28 | -------------------------------------------------------------------------------- /include/phtree/v16/for_each.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Improbable Worlds Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef PHTREE_V16_FOR_EACH_H 18 | #define PHTREE_V16_FOR_EACH_H 19 | 20 | #include "phtree/common/common.h" 21 | #include "iterator_with_parent.h" 22 | 23 | namespace improbable::phtree::v16 { 24 | 25 | /* 26 | * Iterates over the whole tree. Entries and child nodes that are rejected by the Filter are not 27 | * traversed or returned. 28 | */ 29 | template 30 | class ForEach { 31 | static constexpr dimension_t DIM = CONVERT::DimInternal; 32 | using KeyInternal = typename CONVERT::KeyInternal; 33 | using SCALAR = typename CONVERT::ScalarInternal; 34 | using EntryT = Entry; 35 | 36 | public: 37 | template 38 | ForEach(const CONVERT* converter, CB&& callback, F&& filter) 39 | : converter_{converter} 40 | , callback_{std::forward(callback)} 41 | , filter_(std::forward(filter)) {} 42 | 43 | void Traverse(const EntryT& entry) { 44 | assert(entry.IsNode()); 45 | auto& entries = entry.GetNode().Entries(); 46 | auto iter = entries.begin(); 47 | auto end = entries.end(); 48 | for (; iter != end; ++iter) { 49 | const auto& child = iter->second; 50 | const auto& child_key = child.GetKey(); 51 | if (child.IsNode()) { 52 | if (filter_.IsNodeValid(child_key, child.GetNodePostfixLen() + 1)) { 53 | Traverse(child); 54 | } 55 | } else { 56 | T& value = child.GetValue(); 57 | if (filter_.IsEntryValid(child_key, value)) { 58 | callback_(converter_->post(child_key), value); 59 | } 60 | } 61 | } 62 | } 63 | 64 | const CONVERT* converter_; 65 | CALLBACK_FN callback_; 66 | FILTER_FN filter_; 67 | }; 68 | } // namespace improbable::phtree::v16 69 | 70 | #endif // PHTREE_V16_FOR_EACH_H 71 | -------------------------------------------------------------------------------- /include/phtree/v16/for_each_hc.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Improbable Worlds Limited 3 | * Copyright 2022-2023 Tilmann Zäschke 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #ifndef PHTREE_V16_FOR_EACH_HC_H 19 | #define PHTREE_V16_FOR_EACH_HC_H 20 | 21 | #include "iterator_with_parent.h" 22 | #include "phtree/common/common.h" 23 | 24 | namespace improbable::phtree::v16 { 25 | 26 | /* 27 | * The HC (hyper cube) iterator uses `hypercube navigation`, ie. filtering of quadrants by their 28 | * binary hypercube address. In effect it compares the node's volume (box) with the query volume 29 | * (box) to calculate two bit masks, mask_lower_ and mask_upper_. These can be used as the number of 30 | * the lowest and highest quadrant that overlaps with the query box. They can also be used to tell 31 | * for any quadrant whether it overlaps with the query, simply by comparing the quadrant's ID with 32 | * the two masks, see IsPosValid(). 33 | * 34 | * For details see "Efficient Z-Ordered Traversal of Hypercube Indexes" by T. Zäschke, M.C. Norrie, 35 | * 2017. 36 | */ 37 | template 38 | class ForEachHC { 39 | static constexpr dimension_t DIM = CONVERT::DimInternal; 40 | using KeyInternal = typename CONVERT::KeyInternal; 41 | using SCALAR = typename CONVERT::ScalarInternal; 42 | using EntryT = Entry; 43 | using hc_pos_t = detail::hc_pos_dim_t; 44 | 45 | public: 46 | template 47 | ForEachHC( 48 | const KeyInternal& range_min, 49 | const KeyInternal& range_max, 50 | const CONVERT* converter, 51 | CB&& callback, 52 | F&& filter) 53 | : min_{range_min} 54 | , max_{range_max} 55 | , converter_{converter} 56 | , callback_{std::forward(callback)} 57 | , filter_(std::forward(filter)) {} 58 | 59 | void Traverse(const EntryT& entry, const EntryIteratorC* opt_it = nullptr) { 60 | assert(entry.IsNode()); 61 | hc_pos_t mask_lower = 0; 62 | hc_pos_t mask_upper = 0; 63 | detail::CalcLimits(entry.GetNodePostfixLen(), min_, max_, entry.GetKey(), mask_lower, mask_upper); 64 | auto& entries = entry.GetNode().Entries(); 65 | auto postfix_len = entry.GetNodePostfixLen(); 66 | auto end = entries.end(); 67 | auto iter = opt_it != nullptr && *opt_it != end ? *opt_it : entries.lower_bound(mask_lower); 68 | for (; iter != end && iter->first <= mask_upper; ++iter) { 69 | auto child_hc_pos = iter->first; 70 | // Use bit-mask magic to check whether we are in a valid quadrant. 71 | // -> See paper referenced in class description. 72 | if (((child_hc_pos | mask_lower) & mask_upper) == child_hc_pos) { 73 | const auto& child = iter->second; 74 | const auto& child_key = child.GetKey(); 75 | if (child.IsNode()) { 76 | if (CheckNode(child, postfix_len)) { 77 | Traverse(child); 78 | } 79 | } else { 80 | T& value = child.GetValue(); 81 | if (detail::IsInRange(child_key, min_, max_) && 82 | filter_.IsEntryValid(child_key, value)) { 83 | callback_(converter_->post(child_key), value); 84 | } 85 | } 86 | } 87 | } 88 | } 89 | 90 | private: 91 | bool CheckNode(const EntryT& entry, detail::bit_width_t parent_postfix_len) { 92 | const KeyInternal& key = entry.GetKey(); 93 | // Check if the node overlaps with the query box. 94 | // An infix with len=0 implies that at least part of the child node overlaps with the query, 95 | // otherwise the bit mask checking would have returned 'false'. 96 | // Putting it differently, if the infix has len=0, then there is no point in validating it. 97 | bool mismatch = false; 98 | if (entry.HasNodeInfix(parent_postfix_len)) { 99 | // Mask for comparing the prefix with the query boundaries. 100 | assert(entry.GetNodePostfixLen() + 1 < detail::MAX_BIT_WIDTH); 101 | SCALAR comparison_mask = detail::MAX_MASK << (entry.GetNodePostfixLen() + 1); 102 | for (dimension_t dim = 0; dim < DIM; ++dim) { 103 | SCALAR prefix = key[dim] & comparison_mask; 104 | mismatch |= (prefix > max_[dim] || prefix < (min_[dim] & comparison_mask)); 105 | } 106 | } 107 | return !mismatch && filter_.IsNodeValid(key, entry.GetNodePostfixLen() + 1); 108 | } 109 | 110 | const KeyInternal min_; 111 | const KeyInternal max_; 112 | const CONVERT* converter_; 113 | CALLBACK_FN callback_; 114 | FILTER_FN filter_; 115 | }; 116 | } // namespace improbable::phtree::v16 117 | 118 | #endif // PHTREE_V16_FOR_EACH_HC_H 119 | -------------------------------------------------------------------------------- /include/phtree/v16/iterator_base.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Improbable Worlds Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef PHTREE_V16_ITERATOR_BASE_H 18 | #define PHTREE_V16_ITERATOR_BASE_H 19 | 20 | #include "phtree/common/common.h" 21 | #include "phtree/filter.h" 22 | #include "entry.h" 23 | 24 | namespace improbable::phtree::v16 { 25 | 26 | /* 27 | * Base class for all PH-Tree iterators. 28 | */ 29 | template 30 | class IteratorBase { 31 | public: 32 | explicit IteratorBase() noexcept : current_entry_{nullptr} {} 33 | explicit IteratorBase(const EntryT* current_entry) noexcept : current_entry_{current_entry} {} 34 | 35 | inline auto& operator*() const noexcept { 36 | assert(current_entry_); 37 | return current_entry_->GetValue(); 38 | } 39 | 40 | inline auto* operator->() const noexcept { 41 | assert(current_entry_); 42 | return ¤t_entry_->GetValue(); 43 | } 44 | 45 | inline friend bool operator==( 46 | const IteratorBase& left, const IteratorBase& right) noexcept { 47 | return left.current_entry_ == right.current_entry_; 48 | } 49 | 50 | inline friend bool operator!=( 51 | const IteratorBase& left, const IteratorBase& right) noexcept { 52 | return left.current_entry_ != right.current_entry_; 53 | } 54 | 55 | auto& second() const { 56 | return current_entry_->GetValue(); 57 | } 58 | 59 | [[nodiscard]] inline bool IsEnd() const noexcept { 60 | return current_entry_ == nullptr; 61 | } 62 | 63 | inline EntryT* GetEntry() const noexcept { 64 | return const_cast(current_entry_); 65 | } 66 | 67 | protected: 68 | void SetFinished() { 69 | current_entry_ = nullptr; 70 | } 71 | 72 | void SetCurrentResult(const EntryT* current_entry) { 73 | current_entry_ = current_entry; 74 | } 75 | 76 | protected: 77 | const EntryT* current_entry_; 78 | }; 79 | 80 | template 81 | using IteratorEnd = IteratorBase; 82 | 83 | template 84 | class IteratorWithFilter 85 | : public IteratorBase> { 86 | protected: 87 | static constexpr dimension_t DIM = CONVERT::DimInternal; 88 | using KeyInternal = typename CONVERT::KeyInternal; 89 | using SCALAR = typename CONVERT::ScalarInternal; 90 | using EntryT = Entry; 91 | 92 | public: 93 | template 94 | explicit IteratorWithFilter(const CONVERT* converter, F&& filter) noexcept 95 | : IteratorBase(nullptr), converter_{converter}, filter_{std::forward(filter)} {} 96 | 97 | explicit IteratorWithFilter(const EntryT* current_entry, const CONVERT* converter) noexcept 98 | : IteratorBase(current_entry), converter_{converter}, filter_{FILTER_FN()} {} 99 | 100 | auto first() const { 101 | return converter_->post(this->current_entry_->GetKey()); 102 | } 103 | 104 | auto& __Filter() { 105 | return filter_; 106 | } 107 | 108 | protected: 109 | [[nodiscard]] bool ApplyFilter(const EntryT& entry) { 110 | return entry.IsNode() ? filter_.IsNodeValid(entry.GetKey(), entry.GetNodePostfixLen() + 1) 111 | : filter_.IsEntryValid(entry.GetKey(), entry.GetValue()); 112 | } 113 | 114 | auto post(const KeyInternal& point) { 115 | return converter_->post(point); 116 | } 117 | 118 | private: 119 | const CONVERT* converter_; 120 | FILTER_FN filter_; 121 | }; 122 | 123 | } // namespace improbable::phtree::v16 124 | 125 | #endif // PHTREE_V16_ITERATOR_BASE_H 126 | -------------------------------------------------------------------------------- /include/phtree/v16/iterator_full.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Improbable Worlds Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef PHTREE_V16_ITERATOR_FULL_H 18 | #define PHTREE_V16_ITERATOR_FULL_H 19 | 20 | #include "phtree/common/common.h" 21 | #include "iterator_base.h" 22 | 23 | namespace improbable::phtree::v16 { 24 | 25 | template 26 | class Node; 27 | 28 | template 29 | class IteratorFull : public IteratorWithFilter { 30 | static constexpr dimension_t DIM = CONVERT::DimInternal; 31 | using SCALAR = typename CONVERT::ScalarInternal; 32 | using NodeT = Node; 33 | using EntryT = typename IteratorWithFilter::EntryT; 34 | 35 | public: 36 | template 37 | IteratorFull(const EntryT& root, const CONVERT* converter, F&& filter) 38 | : IteratorWithFilter(converter, std::forward(filter)) 39 | , stack_{} 40 | , stack_size_{0} { 41 | PrepareAndPush(root.GetNode()); 42 | FindNextElement(); 43 | } 44 | 45 | IteratorFull& operator++() noexcept { 46 | FindNextElement(); 47 | return *this; 48 | } 49 | 50 | IteratorFull operator++(int) noexcept { 51 | IteratorFull iterator(*this); 52 | ++(*this); 53 | return iterator; 54 | } 55 | 56 | private: 57 | void FindNextElement() noexcept { 58 | while (!IsEmpty()) { 59 | auto* p = &Peek(); 60 | while (*p != PeekEnd()) { 61 | auto& candidate = (*p)->second; 62 | ++(*p); 63 | if (this->ApplyFilter(candidate)) { 64 | if (candidate.IsNode()) { 65 | p = &PrepareAndPush(candidate.GetNode()); 66 | } else { 67 | this->SetCurrentResult(&candidate); 68 | return; 69 | } 70 | } 71 | } 72 | // return to parent node 73 | Pop(); 74 | } 75 | // finished 76 | this->SetFinished(); 77 | } 78 | 79 | auto& PrepareAndPush(const NodeT& node) { 80 | assert(stack_size_ < stack_.size() - 1); 81 | // No '&' because this is a temp value 82 | stack_[stack_size_].first = node.Entries().cbegin(); 83 | stack_[stack_size_].second = node.Entries().end(); 84 | ++stack_size_; 85 | return stack_[stack_size_ - 1].first; 86 | } 87 | 88 | auto& Peek() noexcept { 89 | assert(stack_size_ > 0); 90 | return stack_[stack_size_ - 1].first; 91 | } 92 | 93 | auto& PeekEnd() noexcept { 94 | assert(stack_size_ > 0); 95 | return stack_[stack_size_ - 1].second; 96 | } 97 | 98 | auto& Pop() noexcept { 99 | assert(stack_size_ > 0); 100 | return stack_[--stack_size_].first; 101 | } 102 | 103 | bool IsEmpty() noexcept { 104 | return stack_size_ == 0; 105 | } 106 | 107 | std::array< 108 | std::pair, EntryIteratorC>, 109 | detail::MAX_BIT_WIDTH> 110 | stack_; 111 | size_t stack_size_; 112 | }; 113 | 114 | } // namespace improbable::phtree::v16 115 | 116 | #endif // PHTREE_V16_ITERATOR_FULL_H 117 | -------------------------------------------------------------------------------- /include/phtree/v16/iterator_lower_bound.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Tilmann Zäschke 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef PHTREE_V16_ITERATOR_LOWER_BOUND_H 18 | #define PHTREE_V16_ITERATOR_LOWER_BOUND_H 19 | 20 | #include "iterator_base.h" 21 | #include "phtree/common/common.h" 22 | 23 | namespace improbable::phtree::v16 { 24 | 25 | template 26 | class Node; 27 | 28 | /** 29 | * This iterator starts at a given position defined by "key" and then iterates until end(). 30 | * @tparam T Value type 31 | * @tparam CONVERT Converter 32 | * @tparam FILTER_FN Filter 33 | */ 34 | template 35 | class IteratorLowerBound : public IteratorWithFilter { 36 | static constexpr dimension_t DIM = CONVERT::DimInternal; 37 | using SCALAR = typename CONVERT::ScalarInternal; 38 | using KeyInternalT = typename CONVERT::KeyInternal; 39 | using NodeT = Node; 40 | using EntryT = typename IteratorWithFilter::EntryT; 41 | 42 | public: 43 | template 44 | IteratorLowerBound( 45 | const EntryT* root, const KeyInternalT& key, const CONVERT* converter, F&& filter) 46 | : IteratorWithFilter(converter, std::forward(filter)) 47 | , stack_{} 48 | , stack_size_{0} { 49 | FindFirstElement(root, key); 50 | } 51 | 52 | IteratorLowerBound& operator++() noexcept { 53 | FindNextElement(); 54 | return *this; 55 | } 56 | 57 | IteratorLowerBound operator++(int) noexcept { 58 | IteratorFull iterator(*this); 59 | ++(*this); 60 | return iterator; 61 | } 62 | 63 | private: 64 | void FindFirstElement(const EntryT* root, const KeyInternalT& key) noexcept { 65 | bool found = false; 66 | auto* node = &root->GetNode(); 67 | auto it1 = node->LowerBoundC(key, root->GetNodePostfixLen(), found); 68 | if (it1 != node->End()) { 69 | Push(it1, node->End()); 70 | } 71 | if (!found) { 72 | FindNextElement(); 73 | return; 74 | } 75 | 76 | while (!IsEmpty()) { 77 | auto& it = Peek(); 78 | auto& entry = it->second; 79 | ++it; 80 | if (entry.IsNode()) { 81 | found = false; 82 | auto it2 = entry.GetNode().LowerBoundC(key, entry.GetNodePostfixLen(), found); 83 | if (it2 != entry.GetNode().End()) { 84 | Push(it2, entry.GetNode().End()); 85 | } 86 | if (!found) { 87 | FindNextElement(); 88 | return; 89 | } 90 | } else { 91 | this->SetCurrentResult(&entry); 92 | return; 93 | } 94 | } 95 | // finished 96 | this->SetFinished(); 97 | } 98 | 99 | void FindNextElement() noexcept { 100 | while (!IsEmpty()) { 101 | auto* p = &Peek(); 102 | while (*p != PeekEnd()) { 103 | auto& candidate = (*p)->second; 104 | ++(*p); 105 | if (this->ApplyFilter(candidate)) { 106 | if (candidate.IsNode()) { 107 | p = &PrepareAndPush(candidate.GetNode()); 108 | } else { 109 | this->SetCurrentResult(&candidate); 110 | return; 111 | } 112 | } 113 | } 114 | // return to parent node 115 | Pop(); 116 | } 117 | // finished 118 | this->SetFinished(); 119 | } 120 | 121 | auto& Push(const EntryIteratorC& begin, const EntryIteratorC& end) { 122 | assert(stack_size_ < stack_.size() - 1); 123 | stack_[stack_size_].first = begin; 124 | stack_[stack_size_].second = end; 125 | ++stack_size_; 126 | return stack_[stack_size_ - 1].first; 127 | } 128 | 129 | auto& PrepareAndPush(const NodeT& node) { 130 | assert(stack_size_ < stack_.size() - 1); 131 | // No '&' because this is a temp value 132 | stack_[stack_size_].first = node.Entries().cbegin(); 133 | stack_[stack_size_].second = node.Entries().end(); 134 | ++stack_size_; 135 | return stack_[stack_size_ - 1].first; 136 | } 137 | 138 | auto& Peek() noexcept { 139 | assert(stack_size_ > 0); 140 | return stack_[stack_size_ - 1].first; 141 | } 142 | 143 | auto& PeekEnd() noexcept { 144 | assert(stack_size_ > 0); 145 | return stack_[stack_size_ - 1].second; 146 | } 147 | 148 | auto& Pop() noexcept { 149 | assert(stack_size_ > 0); 150 | return stack_[--stack_size_].first; 151 | } 152 | 153 | bool IsEmpty() noexcept { 154 | return stack_size_ == 0; 155 | } 156 | 157 | std::array< 158 | std::pair, EntryIteratorC>, 159 | detail::MAX_BIT_WIDTH> 160 | stack_; 161 | size_t stack_size_; 162 | }; 163 | 164 | } // namespace improbable::phtree::v16 165 | 166 | #endif // PHTREE_V16_ITERATOR_LOWER_BOUND_H 167 | -------------------------------------------------------------------------------- /include/phtree/v16/iterator_with_parent.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Improbable Worlds Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef PHTREE_V16_ITERATOR_SIMPLE_H 18 | #define PHTREE_V16_ITERATOR_SIMPLE_H 19 | 20 | #include "phtree/common/common.h" 21 | #include "iterator_base.h" 22 | 23 | namespace improbable::phtree::v16 { 24 | 25 | template 26 | class IteratorWithParent : public IteratorWithFilter { 27 | static constexpr dimension_t DIM = CONVERT::DimInternal; 28 | using SCALAR = typename CONVERT::ScalarInternal; 29 | using EntryT = typename IteratorWithFilter::EntryT; 30 | friend PhTreeV16; 31 | 32 | public: 33 | explicit IteratorWithParent( 34 | const EntryT* current_result, 35 | const EntryT* current_node, 36 | const EntryT* parent_node, 37 | const CONVERT* converter) noexcept 38 | : IteratorWithFilter(current_result, converter) 39 | , current_node_{current_node} 40 | , parent_node_{parent_node} {} 41 | 42 | IteratorWithParent& operator++() { 43 | this->SetFinished(); 44 | return *this; 45 | } 46 | 47 | IteratorWithParent operator++(int) { 48 | IteratorWithParent iterator(*this); 49 | ++(*this); 50 | return iterator; 51 | } 52 | 53 | private: 54 | /* 55 | * The parent entry contains the parent node. The parent node is the node ABOVE the current node 56 | * which contains the current entry. 57 | */ 58 | EntryT* GetNodeEntry() const { 59 | return const_cast(current_node_); 60 | } 61 | 62 | EntryT* GetParentNodeEntry() const { 63 | return const_cast(parent_node_); 64 | } 65 | 66 | const EntryT* current_node_; 67 | const EntryT* parent_node_; 68 | }; 69 | 70 | } // namespace improbable::phtree::v16 71 | 72 | #endif // PHTREE_V16_ITERATOR_SIMPLE_H 73 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | project(phtree-tests LANGUAGES CXX) 3 | 4 | include(FetchContent) 5 | include(common/scripts.cmake) 6 | 7 | FetchContent_Declare( 8 | googletest 9 | GIT_REPOSITORY https://github.com/google/googletest.git 10 | GIT_TAG release-1.12.1 11 | ) 12 | if (MSVC) 13 | # Avoids LNK2038 Error with MSVC 14 | set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) 15 | endif () 16 | FetchContent_MakeAvailable(googletest) 17 | 18 | # The next line is optional, but keeps your CACHE cleaner: 19 | mark_as_advanced( 20 | BUILD_GTEST BUILD_SHARED_LIBS 21 | gtest_build_samples gtest_build_tests 22 | gtest_disable_pthreads gtest_force_shared_crt gtest_hide_internal_symbols 23 | ) 24 | 25 | # If you are interested in keeping IDEs that support folders clean, I would also add these lines: 26 | set_target_properties(gtest PROPERTIES FOLDER extern) 27 | set_target_properties(gtest_main PROPERTIES FOLDER extern) 28 | 29 | #include(GoogleTest) 30 | #gtest_discover_tests(all_tests_driver) 31 | 32 | if (PHTREE_CODE_COVERAGE) 33 | package_add_test_main(all_tests 34 | all_tests.cc 35 | converter_test.cc 36 | distance_test.cc 37 | filter_test.cc 38 | phtree_test.cc 39 | phtree_test_const_values.cc 40 | phtree_test_issues.cc 41 | phtree_test_ptr_values.cc 42 | phtree_test_unique_ptr_values.cc 43 | phtree_f_test.cc 44 | phtree_d_test.cc 45 | phtree_d_test_copy_move.cc 46 | phtree_d_test_custom_key.cc 47 | phtree_d_test_filter.cc 48 | phtree_d_test_preprocessor.cc 49 | phtree_box_f_test.cc 50 | phtree_box_d_test.cc 51 | phtree_box_d_test_filter.cc 52 | phtree_box_d_test_query_types.cc 53 | phtree_multimap_d_test.cc 54 | phtree_multimap_d_test_copy_move.cc 55 | phtree_multimap_d_test_filter.cc 56 | phtree_multimap_d_test_unique_ptr_values.cc 57 | phtree_multimap_box_d_test.cc 58 | common/b_plus_tree_hash_map_test.cc 59 | common/b_plus_tree_map_test.cc 60 | common/b_plus_tree_multimap_test.cc 61 | # Disabled, see issue #147 62 | # common/b_priority_queue_test.cc 63 | # common/b_vector_test.cc 64 | common/base_types_test.cc 65 | common/bits_test.cc 66 | common/common_test.cc 67 | common/flat_array_map_test.cc 68 | common/flat_sparse_map_test.cc) 69 | target_compile_definitions(all_tests PUBLIC SKIP_TEST_MEMORY_LEAKS=ON) 70 | else () 71 | package_add_test(phtree_test phtree_test.cc) 72 | package_add_test(phtree_test_const_values phtree_test_const_values.cc) 73 | package_add_test(phtree_test_issues phtree_test_issues.cc) 74 | target_compile_definitions(phtree_test_issues PUBLIC SKIP_TEST_MEMORY_LEAKS=ON) 75 | package_add_test(phtree_test_ptr_values phtree_test_ptr_values.cc) 76 | package_add_test(phtree_test_unique_ptr_values phtree_test_unique_ptr_values.cc) 77 | 78 | package_add_test(phtree_f_test phtree_f_test.cc) 79 | 80 | package_add_test(phtree_d_test phtree_d_test.cc) 81 | package_add_test(phtree_d_test_copy_move phtree_d_test_copy_move.cc) 82 | package_add_test(phtree_d_test_custom_key phtree_d_test_custom_key.cc) 83 | package_add_test(phtree_d_test_filter phtree_d_test_filter.cc) 84 | package_add_test(phtree_d_test_preprocessor phtree_d_test_preprocessor.cc) 85 | 86 | package_add_test(phtree_box_f_test phtree_box_f_test.cc) 87 | 88 | package_add_test(phtree_box_d_test phtree_box_d_test.cc) 89 | package_add_test(phtree_box_d_test_filter phtree_box_d_test_filter.cc) 90 | package_add_test(phtree_box_d_test_query_types phtree_box_d_test_query_types.cc) 91 | 92 | package_add_test(phtree_multimap_d_test phtree_multimap_d_test.cc) 93 | package_add_test(phtree_multimap_d_test_copy_move phtree_multimap_d_test_copy_move.cc) 94 | package_add_test(phtree_multimap_d_test_filter phtree_multimap_d_test_filter.cc) 95 | package_add_test(phtree_multimap_d_test_unique_ptr_values phtree_multimap_d_test_unique_ptr_values.cc) 96 | 97 | package_add_test(phtree_multimap_box_d_test phtree_multimap_box_d_test.cc) 98 | 99 | package_add_test(converter_test converter_test.cc) 100 | package_add_test(distance_test distance_test.cc) 101 | package_add_test(filter_test filter_test.cc) 102 | 103 | add_subdirectory(common) 104 | endif () 105 | -------------------------------------------------------------------------------- /test/all_tests.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // #include "gtest/gtest.h" 4 | 5 | //#include "phtree_f_test.cc" 6 | //#include "phtree_test.cc" 7 | 8 | int main(int argc, char** argv) { 9 | ::testing::InitGoogleTest(&argc, argv); 10 | return RUN_ALL_TESTS(); 11 | } -------------------------------------------------------------------------------- /test/common/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:private"]) 2 | 3 | cc_test( 4 | name = "base_types_test", 5 | timeout = "long", 6 | srcs = [ 7 | "base_types_test.cc", 8 | ], 9 | linkstatic = True, 10 | deps = [ 11 | "//:phtree", 12 | "@gtest//:gtest_main", 13 | ], 14 | ) 15 | 16 | cc_test( 17 | name = "bits_test", 18 | timeout = "long", 19 | srcs = [ 20 | "bits_test.cc", 21 | ], 22 | linkstatic = True, 23 | deps = [ 24 | "//:phtree", 25 | "@gtest//:gtest_main", 26 | ], 27 | ) 28 | 29 | cc_test( 30 | name = "common_test", 31 | timeout = "long", 32 | srcs = [ 33 | "common_test.cc", 34 | ], 35 | linkstatic = True, 36 | deps = [ 37 | "//:phtree", 38 | "@gtest//:gtest_main", 39 | ], 40 | ) 41 | 42 | cc_test( 43 | name = "flat_array_map_test", 44 | timeout = "long", 45 | srcs = [ 46 | "flat_array_map_test.cc", 47 | ], 48 | linkstatic = True, 49 | deps = [ 50 | "//:phtree", 51 | "@gtest//:gtest_main", 52 | ], 53 | ) 54 | 55 | cc_test( 56 | name = "b_priority_queue_test", 57 | timeout = "long", 58 | srcs = [ 59 | "b_priority_queue_test.cc", 60 | ], 61 | linkstatic = True, 62 | deps = [ 63 | "//:phtree", 64 | "@gtest//:gtest_main", 65 | ], 66 | ) 67 | 68 | cc_test( 69 | name = "b_vector_test", 70 | timeout = "long", 71 | srcs = [ 72 | "b_vector_test.cc", 73 | ], 74 | linkstatic = True, 75 | deps = [ 76 | "//:phtree", 77 | "@gtest//:gtest_main", 78 | ], 79 | ) 80 | 81 | cc_test( 82 | name = "b_plus_tree_hash_map_test", 83 | timeout = "long", 84 | srcs = [ 85 | "b_plus_tree_hash_map_test.cc", 86 | ], 87 | linkstatic = True, 88 | deps = [ 89 | "//:phtree", 90 | "@gtest//:gtest_main", 91 | ], 92 | ) 93 | 94 | cc_test( 95 | name = "b_plus_tree_map_test", 96 | timeout = "long", 97 | srcs = [ 98 | "b_plus_tree_map_test.cc", 99 | ], 100 | linkstatic = True, 101 | deps = [ 102 | "//:phtree", 103 | "@gtest//:gtest_main", 104 | ], 105 | ) 106 | 107 | cc_test( 108 | name = "b_plus_tree_multimap_test", 109 | timeout = "long", 110 | srcs = [ 111 | "b_plus_tree_multimap_test.cc", 112 | ], 113 | linkstatic = True, 114 | deps = [ 115 | "//:phtree", 116 | "@gtest//:gtest_main", 117 | ], 118 | ) 119 | 120 | cc_test( 121 | name = "flat_sparse_map_test", 122 | timeout = "long", 123 | srcs = [ 124 | "flat_sparse_map_test.cc", 125 | ], 126 | linkstatic = True, 127 | deps = [ 128 | "//:phtree", 129 | "@gtest//:gtest_main", 130 | ], 131 | ) 132 | -------------------------------------------------------------------------------- /test/common/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include(scripts.cmake) 2 | 3 | package_add_test(b_plus_tree_hash_map_test b_plus_tree_hash_map_test.cc) 4 | package_add_test(b_plus_tree_map_test b_plus_tree_map_test.cc) 5 | package_add_test(b_plus_tree_multimap_test b_plus_tree_multimap_test.cc) 6 | package_add_test(b_priority_queue_test b_priority_queue_test.cc) 7 | package_add_test(b_vector_test b_vector_test.cc) 8 | 9 | package_add_test(base_types_test base_types_test.cc) 10 | package_add_test(bits_test bits_test.cc) 11 | package_add_test(common_test common_test.cc) 12 | 13 | package_add_test(flat_array_map_test flat_array_map_test.cc) 14 | package_add_test(flat_sparse_map_test flat_sparse_map_test.cc) 15 | -------------------------------------------------------------------------------- /test/common/base_types_test.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Improbable Worlds Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "phtree/common/base_types.h" 18 | #include 19 | #include 20 | 21 | using namespace improbable::phtree; 22 | 23 | TEST(PhTreeBaseTypesTest, PhPointD) { 24 | PhPointD<3> point{1, 2, 3}; 25 | 26 | for (int i = 0; i < 3; i++) { 27 | ASSERT_EQ(point[i], i + 1); 28 | } 29 | 30 | // try assigning coordinates 31 | point = {7, 8, 9}; 32 | for (int i = 0; i < 3; i++) { 33 | ASSERT_EQ(point[i], i + 7); 34 | } 35 | 36 | // Test '<<' operator 37 | std::stringstream ss; 38 | ss << point; 39 | ASSERT_EQ("[7,8,9]", ss.str()); 40 | } 41 | 42 | TEST(PhTreeBaseTypesTest, PhPointF) { 43 | PhPoint<3, float> point{1, 2, 3}; 44 | 45 | for (int i = 0; i < 3; i++) { 46 | ASSERT_EQ(point[i], i + 1); 47 | } 48 | 49 | // try assigning coordinates 50 | point = {7, 8, 9}; 51 | for (int i = 0; i < 3; i++) { 52 | ASSERT_EQ(point[i], i + 7); 53 | } 54 | 55 | // Test '<<' operator 56 | std::stringstream ss; 57 | ss << point; 58 | ASSERT_EQ("[7,8,9]", ss.str()); 59 | } 60 | 61 | TEST(PhTreeBaseTypesTest, PhBoxD) { 62 | PhBoxD<3> box({1, 2, 3}, {4, 5, 6}); 63 | 64 | for (int i = 0; i < 3; i++) { 65 | ASSERT_EQ(box.min()[i], i + 1); 66 | ASSERT_EQ(box.max()[i], i + 4); 67 | } 68 | 69 | // try assigning coordinates 70 | box.min() = {7, 8, 9}; 71 | box.max() = {10, 11, 12}; 72 | for (int i = 0; i < 3; i++) { 73 | ASSERT_EQ(box.min()[i], i + 7); 74 | ASSERT_EQ(box.max()[i], i + 10); 75 | } 76 | 77 | // Test that hash function works 78 | std::unordered_set> map; 79 | ASSERT_TRUE(map.insert(box).second); 80 | ASSERT_NE(42, std::hash>()(box)); 81 | 82 | // Test '<<' operator 83 | std::stringstream ss; 84 | ss << box; 85 | ASSERT_EQ("[7,8,9]:[10,11,12]", ss.str()); 86 | } 87 | -------------------------------------------------------------------------------- /test/common/bits_test.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Improbable Worlds Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "phtree/common/bits.h" 18 | #include 19 | #include 20 | 21 | using namespace improbable::phtree; 22 | using namespace detail; 23 | 24 | TEST(PhTreeBitsTest, CountLeadingZeros64) { 25 | std::uint64_t x = 1; 26 | x <<= 63; 27 | for (int i = 0; i < 64; i++) { 28 | int ctz = CountLeadingZeros64(x); 29 | ASSERT_EQ(i, ctz); 30 | x >>= 1; 31 | } 32 | } 33 | 34 | TEST(PhTreeBitsTest, CountTrailingZeros64) { 35 | std::uint64_t x = 1; 36 | for (int i = 0; i < 64; i++) { 37 | int ctz = CountTrailingZeros64(x); 38 | ASSERT_EQ(i, ctz); 39 | x <<= 1; 40 | } 41 | } 42 | 43 | TEST(PhTreeBitsTest, CountLeadingZeros32) { 44 | std::uint32_t x = 1; 45 | x <<= 31; 46 | for (int i = 0; i < 32; i++) { 47 | int ctz = CountLeadingZeros(x); 48 | ASSERT_EQ(i, ctz); 49 | x >>= 1; 50 | } 51 | } 52 | 53 | TEST(PhTreeBitsTest, CountTrailingZeros32) { 54 | std::uint32_t x = 1; 55 | for (int i = 0; i < 32; i++) { 56 | int ctz = CountTrailingZeros(x); 57 | ASSERT_EQ(i, ctz); 58 | x <<= 1; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /test/common/common_test.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Improbable Worlds Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "phtree/common/common.h" 18 | #include "phtree/converter.h" 19 | #include 20 | 21 | using namespace improbable::phtree; 22 | using namespace improbable::phtree::detail; 23 | 24 | TEST(PhTreeCommonTest, NumberOfDivergingBits) { 25 | double d1 = -55; 26 | double d2 = 7; 27 | 28 | auto l1 = ScalarConverterIEEE::pre(d1); 29 | auto l2 = ScalarConverterIEEE::pre(d2); 30 | scalar_64_t l_min = std::numeric_limits::lowest(); 31 | scalar_64_t l_max = std::numeric_limits::max(); 32 | 33 | bit_width_t x = NumberOfDivergingBits(PhPoint<2>({l1, l1}), PhPoint<2>({l2, l2})); 34 | ASSERT_EQ(64u, x); 35 | x = NumberOfDivergingBits(PhPoint<2>({-1, -1}), PhPoint<2>({l_min, l_min})); 36 | ASSERT_EQ(63u, x); 37 | x = NumberOfDivergingBits(PhPoint<2>({1, 1}), PhPoint<2>({l_max, l_max})); 38 | ASSERT_EQ(63u, x); 39 | 40 | x = NumberOfDivergingBits(PhPoint<2>({l1, l2}), PhPoint<2>({l1, l2})); 41 | ASSERT_EQ(0u, x); 42 | 43 | // PhPointD{679.186, 519.897, 519.897} 44 | PhPoint<3> p1{0x4085397c9ffc65e8, 0x40803f2cf7158e9a, 0x40803f2cf7158e9a}; 45 | // PhPointD{35.5375, 8.69049, 8.69049} 46 | PhPoint<3> p2{0x4041c4ce0e8a359e, 0x40216187a0776fd5, 0x40216187a0776fd5}; 47 | x = NumberOfDivergingBits(p1, p2); 48 | ASSERT_EQ(56u, x); 49 | 50 | // PhPointD{132.406, 219.74, 219.74} 51 | PhPoint<3> p20{0x40608cffffe5b480, 0x406b77aff096adc1, 0x406b77aff096adc1}; 52 | // PhPointD{679.186, 519.897, 519.897} 53 | PhPoint<3> p21{0x4085397c9ffc65e8, 0x40803f2cf7158e9a, 0x40803f2cf7158e9a}; 54 | x = NumberOfDivergingBits(p20, p21); 55 | ASSERT_EQ(56u, x); 56 | } 57 | -------------------------------------------------------------------------------- /test/common/flat_array_map_test.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Improbable Worlds Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "phtree/common/flat_array_map.h" 18 | #include 19 | #include 20 | 21 | using namespace improbable::phtree; 22 | 23 | TEST(PhTreeFlatArrayMapTest, SmokeTest) { 24 | const int max_size = 8; 25 | 26 | std::default_random_engine random_engine{0}; 27 | std::uniform_int_distribution<> cube_distribution(0, max_size - 1); 28 | 29 | for (int i = 0; i < 10; i++) { 30 | detail::array_map test_map; 31 | std::map reference_map; 32 | for (int j = 0; j < 2 * max_size; j++) { 33 | size_t val = cube_distribution(random_engine); 34 | bool hasVal = test_map.find(val) != test_map.end(); 35 | bool hasValRef = reference_map.find(val) != reference_map.end(); 36 | ASSERT_EQ(hasVal, hasValRef); 37 | if (!hasVal) { 38 | reference_map.emplace(val, val); 39 | test_map.emplace(val, val); 40 | } 41 | ASSERT_EQ(test_map.size(), reference_map.size()); 42 | for (auto it : reference_map) { 43 | size_t vRef = it.first; 44 | size_t vMap = test_map.find(vRef)->second; 45 | ASSERT_EQ(vMap, vRef); 46 | } 47 | for (auto it : test_map) { 48 | size_t v = it.first; 49 | size_t vRef = reference_map.find(v)->second; 50 | size_t vMap = test_map.find(v)->second; 51 | ASSERT_EQ(vMap, vRef); 52 | } 53 | } 54 | } 55 | } 56 | 57 | TEST(PhTreeFlatArrayMapTest, SmokeTestWithTryEmplace) { 58 | const int max_size = 8; 59 | 60 | std::default_random_engine random_engine{0}; 61 | std::uniform_int_distribution<> cube_distribution(0, max_size - 1); 62 | 63 | for (int i = 0; i < 10; i++) { 64 | detail::array_map test_map; 65 | std::map reference_map; 66 | for (int j = 0; j < 2 * max_size; j++) { 67 | size_t val = cube_distribution(random_engine); 68 | bool hasVal = test_map.find(val) != test_map.end(); 69 | bool hasValRef = reference_map.find(val) != reference_map.end(); 70 | ASSERT_EQ(hasVal, hasValRef); 71 | if (!hasVal) { 72 | reference_map.emplace(val, val); 73 | test_map.try_emplace(val, val); 74 | } 75 | ASSERT_EQ(test_map.size(), reference_map.size()); 76 | for (auto it : reference_map) { 77 | size_t vRef = it.first; 78 | size_t vMap = test_map.find(vRef)->second; 79 | ASSERT_EQ(vMap, vRef); 80 | } 81 | for (auto it : test_map) { 82 | size_t v = it.first; 83 | size_t vRef = reference_map.find(v)->second; 84 | size_t vMap = test_map.find(v)->second; 85 | ASSERT_EQ(vMap, vRef); 86 | } 87 | } 88 | } 89 | } 90 | 91 | TEST(PhTreeFlatArrayMapTest, IteratorPostIncrementTest) { 92 | const int num_entries = 3; 93 | 94 | detail::array_map test_map; 95 | for (int j = 0; j < num_entries; j++) { 96 | size_t val = j * 2; 97 | bool hasVal = test_map.find(val) != test_map.end(); 98 | if (!hasVal) { 99 | test_map.try_emplace(val, val); 100 | } 101 | } 102 | 103 | // test post increment 104 | auto it_post = test_map.begin(); 105 | int n_post = 0; 106 | while (it_post != test_map.end()) { 107 | size_t v = (*it_post).first; 108 | auto it_find = test_map.find(v); 109 | ASSERT_EQ(it_post, it_find); 110 | // post increment 111 | auto it2 = it_post++; 112 | ASSERT_NE(it2, it_post); 113 | ++n_post; 114 | } 115 | ASSERT_EQ(num_entries, n_post); 116 | 117 | // test pre increment 118 | auto it_pre = test_map.begin(); 119 | int n_pre = 0; 120 | while (it_pre != test_map.end()) { 121 | size_t v = (*it_pre).first; 122 | auto it_find = test_map.find(v); 123 | ASSERT_EQ(it_pre, it_find); 124 | auto it2 = ++it_pre; 125 | ASSERT_EQ(it2, it_pre); 126 | ++n_pre; 127 | } 128 | ASSERT_EQ(num_entries, n_pre); 129 | } 130 | 131 | TEST(PhTreeFlatArrayMapTest, EmptyTest) { 132 | detail::array_map test_map; 133 | ASSERT_EQ(test_map.size(), 0); 134 | ASSERT_EQ(test_map.begin(), test_map.end()); 135 | ASSERT_EQ(test_map.find(0), test_map.end()); 136 | ASSERT_EQ(test_map.lower_bound(0), test_map.end()); 137 | ASSERT_EQ(test_map.lower_bound(7), test_map.end()); 138 | ASSERT_FALSE(test_map.erase(0)); 139 | } 140 | 141 | TEST(PhTreeFlatArrayMapTest, FailTest) { 142 | detail::array_map test_map; 143 | test_map.emplace(1); 144 | test_map.emplace(2); 145 | test_map.emplace(3); 146 | ASSERT_EQ(test_map.find(0), test_map.end()); 147 | ASSERT_EQ(test_map.find(4), test_map.end()); 148 | ASSERT_EQ(test_map.lower_bound(7), test_map.end()); 149 | ASSERT_FALSE(test_map.erase(0)); 150 | ASSERT_FALSE(test_map.erase(4)); 151 | } 152 | -------------------------------------------------------------------------------- /test/common/flat_sparse_map_test.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Improbable Worlds Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "phtree/common/flat_sparse_map.h" 18 | #include 19 | #include 20 | 21 | using namespace improbable::phtree; 22 | 23 | TEST(PhTreeFlatSparseMapTest, SmokeTest) { 24 | const int max_size = 8; 25 | 26 | std::default_random_engine random_engine{0}; 27 | std::uniform_int_distribution<> cube_distribution(0, max_size - 1); 28 | 29 | for (int i = 0; i < 10; i++) { 30 | detail::sparse_map test_map; 31 | std::map reference_map; 32 | for (int j = 0; j < 2 * max_size; j++) { 33 | size_t val = cube_distribution(random_engine); 34 | bool hasVal = test_map.find(val) != test_map.end(); 35 | bool hasValRef = reference_map.find(val) != reference_map.end(); 36 | ASSERT_EQ(hasVal, hasValRef); 37 | if (!hasVal) { 38 | reference_map.emplace(val, val); 39 | test_map.emplace(val, val); 40 | } 41 | ASSERT_EQ(test_map.size(), reference_map.size()); 42 | for (auto it : reference_map) { 43 | size_t vRef = it.first; 44 | size_t vMap = test_map.find(vRef)->second; 45 | ASSERT_EQ(vMap, vRef); 46 | } 47 | for (auto it : test_map) { 48 | size_t v = it.first; 49 | size_t vRef = reference_map.find(v)->second; 50 | size_t vMap = test_map.find(v)->second; 51 | ASSERT_EQ(vMap, vRef); 52 | } 53 | } 54 | } 55 | } 56 | 57 | TEST(PhTreeFlatSparseMapTest, SmokeTestWithTryEmplace) { 58 | const int max_size = 8; 59 | 60 | std::default_random_engine random_engine{0}; 61 | std::uniform_int_distribution<> cube_distribution(0, max_size - 1); 62 | 63 | for (int i = 0; i < 10; i++) { 64 | detail::sparse_map test_map; 65 | std::map reference_map; 66 | for (int j = 0; j < 2 * max_size; j++) { 67 | size_t val = cube_distribution(random_engine); 68 | bool hasVal = test_map.find(val) != test_map.end(); 69 | bool hasValRef = reference_map.find(val) != reference_map.end(); 70 | ASSERT_EQ(hasVal, hasValRef); 71 | if (!hasVal) { 72 | reference_map.emplace(val, val); 73 | test_map.try_emplace(val, val); 74 | } 75 | ASSERT_EQ(test_map.size(), reference_map.size()); 76 | for (auto it : reference_map) { 77 | size_t vRef = it.first; 78 | size_t vMap = test_map.find(vRef)->second; 79 | ASSERT_EQ(vMap, vRef); 80 | } 81 | for (auto it : test_map) { 82 | size_t v = it.first; 83 | size_t vRef = reference_map.find(v)->second; 84 | size_t vMap = test_map.find(v)->second; 85 | ASSERT_EQ(vMap, vRef); 86 | } 87 | } 88 | } 89 | } 90 | 91 | TEST(PhTreeFlatSparseMapTest, EmptyTest) { 92 | detail::sparse_map test_map; 93 | ASSERT_EQ(test_map.size(), 0); 94 | ASSERT_EQ(test_map.begin(), test_map.end()); 95 | ASSERT_EQ(test_map.find(0), test_map.end()); 96 | ASSERT_EQ(test_map.lower_bound(0), test_map.end()); 97 | ASSERT_EQ(test_map.lower_bound(7), test_map.end()); 98 | test_map.erase(0); 99 | } 100 | 101 | TEST(PhTreeFlatSparseMapTest, FailTest) { 102 | detail::sparse_map test_map; 103 | test_map.emplace(1); 104 | test_map.emplace(2); 105 | test_map.emplace(3); 106 | ASSERT_EQ(test_map.find(0), test_map.end()); 107 | ASSERT_EQ(test_map.find(4), test_map.end()); 108 | ASSERT_EQ(test_map.lower_bound(7), test_map.end()); 109 | test_map.erase(0); 110 | test_map.erase(4); 111 | } 112 | -------------------------------------------------------------------------------- /test/common/scripts.cmake: -------------------------------------------------------------------------------- 1 | macro(package_add_test TESTNAME) 2 | # create an executable in which the tests will be stored 3 | add_executable(${TESTNAME} ${ARGN}) 4 | # link the Google test infrastructure, mocking library, and a default main function to 5 | # the test executable. Remove g_test_main if writing your own main function. 6 | target_link_libraries(${TESTNAME} GTest::gtest_main phtree) 7 | # gtest_discover_tests replaces gtest_add_tests, 8 | # see https://cmake.org/cmake/help/v3.10/module/GoogleTest.html for more options to pass to it 9 | gtest_discover_tests(${TESTNAME} 10 | # set a working directory so your project root so that you can find test data via paths relative to the project root 11 | WORKING_DIRECTORY ${PROJECT_DIR} 12 | PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${PROJECT_DIR}" 13 | ) 14 | set_target_properties(${TESTNAME} PROPERTIES FOLDER test) 15 | endmacro() 16 | 17 | macro(package_add_test_main TESTNAME) 18 | # create an executable in which the tests will be stored 19 | add_executable(${TESTNAME} ${ARGN}) 20 | # link the Google test infrastructure, mocking library, and a default main function to 21 | # the test executable. Remove g_test_main if writing your own main function. 22 | target_link_libraries(${TESTNAME} gtest phtree) 23 | # gtest_discover_tests replaces gtest_add_tests, 24 | # see https://cmake.org/cmake/help/v3.10/module/GoogleTest.html for more options to pass to it 25 | gtest_discover_tests(${TESTNAME} 26 | # set a working directory so your project root so that you can find test data via paths relative to the project root 27 | WORKING_DIRECTORY ${PROJECT_DIR} 28 | PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${PROJECT_DIR}" 29 | ) 30 | set_target_properties(${TESTNAME} PROPERTIES FOLDER test) 31 | endmacro() -------------------------------------------------------------------------------- /test/converter_test.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Improbable Worlds Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "phtree/converter.h" 18 | #include 19 | 20 | using namespace improbable::phtree; 21 | 22 | template 23 | void test_less_than(PRE pre, POST post, float d1, float d2) { 24 | auto l1 = pre(d1); 25 | auto l2 = pre(d2); 26 | ASSERT_LT(l1, l2); 27 | ASSERT_EQ(d1, post(l1)); 28 | ASSERT_EQ(d2, post(l2)); 29 | } 30 | 31 | template 32 | void testAll(PRE pre, POST post) { 33 | test_less_than(pre, post, 55.0f, 71.0f); 34 | test_less_than(pre, post, -55.0f, 7.0f); 35 | test_less_than(pre, post, -55.0f, -7.0f); 36 | } 37 | 38 | TEST(PhTreePreprocessorTest, IEEE_Double_SmokeTest) { 39 | auto pre = [](double d) { return ScalarConverterIEEE::pre(d); }; 40 | auto post = [](scalar_64_t s) { return ScalarConverterIEEE::post(s); }; 41 | testAll(pre, post); 42 | } 43 | 44 | TEST(PhTreePreprocessorTest, IEEE_Float_SmokeTest) { 45 | auto pre = [](float f) { return ScalarConverterIEEE::pre(f); }; 46 | auto post = [](scalar_32_t s) { return ScalarConverterIEEE::post(s); }; 47 | testAll(pre, post); 48 | } 49 | -------------------------------------------------------------------------------- /test/distance_test.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Improbable Worlds Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "phtree/distance.h" 18 | #include 19 | #include 20 | 21 | using namespace improbable::phtree; 22 | 23 | // NOTE: These are very superficial tests. Proper testing is done in the respective PhTree tests. 24 | 25 | TEST(PhTreeDistanceTest, DoubleEuclidean) { 26 | auto distance = DistanceEuclidean<2>(); 27 | ASSERT_DOUBLE_EQ(5, distance(PhPointD<2>{-1, -1}, PhPointD<2>{2, 3})); 28 | } 29 | 30 | TEST(PhTreeDistanceTest, DoubleL1) { 31 | auto distance = DistanceL1<2>(); 32 | ASSERT_DOUBLE_EQ(7, distance(PhPointD<2>{-1, -1}, PhPointD<2>{2, 3})); 33 | } 34 | 35 | TEST(PhTreeDistanceTest, DoubleChebyshev) { 36 | auto distance = DistanceChebyshev<2>(); 37 | ASSERT_DOUBLE_EQ(4, distance(PhPointD<2>{-1, -1}, PhPointD<2>{2, 3})); 38 | } 39 | 40 | TEST(PhTreeDistanceTest, FloatEuclidean) { 41 | auto distance = DistanceEuclidean<2>(); 42 | ASSERT_DOUBLE_EQ(5, distance(PhPointF<2>{-1, -1}, PhPointF<2>{2, 3})); 43 | } 44 | 45 | TEST(PhTreeDistanceTest, FloatL1) { 46 | auto distance = DistanceL1<2>(); 47 | ASSERT_DOUBLE_EQ(7, distance(PhPointF<2>{-1, -1}, PhPointF<2>{2, 3})); 48 | } 49 | 50 | TEST(PhTreeDistanceTest, FloatChebyshev) { 51 | auto distance = DistanceChebyshev<2>(); 52 | ASSERT_DOUBLE_EQ(4, distance(PhPointF<2>{-1, -1}, PhPointF<2>{2, 3})); 53 | } 54 | 55 | TEST(PhTreeDistanceTest, LongEuclidean) { 56 | auto distance = DistanceEuclidean<2>(); 57 | ASSERT_DOUBLE_EQ(5, distance(PhPoint<2>{-1, -1}, PhPoint<2>{2, 3})); 58 | } 59 | 60 | TEST(PhTreeDistanceTest, LongL1) { 61 | auto distance = DistanceL1<2>(); 62 | ASSERT_DOUBLE_EQ(7, distance(PhPoint<2>{-1, -1}, PhPoint<2>{2, 3})); 63 | } 64 | 65 | TEST(PhTreeDistanceTest, LongChebyshev) { 66 | auto distance = DistanceChebyshev<2>(); 67 | ASSERT_DOUBLE_EQ(4, distance(PhPoint<2>{-1, -1}, PhPoint<2>{2, 3})); 68 | } 69 | 70 | -------------------------------------------------------------------------------- /test/filter_test.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Improbable Worlds Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "phtree/filter.h" 18 | #include 19 | #include 20 | 21 | using namespace improbable::phtree; 22 | 23 | TEST(PhTreeFilterTest, FilterSphereTest) { 24 | ConverterNoOp<2, scalar_64_t> conv{}; 25 | FilterSphere filter{{5, 3}, 5, conv, DistanceEuclidean<2>{}}; 26 | // root is always valid 27 | ASSERT_TRUE(filter.IsNodeValid({0, 0}, 63)); 28 | // valid because node encompasses the circle 29 | ASSERT_TRUE(filter.IsNodeValid({1, 1}, 10)); 30 | // valid because circle encompasses the node 31 | ASSERT_TRUE(filter.IsNodeValid({5, 5}, 2)); 32 | // valid because circle encompasses the node AABB 33 | ASSERT_TRUE(filter.IsNodeValid({7, 7}, 1)); 34 | // valid because circle touches the edge of the node AABB 35 | ASSERT_TRUE(filter.IsNodeValid({5, 9}, 1)); 36 | // valid because circle cuts edge of node AABB 37 | ASSERT_TRUE(filter.IsNodeValid({12, 7}, 3)); 38 | ASSERT_TRUE(filter.IsNodeValid({10, 7}, 2)); 39 | // invalid because node is just outside the circle 40 | ASSERT_FALSE(filter.IsNodeValid({5, 10}, 1)); 41 | ASSERT_FALSE(filter.IsNodeValid({12, 12}, 3)); 42 | 43 | ASSERT_TRUE(filter.IsEntryValid({3, 7}, nullptr)); 44 | ASSERT_TRUE(filter.IsEntryValid({5, 8}, nullptr)); 45 | ASSERT_FALSE(filter.IsEntryValid({3, 8}, nullptr)); 46 | } 47 | 48 | TEST(PhTreeFilterTest, FilterAABBTest) { 49 | ConverterNoOp<2, scalar_64_t> conv{}; 50 | FilterAABB filter{{3, 3}, {7, 7}, conv}; 51 | // root is always valid 52 | ASSERT_TRUE(filter.IsNodeValid({0, 0}, 63)); 53 | // valid because node encompasses the AABB 54 | ASSERT_TRUE(filter.IsNodeValid({1, 1}, 10)); 55 | // valid 56 | ASSERT_TRUE(filter.IsNodeValid({7, 7}, 1)); 57 | // invalid 58 | ASSERT_FALSE(filter.IsNodeValid({88, 5}, 1)); 59 | 60 | ASSERT_TRUE(filter.IsEntryValid({3, 7}, nullptr)); 61 | ASSERT_FALSE(filter.IsEntryValid({2, 8}, nullptr)); 62 | } 63 | 64 | TEST(PhTreeFilterTest, FilterNoOpSmokeTest) { 65 | auto filter = FilterNoOp(); 66 | ASSERT_TRUE(filter.IsNodeValid>({3, 7, 2}, 10)); 67 | ASSERT_TRUE(filter.IsEntryValid>({3, 7, 2}, 10)); 68 | } 69 | 70 | template 71 | void TestAssignability() { 72 | ASSERT_TRUE(std::is_copy_constructible_v); 73 | ASSERT_TRUE(std::is_copy_assignable_v); 74 | ASSERT_TRUE(std::is_move_constructible_v); 75 | ASSERT_TRUE(std::is_move_assignable_v); 76 | } 77 | 78 | TEST(PhTreeFilterTest, FilterAssignableTest) { 79 | using CONV = ConverterIEEE<3>; 80 | using DIST = DistanceEuclidean<3>; 81 | TestAssignability(); 82 | TestAssignability>(); 83 | TestAssignability>(); 84 | TestAssignability>(); 85 | TestAssignability>(); 86 | } 87 | 88 | TEST(PhTreeFilterTest, ConverterAssignableTest) { 89 | TestAssignability>(); 90 | TestAssignability(); 91 | } 92 | 93 | class TestConverter : public ConverterMultiply<2, 1, 1> { 94 | public: 95 | TestConverter() = default; 96 | 97 | TestConverter(const TestConverter&) = delete; 98 | TestConverter(TestConverter&&) = delete; 99 | TestConverter& operator=(const TestConverter&) = delete; 100 | TestConverter& operator=(TestConverter&&) = delete; 101 | }; 102 | 103 | TEST(PhTreeFilterTest, ConstructFilterAABBTest) { 104 | TestConverter conv; 105 | FilterAABB filter1{{3, 3}, {7, 7}, conv}; 106 | ASSERT_TRUE(filter1.IsNodeValid({0, 0}, 63)); 107 | 108 | FilterAABB filter2{{3, 3}, {7, 7}, TestConverter()}; 109 | ASSERT_TRUE(filter2.IsNodeValid({0, 0}, 63)); 110 | } 111 | 112 | TEST(PhTreeFilterTest, ConstructFilterSphereTest) { 113 | DistanceL1<2> dist; 114 | TestConverter conv; 115 | FilterSphere filter1a{{3, 3}, 7, conv}; 116 | ASSERT_TRUE(filter1a.IsNodeValid({0, 0}, 63)); 117 | FilterSphere filter1b{{3, 3}, 7, conv, {}}; 118 | ASSERT_TRUE(filter1b.IsNodeValid({0, 0}, 63)); 119 | FilterSphere filter1c{{3, 3}, 7, conv, dist}; 120 | ASSERT_TRUE(filter1c.IsNodeValid({0, 0}, 63)); 121 | FilterSphere filter1d{{3, 3}, 7, conv, DistanceL1<2>{}}; 122 | ASSERT_TRUE(filter1d.IsNodeValid({0, 0}, 63)); 123 | 124 | FilterSphere filter2{{3, 3}, 7, TestConverter()}; 125 | ASSERT_TRUE(filter2.IsNodeValid({0, 0}, 63)); 126 | } 127 | -------------------------------------------------------------------------------- /test/phtree_box_d_test_query_types.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Improbable Worlds Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "phtree/phtree.h" 18 | #include 19 | #include 20 | 21 | using namespace improbable::phtree; 22 | 23 | namespace phtree_box_d_test_query_types { 24 | 25 | template 26 | using TestPoint = PhBoxD; 27 | 28 | template 29 | using TestTree = PhTreeBoxD; 30 | 31 | TEST(PhTreeBoxDTestQueryTypes, SmokeTestQuery) { 32 | const dimension_t DIM = 2; 33 | TestTree tree; 34 | 35 | PhPointD p00{-10, -10}; 36 | PhPointD p11{10, 10}; 37 | PhPointD pm{0, 0}; 38 | 39 | PhBoxD b00{p00, pm}; 40 | PhBoxD b11{pm, p11}; 41 | 42 | tree.emplace(b00, -1); 43 | tree.emplace(b11, 1); 44 | 45 | auto query_type = QueryInclude(); 46 | 47 | // empty 48 | auto q1 = tree.begin_query({{-9, -9}, {9, 9}}, FilterNoOp(), query_type); 49 | ASSERT_EQ(q1, tree.end()); 50 | 51 | // Find box00 but not box11 52 | auto q2 = tree.begin_query({{-11, -11}, {9, 9}}, FilterNoOp(), query_type); 53 | ASSERT_NE(q2, tree.end()); 54 | ASSERT_EQ(-1, (*q2)); 55 | ++q2; 56 | ASSERT_EQ(q2, tree.end()); 57 | 58 | // Find box11 but not box00 59 | auto q3 = tree.begin_query({{-9, -9}, {11, 11}}, FilterNoOp(), query_type); 60 | ASSERT_NE(q3, tree.end()); 61 | ASSERT_EQ(1, (*q3)); 62 | ++q3; 63 | ASSERT_EQ(q3, tree.end()); 64 | } 65 | 66 | } // namespace phtree_box_d_test_query_types 67 | -------------------------------------------------------------------------------- /test/phtree_d_test_preprocessor.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Improbable Worlds Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "phtree/phtree.h" 18 | #include 19 | #include 20 | 21 | using namespace improbable::phtree; 22 | 23 | namespace phtree_d_test_preprocessor { 24 | 25 | template 26 | using TestPoint = PhPointD; 27 | 28 | template 29 | using TestTree = PhTreeD>; 30 | 31 | class DoubleRng { 32 | public: 33 | DoubleRng(double minIncl, double maxExcl) : eng(), rnd{minIncl, maxExcl} {} 34 | 35 | double next() { 36 | return rnd(eng); 37 | } 38 | 39 | private: 40 | std::default_random_engine eng; 41 | std::uniform_real_distribution rnd; 42 | }; 43 | 44 | struct Id { 45 | Id() = default; 46 | 47 | explicit Id(const size_t i) : _i{static_cast(i)} {} 48 | 49 | bool operator==(const Id& rhs) const { 50 | return _i == rhs._i; 51 | } 52 | 53 | int _i; 54 | }; 55 | 56 | template 57 | void generateCube(std::vector>& points, size_t N) { 58 | DoubleRng rng(-1000, 1000); 59 | auto refTree = std::map, size_t>(); 60 | 61 | points.reserve(N); 62 | for (size_t i = 0; i < N; i++) { 63 | auto point = TestPoint{rng.next(), rng.next(), rng.next()}; 64 | if (refTree.count(point) != 0) { 65 | i--; 66 | continue; 67 | } 68 | 69 | refTree.emplace(point, i); 70 | points.push_back(point); 71 | } 72 | assert(refTree.size() == N); 73 | assert(points.size() == N); 74 | } 75 | 76 | template 77 | void SmokeTestBasicOps() { 78 | TestTree tree; 79 | size_t N = 10000; 80 | 81 | std::vector> points; 82 | generateCube(points, N); 83 | 84 | ASSERT_EQ(0, tree.size()); 85 | ASSERT_TRUE(tree.empty()); 86 | PhTreeDebugHelper::CheckConsistency(tree); 87 | 88 | for (size_t i = 0; i < N; i++) { 89 | TestPoint& p = points.at(i); 90 | ASSERT_EQ(tree.count(p), 0); 91 | ASSERT_EQ(tree.end(), tree.find(p)); 92 | 93 | Id id(i); 94 | if (i % 2 == 0) { 95 | ASSERT_TRUE(tree.emplace(p, id).second); 96 | } else { 97 | ASSERT_TRUE(tree.insert(p, id).second); 98 | } 99 | ASSERT_EQ(tree.count(p), 1); 100 | ASSERT_NE(tree.end(), tree.find(p)); 101 | ASSERT_EQ(id._i, tree.find(p)->_i); 102 | ASSERT_EQ(i + 1, tree.size()); 103 | 104 | // try add again 105 | ASSERT_FALSE(tree.insert(p, id).second); 106 | ASSERT_FALSE(tree.emplace(p, id).second); 107 | ASSERT_EQ(tree.count(p), 1); 108 | ASSERT_NE(tree.end(), tree.find(p)); 109 | ASSERT_EQ(id._i, tree.find(p)->_i); 110 | ASSERT_EQ(i + 1, tree.size()); 111 | ASSERT_FALSE(tree.empty()); 112 | } 113 | 114 | for (size_t i = 0; i < N; i++) { 115 | TestPoint& p = points.at(i); 116 | auto q = tree.begin_query({p, p}); 117 | ASSERT_NE(q, tree.end()); 118 | ASSERT_EQ(i, (*q)._i); 119 | ++q; 120 | ASSERT_EQ(q, tree.end()); 121 | } 122 | 123 | PhTreeDebugHelper::CheckConsistency(tree); 124 | 125 | for (size_t i = 0; i < N; i++) { 126 | TestPoint& p = points.at(i); 127 | ASSERT_NE(tree.find(p), tree.end()); 128 | ASSERT_EQ(tree.count(p), 1); 129 | ASSERT_EQ(i, tree.find(p)->_i); 130 | ASSERT_EQ(1, tree.erase(p)); 131 | 132 | ASSERT_EQ(tree.count(p), 0); 133 | ASSERT_EQ(tree.end(), tree.find(p)); 134 | ASSERT_EQ(N - i - 1, tree.size()); 135 | 136 | // try remove again 137 | ASSERT_EQ(0, tree.erase(p)); 138 | ASSERT_EQ(tree.count(p), 0); 139 | ASSERT_EQ(tree.end(), tree.find(p)); 140 | ASSERT_EQ(N - i - 1, tree.size()); 141 | if (i < N - 1) { 142 | ASSERT_FALSE(tree.empty()); 143 | } 144 | } 145 | ASSERT_EQ(0, tree.size()); 146 | ASSERT_TRUE(tree.empty()); 147 | PhTreeDebugHelper::CheckConsistency(tree); 148 | } 149 | 150 | TEST(PhTreeDTestPreprocessor, SmokeTestBasicOps) { 151 | SmokeTestBasicOps<3>(); 152 | SmokeTestBasicOps<6>(); 153 | SmokeTestBasicOps<10>(); 154 | SmokeTestBasicOps<20>(); 155 | } 156 | 157 | } // namespace phtree_d_test_preprocessor 158 | -------------------------------------------------------------------------------- /test/phtree_test_issue_142.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Tilmann Zäschke 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Test that the CALLBACK in Windows SDK does not conflict with the PhTree 18 | #define CALLBACK 19 | #include "phtree/phtree.h" 20 | #include "phtree/phtree_multimap.h" 21 | #include 22 | 23 | using namespace improbable::phtree; 24 | 25 | namespace phtree_test_issue_142 { 26 | 27 | TEST(PhTreeTestIssue142, TestIssue142) { 28 | // The real test is ABOVE, see "#define CALLBACK" 29 | auto tree = PhTreeD<2, int>(); 30 | PhPointD<2> p{1, 2}; 31 | tree.emplace(p, 42); 32 | } 33 | 34 | TEST(PhTreeTestIssue142, TestIssue142_MM) { 35 | // The real test is ABOVE, see "#define CALLBACK" 36 | auto tree = PhTreeMultiMapD<2, int>(); 37 | PhPointD<2> p{1, 2}; 38 | tree.emplace(p, 42); 39 | } 40 | 41 | } // namespace phtree_test_issue_142 42 | -------------------------------------------------------------------------------- /third_party/BUILD: -------------------------------------------------------------------------------- 1 | # NOTE: This file is intentionally left blank. 2 | # We make third_party/ its own bazel workspace because it allows to run `bazel build ...` without 3 | # having all targets defined in third-party BUILD files in this directory buildable. 4 | -------------------------------------------------------------------------------- /third_party/WORKSPACE: -------------------------------------------------------------------------------- 1 | # NOTE: This file is intentionally left blank. 2 | # We make third_party/ its own bazel workspace because it allows to run `bazel build ...` without 3 | # having all targets defined in third-party BUILD files in this directory buildable. 4 | -------------------------------------------------------------------------------- /third_party/gtest/BUILD: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Google Inc. 2 | # All Rights Reserved. 3 | # 4 | # 5 | # Redistribution and use in source and binary forms, with or without 6 | # modification, are permitted provided that the following conditions are 7 | # met: 8 | # 9 | # * Redistributions of source code must retain the above copyright 10 | # notice, this list of conditions and the following disclaimer. 11 | # * Redistributions in binary form must reproduce the above 12 | # copyright notice, this list of conditions and the following disclaimer 13 | # in the documentation and/or other materials provided with the 14 | # distribution. 15 | # * Neither the name of Google Inc. nor the names of its 16 | # contributors may be used to endorse or promote products derived from 17 | # this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | # 31 | # Author: misterg@google.com (Gennadiy Civil) 32 | # 33 | # Bazel Build for Google C++ Testing Framework(Google Test) 34 | 35 | package(default_visibility = ["//visibility:public"]) 36 | 37 | licenses(["notice"]) 38 | 39 | # This is a fork of gtests BUILD file, that additionally disables warnings (-w), due to us building with maximum warnings. 40 | 41 | # Google Test including Google Mock 42 | cc_library( 43 | name = "gtest", 44 | srcs = glob( 45 | include = [ 46 | "googletest/src/*.cc", 47 | "googletest/src/*.h", 48 | "googletest/include/gtest/**/*.h", 49 | "googlemock/src/*.cc", 50 | "googlemock/include/gmock/**/*.h", 51 | ], 52 | exclude = [ 53 | "googletest/src/gtest-all.cc", 54 | "googletest/src/gtest_main.cc", 55 | "googlemock/src/gmock-all.cc", 56 | "googlemock/src/gmock_main.cc", 57 | ], 58 | ), 59 | hdrs = glob([ 60 | "googletest/include/gtest/*.h", 61 | "googlemock/include/gmock/*.h", 62 | ]), 63 | copts = select({ 64 | "@//:windows": [], 65 | "//conditions:default": ["-w"], 66 | }), 67 | includes = [ 68 | "googlemock", 69 | "googlemock/include", 70 | "googletest", 71 | "googletest/include", 72 | ], 73 | linkopts = select({ 74 | "@//:windows": [], 75 | "//conditions:default": ["-pthread"], 76 | }), 77 | ) 78 | -------------------------------------------------------------------------------- /third_party/spdlog/BUILD: -------------------------------------------------------------------------------- 1 | licenses(["notice"]) 2 | 3 | cc_library( 4 | name = "spdlog", 5 | hdrs = glob([ 6 | "include/**/*.cc", 7 | "include/**/*.h", 8 | ]), 9 | includes = ["include"], 10 | visibility = ["//visibility:public"], 11 | ) 12 | -------------------------------------------------------------------------------- /tools/BUILD: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tzaeschke/phtree-cpp/e72ab282a37d0e7e51c257682d605d2aacb80550/tools/BUILD -------------------------------------------------------------------------------- /tools/runners/BUILD: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tzaeschke/phtree-cpp/e72ab282a37d0e7e51c257682d605d2aacb80550/tools/runners/BUILD -------------------------------------------------------------------------------- /tools/runners/sanitizers/asan/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:private"]) 2 | 3 | sh_binary( 4 | name = "asan", 5 | srcs = ["asan.sh"], 6 | data = [ 7 | "asan-suppressions.txt", 8 | "lsan-suppressions.txt", 9 | ], 10 | ) 11 | -------------------------------------------------------------------------------- /tools/runners/sanitizers/asan/asan-suppressions.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tzaeschke/phtree-cpp/e72ab282a37d0e7e51c257682d605d2aacb80550/tools/runners/sanitizers/asan/asan-suppressions.txt -------------------------------------------------------------------------------- /tools/runners/sanitizers/asan/asan.sh: -------------------------------------------------------------------------------- 1 | ASAN_OPTIONS=suppressions="tools/runners/sanitizers/asan/asan-suppressions.txt ${ASAN_OPTIONS}" \ 2 | LSAN_OPTIONS=suppressions="tools/runners/sanitizers/asan/lsan-suppressions.txt ${LSAN_OPTIONS}" \ 3 | "${@}" 4 | -------------------------------------------------------------------------------- /tools/runners/sanitizers/asan/lsan-suppressions.txt: -------------------------------------------------------------------------------- 1 | leak:ossl_init_load_crypto_nodelete -------------------------------------------------------------------------------- /tools/runners/sanitizers/msan/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:private"]) 2 | 3 | sh_binary( 4 | name = "msan", 5 | srcs = ["msan.sh"], 6 | data = [ 7 | "msan-suppressions.txt", 8 | ], 9 | ) 10 | -------------------------------------------------------------------------------- /tools/runners/sanitizers/msan/msan-suppressions.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tzaeschke/phtree-cpp/e72ab282a37d0e7e51c257682d605d2aacb80550/tools/runners/sanitizers/msan/msan-suppressions.txt -------------------------------------------------------------------------------- /tools/runners/sanitizers/msan/msan.sh: -------------------------------------------------------------------------------- 1 | MSAN_OPTIONS=suppressions="tools/runners/sanitizers/msan/msan-suppressions.txt ${MSAN_OPTIONS}" "${@}" 2 | -------------------------------------------------------------------------------- /tools/runners/sanitizers/tsan/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:private"]) 2 | 3 | sh_binary( 4 | name = "tsan", 5 | srcs = ["tsan.sh"], 6 | data = [ 7 | "tsan-suppressions.txt", 8 | ], 9 | ) 10 | -------------------------------------------------------------------------------- /tools/runners/sanitizers/tsan/tsan-suppressions.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tzaeschke/phtree-cpp/e72ab282a37d0e7e51c257682d605d2aacb80550/tools/runners/sanitizers/tsan/tsan-suppressions.txt -------------------------------------------------------------------------------- /tools/runners/sanitizers/tsan/tsan.sh: -------------------------------------------------------------------------------- 1 | TSAN_OPTIONS=suppressions="tools/runners/sanitizers/tsan/tsan-suppressions.txt ${TSAN_OPTIONS}" "${@}" 2 | -------------------------------------------------------------------------------- /tools/runners/sanitizers/ubsan/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:private"]) 2 | 3 | sh_binary( 4 | name = "ubsan", 5 | srcs = ["ubsan.sh"], 6 | data = [ 7 | "ubsan-suppressions.txt", 8 | ], 9 | ) 10 | -------------------------------------------------------------------------------- /tools/runners/sanitizers/ubsan/ubsan-suppressions.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tzaeschke/phtree-cpp/e72ab282a37d0e7e51c257682d605d2aacb80550/tools/runners/sanitizers/ubsan/ubsan-suppressions.txt -------------------------------------------------------------------------------- /tools/runners/sanitizers/ubsan/ubsan.sh: -------------------------------------------------------------------------------- 1 | UBSAN_OPTIONS=suppressions="tools/runners/sanitizers/ubsan/ubsan-suppressions.txt print_stacktrace=1 ${UBSAN_OPTIONS}" "${@}" 2 | -------------------------------------------------------------------------------- /tools/runners/sanitizers/valgrind-memcheck/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:private"]) 2 | 3 | sh_binary( 4 | name = "valgrind-memcheck", 5 | srcs = ["valgrind-memcheck.sh"], 6 | data = [ 7 | "valgrind-suppressions.txt", 8 | ], 9 | ) 10 | -------------------------------------------------------------------------------- /tools/runners/sanitizers/valgrind-memcheck/valgrind-memcheck.sh: -------------------------------------------------------------------------------- 1 | valgrind --leak-check=full --error-exitcode=1 --num-callers=30 --suppressions=tools/runners/sanitizers/valgrind-memcheck/valgrind-suppressions.txt "${@}" 2 | -------------------------------------------------------------------------------- /tools/runners/sanitizers/valgrind-memcheck/valgrind-suppressions.txt: -------------------------------------------------------------------------------- 1 | { 2 | openssl_leak 3 | Memcheck:Leak 4 | match-leak-kinds: definite 5 | fun:realloc 6 | fun:CRYPTO_realloc 7 | fun:ERR_add_error_vdata 8 | fun:ERR_add_error_data 9 | fun:dlfcn_load 10 | fun:DSO_load 11 | fun:DSO_dsobyaddr 12 | fun:ossl_init_load_crypto_nodelete 13 | fun:ossl_init_load_crypto_nodelete_ossl_ 14 | fun:__pthread_once_slow 15 | fun:CRYPTO_THREAD_run_once 16 | fun:OPENSSL_init_crypto 17 | } 18 | { 19 | pthread_stack_allocation 20 | Memcheck:Leak 21 | match-leak-kinds: possible 22 | ... 23 | fun:allocate_stack 24 | fun:pthread_create* 25 | } 26 | { 27 | gtest_use_of_uninitialised_value 28 | Memcheck:Value8 29 | fun:_itoa_word 30 | fun:vfprintf 31 | fun:vsnprintf 32 | fun:snprintf 33 | fun:_ZN7testing* 34 | } 35 | { 36 | gtest_conditional_jump_or_move 37 | Memcheck:Cond 38 | fun:_itoa_word 39 | fun:vfprintf 40 | fun:vsnprintf 41 | fun:snprintf 42 | fun:_ZN7testing* 43 | } 44 | { 45 | gtest_conditional_jump_or_move_2 46 | Memcheck:Cond 47 | fun:vfprintf 48 | fun:vsnprintf 49 | fun:snprintf 50 | fun:_ZN7testing* 51 | } 52 | 53 | --------------------------------------------------------------------------------