├── .bazelrc ├── .bazelversion ├── .clang-format ├── .github └── workflows │ ├── bazel.yml │ └── cmake.yml ├── .gitignore ├── BUILD ├── CHANGELOG.md ├── CMakeLists.txt ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── WORKSPACE ├── ci ├── includes │ ├── bazel.sh │ └── os.sh └── linting │ ├── buildifier.sh │ └── clang-format.sh ├── examples ├── BUILD ├── CMakeLists.txt └── example.cc ├── phtree ├── BUILD ├── CMakeLists.txt ├── benchmark │ ├── BUILD │ ├── benchmark_util.h │ ├── count_mm_d_benchmark.cc │ ├── erase_benchmark.cc │ ├── erase_d_benchmark.cc │ ├── extent_benchmark.cc │ ├── extent_benchmark_weird.cc │ ├── find_benchmark.cc │ ├── insert_benchmark.cc │ ├── insert_box_d_benchmark.cc │ ├── insert_d_benchmark.cc │ ├── knn_d_benchmark.cc │ ├── logging.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 │ ├── update_box_d_benchmark.cc │ ├── update_d_benchmark.cc │ ├── update_mm_box_d_benchmark.cc │ └── update_mm_d_benchmark.cc ├── common │ ├── BUILD │ ├── CMakeLists.txt │ ├── README.md │ ├── base_types.h │ ├── base_types_test.cc │ ├── bits.h │ ├── bits_test.cc │ ├── common.h │ ├── common_test.cc │ ├── converter.h │ ├── converter_test.cc │ ├── debug_helper.h │ ├── distance.h │ ├── distance_test.cc │ ├── filter.h │ ├── filter_test.cc │ ├── flat_array_map.h │ ├── flat_array_map_test.cc │ ├── flat_sparse_map.h │ ├── flat_sparse_map_test.cc │ └── tree_stats.h ├── phtree.h ├── phtree_box_d_test.cc ├── phtree_box_d_test_query_types.cc ├── phtree_box_f_test.cc ├── phtree_d_test.cc ├── phtree_d_test_custom_key.cc ├── phtree_d_test_filter.cc ├── phtree_d_test_preprocessor.cc ├── phtree_f_test.cc ├── phtree_multimap.h ├── phtree_multimap_box_d_test.cc ├── phtree_multimap_d_test.cc ├── phtree_test.cc ├── phtree_test_const_values.cc ├── phtree_test_ptr_values.cc ├── phtree_test_unique_ptr_values.cc ├── testing │ ├── BUILD │ └── gtest_main │ │ ├── BUILD │ │ └── gtest_main.cc └── v16 │ ├── BUILD │ ├── CMakeLists.txt │ ├── 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_simple.h │ ├── node.h │ └── phtree_v16.h ├── third_party ├── BUILD ├── WORKSPACE ├── gtest │ └── BUILD └── spdlog │ └── BUILD └── tools ├── BUILD ├── bazel ├── build_rules ├── BUILD ├── http.bzl └── utils.bzl └── runners ├── BUILD └── sanitizers ├── asan ├── BUILD ├── asan-suppressions.txt ├── asan.sh └── lsan-suppressions.txt ├── tsan ├── BUILD ├── tsan-suppressions.txt └── tsan.sh ├── ubsan ├── BUILD ├── ubsan-suppressions.txt └── ubsan.sh └── valgrind-memcheck ├── BUILD ├── valgrind-memcheck.sh └── valgrind-suppressions.txt /.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="-fvisibility=hidden" 30 | build:linux --copt="-fno-omit-frame-pointer" # for friendlier stack traces 31 | build:linux --copt="-Wno-error" 32 | build:linux --copt="-Wall" 33 | build:linux --copt="-Wextra" 34 | build:linux --copt="-Werror=return-type" 35 | build:linux --copt="-Werror=switch" 36 | build:linux --copt="-mavx" 37 | build:linux --copt="-Wsequence-point" 38 | build:linux --copt="-Wsign-compare" 39 | build:linux --cxxopt="-std=c++17" 40 | 41 | build:linux-release --config=release 42 | build:linux-release --config=linux 43 | build:linux-release --copt="-O3" 44 | 45 | build:benchmark --config=linux-release 46 | build:benchmark --copt="-g" # To get code references in vtune 47 | 48 | build:macos --copt="-fvisibility=hidden" 49 | build:macos --copt="-Wno-error" 50 | build:macos --cxxopt="-std=c++17" 51 | 52 | build:windows --cxxopt="/std:c++17" 53 | # Disables wingdi.h, which avoids defining a macro called ERROR. 54 | build:windows --cxxopt="/DNOGDI" 55 | build:windows --features=static_link_msvcrt 56 | # We fix the temp directory, as otherwise it is different across different Windows BK agents, which causes 57 | # the remote cache to never be hit due to differing build graph hashes. 58 | build:windows --action_env TMP=C:/Windows/Temp 59 | build:windows --action_env TEMP=C:/Windows/Temp 60 | build:windows --cxxopt="/DWIN32_LEAN_AND_MEAN" 61 | 62 | # Valgrind config. 63 | build:valgrind-memcheck --config=linux 64 | build:valgrind-memcheck --define valgrind-memcheck=true 65 | test:valgrind-memcheck --run_under=//tools/runners/sanitizers/valgrind-memcheck 66 | run:valgrind-memcheck --run_under=//tools/runners/sanitizers/valgrind-memcheck 67 | 68 | # Sanitizer configs; for an overview of the sanitizers, see https://github.com/google/sanitizers/wiki 69 | # For more specific information on sanitizers: 70 | # - https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html 71 | # - https://clang.llvm.org/docs/AddressSanitizer.html 72 | # - https://clang.llvm.org/docs/ThreadSanitizer.html 73 | build:base-sanitizer --copt="-fno-omit-frame-pointer" # for friendlier stack traces 74 | build:base-sanitiser -strip=never 75 | 76 | build:asan --config=base-sanitizer 77 | build:asan --copt="-O1" 78 | build:asan --copt="-fno-optimize-sibling-calls" 79 | build:asan --copt="-fsanitize=address" 80 | build:asan --linkopt="-fsanitize=address" 81 | test:asan --test_env="ASAN_SYMBOLIZER_PATH=/usr/lib/llvm-9/bin/llvm-symbolizer" 82 | test:asan --run_under=//tools/runners/sanitizers/asan 83 | 84 | build:tsan --config=base-sanitizer 85 | build:tsan --copt="-O1" 86 | build:tsan --copt="-fno-optimize-sibling-calls" 87 | build:tsan --copt="-fsanitize=thread" 88 | build:tsan --linkopt="-fsanitize=thread" 89 | #test:tsan --test_env="TSAN_SYMBOLIZER_PATH=/usr/lib/llvm-9/bin/llvm-symbolizer" 90 | test:tsan --run_under=//tools/runners/sanitizers/tsan 91 | 92 | build:ubsan --config=base-sanitizer 93 | build:ubsan --copt="-O1" 94 | build:ubsan --copt="-fsanitize=undefined" 95 | build:ubsan --copt="-fno-sanitize-recover=all" 96 | # Since Bazel uses clang instead of clang++, enabling -fsanitize=vptr would 97 | # require extra linkopts that cause segmentation faults on pure C code. 98 | build:ubsan --copt="-fno-sanitize=function" 99 | build:ubsan --linkopt="-fsanitize=undefined" 100 | build:ubsan --linkopt="-lubsan" 101 | test:ubsan --run_under=//tools/runners/sanitizers/ubsan 102 | 103 | # MSAN is disabled for now, as there are false positives and we can't suppress them easily. 104 | #build:msan --config=base-sanitizer 105 | #build:msan --copt="-fsanitize=memory" 106 | #build:msan --linkopt="-fsanitize=memory" 107 | #test:msan --run_under=//tools/runners/sanitizers/msan 108 | 109 | build:lint --define linting_only=true 110 | -------------------------------------------------------------------------------- /.bazelversion: -------------------------------------------------------------------------------- 1 | 4.2.2 -------------------------------------------------------------------------------- /.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.yml: -------------------------------------------------------------------------------- 1 | name: Bazel build 2 | 3 | on: [push, pull_request] 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@v2 17 | 18 | - name: Setup bazel 19 | # install bazelisk to install the appropriate bazel version 20 | run: | 21 | export PATH=$PATH:$HOME/bin && mkdir -p $HOME/bin 22 | wget https://github.com/bazelbuild/bazelisk/releases/download/v1.5.0/bazelisk-linux-amd64 && chmod +x bazelisk-linux-amd64 && mv bazelisk-linux-amd64 $HOME/bin/bazel 23 | wget https://github.com/bazelbuild/buildtools/releases/download/0.22.0/buildifier && chmod +x buildifier && mv buildifier $HOME/bin/ 24 | 25 | - name: Build 26 | shell: bash 27 | run: bazel build ... 28 | 29 | - name: Test 30 | shell: bash 31 | run: bazel test ... 32 | -------------------------------------------------------------------------------- /.github/workflows/cmake.yml: -------------------------------------------------------------------------------- 1 | name: CMake build 2 | 3 | on: [push, pull_request] 4 | 5 | env: 6 | BUILD_TYPE: Release 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v2 14 | 15 | - name: Create Build Environment 16 | run: cmake -E make_directory ${{github.workspace}}/build 17 | 18 | - name: Configure CMake 19 | # Use a bash shell so we can use the same syntax for environment variable 20 | # access regardless of the host operating system 21 | shell: bash 22 | working-directory: ${{github.workspace}}/build 23 | # Note the current convention is to use the -S and -B options here to specify source 24 | # and build directories, but this is only available with CMake 3.13 and higher. 25 | # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 26 | run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE 27 | 28 | - name: Build 29 | working-directory: ${{github.workspace}}/build 30 | shell: bash 31 | # Execute the build. You can specify a specific target with "--target " 32 | run: cmake --build . --config $BUILD_TYPE 33 | 34 | - name: Test 35 | working-directory: ${{github.workspace}}/build 36 | shell: bash 37 | # Execute tests defined by the CMake configuration. 38 | # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail 39 | # TODO Currently tests are run via bazel only. 40 | run: ctest -C $BUILD_TYPE 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .* 2 | !.bazelrc 3 | !.bazelversion 4 | !.clang-format 5 | !.gitignore 6 | !.github 7 | bazel-* 8 | !bazel-*.sh 9 | compile_commands.json 10 | perf.data* 11 | build 12 | 13 | -------------------------------------------------------------------------------- /BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | # Platform configuration definitions for select() 4 | 5 | config_setting( 6 | name = "linux", 7 | constraint_values = ["@bazel_tools//platforms:linux"], 8 | ) 9 | 10 | config_setting( 11 | name = "macos", 12 | constraint_values = ["@bazel_tools//platforms:osx"], 13 | ) 14 | 15 | config_setting( 16 | name = "macos_not_ios", 17 | constraint_values = ["@bazel_tools//platforms:osx"], 18 | ) 19 | 20 | config_setting( 21 | name = "windows", 22 | constraint_values = ["@bazel_tools//platforms:windows"], 23 | ) 24 | 25 | config_setting( 26 | name = "windows_debug", 27 | constraint_values = ["@bazel_tools//platforms:windows"], 28 | values = { 29 | "compilation_mode": "dbg", 30 | }, 31 | ) 32 | 33 | config_setting( 34 | name = "windows_release", 35 | constraint_values = ["@bazel_tools//platforms:windows"], 36 | values = { 37 | "compilation_mode": "opt", 38 | }, 39 | ) 40 | 41 | config_setting( 42 | name = "windows-x86_64", 43 | constraint_values = ["@bazel_tools//platforms:windows"], 44 | ) 45 | 46 | # Buildifier 47 | 48 | sh_binary( 49 | name = "buildifier", 50 | srcs = select( 51 | { 52 | ":linux": ["@buildifier_linux//file"], 53 | ":macos": ["@buildifier_macos//file"], 54 | ":windows": ["@buildifier_windows//file"], 55 | }, 56 | ), 57 | ) 58 | 59 | # Aspect-based clang-format 60 | 61 | filegroup( 62 | name = "dot_clang_format", 63 | srcs = [".clang-format"], 64 | ) 65 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) 5 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | Nothing yet. 9 | 10 | ## [1.1.1] - 2022-01-30 11 | ### Changed 12 | - Replaced size() in filters with DIM [#26](https://github.com/improbable-eng/phtree-cpp/pull/26) 13 | 14 | ## [1.1.0] - 2022-01-25 15 | ### Added 16 | - FilterSphere for filtering by sphere constraint (by ctbur) [#16](https://github.com/improbable-eng/phtree-cpp/pull/16) 17 | - IEEE converter for 32bit float, see `distance.h` (by ctbur) [#18](https://github.com/improbable-eng/phtree-cpp/pull/18) 18 | 19 | ### Changed 20 | - Performance improvement for updates and queries: removed use of `std::variant`. [#23](https://github.com/improbable-eng/phtree-cpp/pull/23) 21 | - Fixed imports `` -> `` (by ctbur) [#15](https://github.com/improbable-eng/phtree-cpp/pull/15) 22 | - Cleaned up build scripts [#21](https://github.com/improbable-eng/phtree-cpp/pull/21) 23 | - Fixed warnings: [#20](https://github.com/improbable-eng/phtree-cpp/pull/20) 24 | - "unused function argument" warnings 25 | - gcc/clang warnings 26 | - MSVC warnings 27 | - reserved identifier warnings (identifiers starting with `_`) 28 | - typos in README.md [#22](https://github.com/improbable-eng/phtree-cpp/pull/22) 29 | 30 | ## [1.0.1] - 2021-05-06 31 | ### Changed 32 | - replaced compilation flag `-fpermissive` with `-Werror`, and fixed all warnings/errors, see issue #10 33 | 34 | ## [1.0.0] - 2021-03-23 35 | ### Added 36 | - API: `MultiMap`: A wrapper that makes PH-Tree behave as a multi-map. 37 | - API: `erase(iterator)` 38 | - API: `emplace_hint(iterator, ...)` 39 | - API for `PhTreeF` and `PhTreeBoxF`: 32bit floating point options 40 | - Support for custom key classes 41 | 42 | ### Changed 43 | - BREAKING CHANGE: The query functions now require a query box as input (instead of a min/max point pair) 44 | - BREAKING CHANGE: `phtree_box_d.h` has been removed, please use `phtree.h instead. 45 | - BREAKING CHANGE: `phtree_d.h` has been removed, please use `phtree.h` instead. 46 | - BREAKING CHANGE: Data converters (IEEE, Multiply, etc) are now structs i.o. functions/functors 47 | - BREAKING CHANGE: `PhFilterNoOp` has been renamed to `FilterNoOp` 48 | - BREAKING CHANGE: kNN queries now always require the distance function to be specified. 49 | - BREAKING CHANGE: Preprocessors have been refactored and renamed to Converter/ScalarConverter 50 | - Moved CI builds from Travis to GitHub actions 51 | 52 | ### Removed 53 | - Nothing. 54 | 55 | ### Fixed 56 | - GCC warnings from `-Wsign-compare` and `-Wsequence-point`. 57 | 58 | 59 | ## 0.1.0 - 2020-07-02 60 | ### Added 61 | - Initial version. 62 | 63 | ### Changed 64 | - Nothing. 65 | 66 | ### Removed 67 | - Nothing. 68 | 69 | ### Fixed 70 | - Nothing. 71 | 72 | 73 | [Unreleased]: https://github.com/improbable-eng/phtree-cpp/compare/v1.1.1...HEAD 74 | [1.1.1]: https://github.com/improbable-eng/phtree-cpp/compare/v1.1.0...v1.1.1 75 | [1.1.0]: https://github.com/improbable-eng/phtree-cpp/compare/v1.0.0...v1.1.0 76 | [1.0.1]: https://github.com/improbable-eng/phtree-cpp/compare/v1.0.0...v1.0.1 77 | [1.0.0]: https://github.com/improbable-eng/phtree-cpp/compare/v0.1.0...v1.0.0 78 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | 3 | # set the project name 4 | project(PH_Tree_Main VERSION 1.1.1 5 | DESCRIPTION "PH-Tree C++" 6 | LANGUAGES CXX) 7 | 8 | if(NOT CMAKE_BUILD_TYPE) 9 | set(CMAKE_BUILD_TYPE Release) 10 | endif() 11 | 12 | # specify the C++ standard 13 | set(CMAKE_CXX_STANDARD 17) 14 | set(CMAKE_CXX_STANDARD_REQUIRED True) 15 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -Wall -Werror") 16 | set(CMAKE_CXX_FLAGS_RELEASE "-O3") 17 | 18 | add_subdirectory(phtree) 19 | add_subdirectory(examples) 20 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /WORKSPACE: -------------------------------------------------------------------------------- 1 | # Bazel bootstrapping 2 | 3 | load("//tools/build_rules:http.bzl", "http_archive", "http_file") 4 | 5 | http_archive( 6 | name = "bazel_skylib", 7 | sha256 = "1dde365491125a3db70731e25658dfdd3bc5dbdfd11b840b3e987ecf043c7ca0", 8 | url = "https://github.com/bazelbuild/bazel-skylib/releases/download/0.9.0/bazel_skylib-0.9.0.tar.gz", 9 | ) 10 | 11 | load("@bazel_skylib//lib:versions.bzl", "versions") 12 | 13 | versions.check( 14 | minimum_bazel_version = "4.2.2", 15 | maximum_bazel_version = "4.2.2", 16 | ) 17 | 18 | # NOTE: We make third_party/ its own bazel workspace because it allows to run `bazel build ...` without 19 | # having all targets defined in third-party BUILD files in that directory buildable. 20 | local_repository( 21 | name = "third_party", 22 | path = "third_party", 23 | ) 24 | 25 | # External PH-Tree dependencies 26 | 27 | http_archive( 28 | name = "spdlog", 29 | build_file = "@third_party//spdlog:BUILD", 30 | sha256 = "b38e0bbef7faac2b82fed550a0c19b0d4e7f6737d5321d4fd8f216b80f8aee8a", 31 | strip_prefix = "spdlog-1.5.0", 32 | url = "https://github.com/gabime/spdlog/archive/v1.5.0.tar.gz", 33 | ) 34 | 35 | http_archive( 36 | name = "gbenchmark", 37 | sha256 = "dccbdab796baa1043f04982147e67bb6e118fe610da2c65f88912d73987e700c", 38 | strip_prefix = "benchmark-1.5.2", 39 | url = "https://github.com/google/benchmark/archive/v1.5.2.tar.gz", 40 | ) 41 | 42 | http_archive( 43 | name = "gtest", 44 | build_file = "@third_party//gtest:BUILD", 45 | sha256 = "9dc9157a9a1551ec7a7e43daea9a694a0bb5fb8bec81235d8a1e6ef64c716dcb", 46 | strip_prefix = "googletest-release-1.10.0", 47 | url = "https://github.com/google/googletest/archive/release-1.10.0.tar.gz", 48 | ) 49 | 50 | # Development environment tooling 51 | 52 | BUILDIFIER_VERSION = "0.29.0" 53 | 54 | http_file( 55 | name = "buildifier_linux", 56 | executable = True, 57 | sha256 = "4c985c883eafdde9c0e8cf3c8595b8bfdf32e77571c369bf8ddae83b042028d6", 58 | urls = ["https://github.com/bazelbuild/buildtools/releases/download/{version}/buildifier".format(version = BUILDIFIER_VERSION)], 59 | ) 60 | 61 | http_file( 62 | name = "buildifier_macos", 63 | executable = True, 64 | sha256 = "9b108decaa9a624fbac65285e529994088c5d15fecc1a30866afc03a48619245", 65 | urls = ["https://github.com/bazelbuild/buildtools/releases/download/{version}/buildifier.mac".format(version = BUILDIFIER_VERSION)], 66 | ) 67 | 68 | http_file( 69 | name = "buildifier_windows", 70 | executable = True, 71 | sha256 = "dc5d6ed5e3e0dbe9955f7606939c627af5a2be7f9bdd8814e77a22109164394f", 72 | urls = ["https://github.com/bazelbuild/buildtools/releases/download/{version}/buildifier.exe".format(version = BUILDIFIER_VERSION)], 73 | ) 74 | 75 | http_archive( 76 | name = "bazel_compilation_database", 77 | sha256 = "bb1b812396e2ee36a50a13b03ae6833173ce643e8a4bd50731067d0b4e5c6e86", 78 | strip_prefix = "bazel-compilation-database-0.3.5", 79 | url = "https://github.com/grailbio/bazel-compilation-database/archive/0.3.5.tar.gz", 80 | ) 81 | -------------------------------------------------------------------------------- /ci/includes/bazel.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | source ci/includes/os.sh 4 | 5 | # Main function that should be used by scripts sourcing this file. 6 | function runBazel() { 7 | BAZEL_SUBCOMMAND="$1" 8 | shift 9 | "$(pwd)/tools/bazel" "$BAZEL_SUBCOMMAND" ${BAZEL_CI_CONFIG:-} "$@" 10 | } 11 | 12 | function getBazelVersion() { 13 | echo "4.2.2" 14 | } 15 | -------------------------------------------------------------------------------- /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/bazel.sh 8 | source ci/includes/os.sh 9 | 10 | MAYBEARG='-mode=check' 11 | if [ $# -eq 1 ]; then 12 | if [ "$1" = "-fix" ]; then 13 | echo -e "\033[0;34mAttempting to fix linting errors automatically as '-fix' is specified.\033[0m" 14 | MAYBEARG='' 15 | fi 16 | fi 17 | 18 | # Ensure Bazel is installed. 19 | runBazel version 20 | 21 | if runBazel run buildifier -- ${MAYBEARG} -v $(find "$(pwd)/" \( -name BUILD -o -name WORKSPACE \) -type f); then 22 | echo -e "\033[0;32mAll BUILD and WORKSPACE files passed buildifier linting check.\033[0m" 23 | else 24 | echo -e "\033[0;31mThe above listed BUILD and WORKSPACE file(s) didn't pass the buildifier linting check!\033[0m" 25 | echo -e "\033[0;34mYou can run 'ci/linting/buildifier.sh -fix' to fix them automatically.\033[0m" 26 | exit 1 27 | fi 28 | 29 | -------------------------------------------------------------------------------- /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 | source ci/includes/bazel.sh 7 | 8 | TARGETS="//..." 9 | EXCLUDED_TARGETS="" 10 | 11 | # NOTE: We need to set the 'MSYS_ARG_CONV_EXCL' environment variable in various places, because 12 | # otherwise on Windows, MSYS2 does weird things with Bazel target paths (see 13 | # http://www.mingw.org/wiki/Posix_path_conversion). 14 | export MSYS2_ARG_CONV_EXCL="//..." 15 | 16 | # Function to join arrays with a specified string. 17 | # Taken from: https://stackoverflow.com/questions/1527049/how-can-i-join-elements-of-an-array-in-bash#comment37571340_17841619 18 | function joinBy { perl -e '$s = shift @ARGV; print join($s, @ARGV);' "$@"; } 19 | 20 | function clangFormatLocation() { 21 | local CLANG_FORMAT_VERSION=10.0.0 22 | # Use find to get the path for either clang-format (macOS / Linux) or clang-format.exe (Windows) 23 | local CLANG_FORMAT_EXE=clang-format 24 | local CLANG_FORMAT_SHIM=clang-format 25 | if isWindows; then 26 | CLANG_FORMAT_EXE=clang-format.exe 27 | fi 28 | 29 | CLANG_FORMAT_VERSION_CALL="$("${CLANG_FORMAT_SHIM}" -version)" # Ensures that the binary is downloaded. 30 | 31 | local CLANG_FORMAT=CLANG_FORMAT_EXE 32 | if [[ ${CLANG_FORMAT} == "" ]]; then 33 | echo "ERROR: could not locate clang-format" 34 | echo "clang-format -version: ${CLANG_FORMAT_VERSION_CALL}" 35 | echo "which clang-format: $(which clang-format)" 36 | exit 1 37 | fi 38 | echo "${CLANG_FORMAT}" 39 | } 40 | 41 | # Generates a string of the form `... -...` 42 | function generateBuildTargetString() { 43 | TARGET_STRING="${TARGETS}" 44 | 45 | # Append target exclusions only if there are excluded targets. 46 | if ! [[ -z "${EXCLUDED_TARGETS}" ]]; then 47 | TARGET_STRING="${TARGET_STRING} -$(joinBy " -" ${EXCLUDED_TARGETS})" 48 | fi 49 | 50 | echo "${TARGET_STRING}" 51 | } 52 | 53 | # Generates a string of the form ` [union ]* except ( [union ]*)` 54 | # i.e. worker_sdk/... union applications except (worker_sdk/common:some_target union worker_sdk/common:some_other_target) 55 | function generateAqueryTargetString() { 56 | TARGET_STRING="$(joinBy " union " ${TARGETS})" 57 | 58 | # Append target exclusions only if there are excluded targets. 59 | if ! [[ -z "${EXCLUDED_TARGETS}" ]]; then 60 | TARGET_STRING="${TARGET_STRING} except ($(joinBy " union " ${EXCLUDED_TARGETS}))" 61 | fi 62 | 63 | echo "${TARGET_STRING}" 64 | } 65 | 66 | function bazelLintTest() { 67 | # Use bazel to create patch files for all eligible source files. 68 | # Fail if any of the patch files are non-empty (i.e. lint was detected). 69 | CLANG_FORMAT="$(clangFormatLocation)" runBazel build --config lint --output_groups=clang_format_test -- $(generateBuildTargetString) 70 | } 71 | 72 | function bazelLintFix() { 73 | # Use bazel to create patch files for all eligible source files. 74 | CLANG_FORMAT="$(clangFormatLocation)" runBazel build --config lint --output_groups=clang_format_patches_only -- $(generateBuildTargetString) 75 | 76 | # Find bazel-bin prefix. 77 | BAZEL_BIN=$(runBazel info bazel-bin) 78 | # I.e. on Linux, this is `bazel-out/k8-gcc-opt/bin`. 79 | PREFIX=${BAZEL_BIN#$(runBazel info execution_root)/} 80 | 81 | # Use aquery to get the list of output files of the `CreatePatch` action, 82 | # Then strip the patch path down to that of its source file, and apply 83 | # the patch file generated by Bazel to the original source file. 84 | CLANG_FORMAT="$(clangFormatLocation)" runBazel aquery --config lint --include_aspects --output_groups clang_format_patches_only "mnemonic(\"CreatePatch\", $(generateAqueryTargetString))" --output textproto \ 85 | `# Get relative paths to source files` \ 86 | `# perl used instead of grep --perl-regexp since grep macOS doesnt support it` \ 87 | | perl -ne "while(/(?<=exec_path: \"${PREFIX//\//\\/}\/).*\.patch_.+(?=\")/g){print \"\$&\n\";}" \ 88 | `# Create the patch commands which, when executed patch the source files.` \ 89 | `# --binary flag used to correctly handle line endings on Windows.` \ 90 | | xargs -L1 -I 'PATH_TO_SOURCE_FILE' echo 'FILE_PATH="PATH_TO_SOURCE_FILE"; echo patch --binary "${FILE_PATH%.patch_*}" "'${BAZEL_BIN}'/${FILE_PATH}"' \ 91 | `# De-duplicate the patch commands, such that there is at most one patch command for each source file.` \ 92 | `# There are N patch files per source files, where N is the number of bazel targets directly including the target.` \ 93 | `# The format of the commands being de-duplicated is: patch --binary -source file- -patch file-` \ 94 | | sh `# Calls the echo command generated by xargs, resulting in the patch command to be run by the next sh invocation below`\ 95 | | sort --unique --field-separator=" " --key=3,3 `# Remove duplicate patch commands to the same source file` \ 96 | | sh `# Execute patch commands` 97 | } 98 | 99 | MAYBEARG='-mode=check' 100 | if [ $# -eq 1 ]; then 101 | if [ "$1" = "-fix" ]; then 102 | MAYBEARG='' 103 | fi 104 | fi 105 | 106 | if [[ -z "$MAYBEARG" ]]; then 107 | echo -e "\033[0;34mAttempting to fix linting errors automatically as '-fix' is specified.\033[0m" 108 | bazelLintFix 109 | elif bazelLintTest; then 110 | echo -e "\033[0;32mAll source files passed clang-format linting check.\033[0m" 111 | else 112 | echo -e "\033[0;31mThe above listed source file(s) didn't pass the clang-format linting check!\033[0m" 113 | echo -e "\033[0;34mYou can run 'ci/linting/clang-format.sh -fix' to fix them automatically.\033[0m" 114 | exit 1 115 | fi 116 | -------------------------------------------------------------------------------- /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(Example) 3 | 4 | set(SOURCE_FILES example.cc) 5 | add_executable(Example ${SOURCE_FILES}) 6 | target_link_libraries(Example phtree) 7 | -------------------------------------------------------------------------------- /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 19 | 20 | using namespace improbable::phtree; 21 | 22 | int main() { 23 | std::cout << "PH-Tree example with 3D `double` coordinates." << std::endl; 24 | PhPointD<3> p1({1, 1, 1}); 25 | PhPointD<3> p2({2, 2, 2}); 26 | PhPointD<3> p3({3, 3, 3}); 27 | PhPointD<3> p4({4, 4, 4}); 28 | 29 | PhTreeD<3, int> tree; 30 | tree.emplace(p1, 1); 31 | tree.emplace(p2, 2); 32 | tree.emplace(p3, 3); 33 | tree.emplace(p4, 4); 34 | 35 | std::cout << "All values:" << std::endl; 36 | for (auto it : tree) { 37 | std::cout << " id=" << it << std::endl; 38 | } 39 | std::cout << std::endl; 40 | 41 | std::cout << "All points in range:" << p2 << "/" << p4 << std::endl; 42 | for (auto it = tree.begin_query({p2, p4}); it != tree.end(); ++it) { 43 | std::cout << " " << it.second() << " -> " << it.first() << std::endl; 44 | } 45 | std::cout << std::endl; 46 | 47 | std::cout << "PH-Tree is a MAP which means that, like std::map, every position " << std::endl; 48 | std::cout << " (=key) can have only ONE value." << std::endl; 49 | std::cout << "Storing multiple values for a single coordinate requires storing " << std::endl; 50 | std::cout << "lists or sets, for example using PhTree<3, std::vector>." << std::endl; 51 | 52 | PhPointD<3> p4b({4, 4, 4}); 53 | tree.emplace(p4b, 5); 54 | // Still showing '4' after emplace() 55 | std::cout << "ID at " << p4b << ": " << tree.find(p4b).second() << std::endl; 56 | 57 | std::cout << "Done." << std::endl; 58 | } -------------------------------------------------------------------------------- /phtree/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:private"]) 2 | 3 | cc_library( 4 | name = "phtree", 5 | srcs = [ 6 | ], 7 | hdrs = [ 8 | "phtree.h", 9 | "phtree_multimap.h", 10 | ], 11 | linkstatic = True, 12 | visibility = [ 13 | "//visibility:public", 14 | ], 15 | deps = [ 16 | "//phtree/v16", 17 | ], 18 | ) 19 | 20 | cc_test( 21 | name = "phtree_test", 22 | timeout = "long", 23 | srcs = [ 24 | "phtree_test.cc", 25 | ], 26 | linkstatic = True, 27 | deps = [ 28 | ":phtree", 29 | "//phtree/testing/gtest_main", 30 | ], 31 | ) 32 | 33 | cc_test( 34 | name = "phtree_test_const_values", 35 | timeout = "long", 36 | srcs = [ 37 | "phtree_test_const_values.cc", 38 | ], 39 | linkstatic = True, 40 | deps = [ 41 | ":phtree", 42 | "//phtree/testing/gtest_main", 43 | ], 44 | ) 45 | 46 | cc_test( 47 | name = "phtree_test_ptr_values", 48 | timeout = "long", 49 | srcs = [ 50 | "phtree_test_ptr_values.cc", 51 | ], 52 | linkstatic = True, 53 | deps = [ 54 | ":phtree", 55 | "//phtree/testing/gtest_main", 56 | ], 57 | ) 58 | 59 | cc_test( 60 | name = "phtree_test_unique_ptr_values", 61 | timeout = "long", 62 | srcs = [ 63 | "phtree_test_unique_ptr_values.cc", 64 | ], 65 | linkstatic = True, 66 | deps = [ 67 | ":phtree", 68 | "//phtree/testing/gtest_main", 69 | ], 70 | ) 71 | 72 | cc_test( 73 | name = "phtree_multimap_test_move_only_values", 74 | timeout = "long", 75 | srcs = [ 76 | "phtree_test_unique_ptr_values.cc", 77 | ], 78 | linkstatic = True, 79 | deps = [ 80 | ":phtree", 81 | "//phtree/testing/gtest_main", 82 | ], 83 | ) 84 | 85 | cc_test( 86 | name = "phtree_d_test", 87 | timeout = "long", 88 | srcs = [ 89 | "phtree_d_test.cc", 90 | ], 91 | linkstatic = True, 92 | deps = [ 93 | ":phtree", 94 | "//phtree/testing/gtest_main", 95 | ], 96 | ) 97 | 98 | cc_test( 99 | name = "phtree_d_test_filter", 100 | timeout = "long", 101 | srcs = [ 102 | "phtree_d_test_filter.cc", 103 | ], 104 | linkstatic = True, 105 | deps = [ 106 | ":phtree", 107 | "//phtree/testing/gtest_main", 108 | ], 109 | ) 110 | 111 | cc_test( 112 | name = "phtree_d_test_custom_key", 113 | timeout = "long", 114 | srcs = [ 115 | "phtree_d_test_custom_key.cc", 116 | ], 117 | linkstatic = True, 118 | deps = [ 119 | ":phtree", 120 | "//phtree/testing/gtest_main", 121 | ], 122 | ) 123 | 124 | cc_test( 125 | name = "phtree_d_test_preprocessor", 126 | timeout = "long", 127 | srcs = [ 128 | "phtree_d_test_preprocessor.cc", 129 | ], 130 | linkstatic = True, 131 | deps = [ 132 | ":phtree", 133 | "//phtree/testing/gtest_main", 134 | ], 135 | ) 136 | 137 | cc_test( 138 | name = "phtree_multimap_d_test", 139 | timeout = "long", 140 | srcs = [ 141 | "phtree_multimap_d_test.cc", 142 | ], 143 | linkstatic = True, 144 | deps = [ 145 | ":phtree", 146 | "//phtree/testing/gtest_main", 147 | ], 148 | ) 149 | 150 | cc_test( 151 | name = "phtree_box_d_test_query_types", 152 | timeout = "long", 153 | srcs = [ 154 | "phtree_box_d_test_query_types.cc", 155 | ], 156 | linkstatic = True, 157 | deps = [ 158 | ":phtree", 159 | "//phtree/testing/gtest_main", 160 | ], 161 | ) 162 | 163 | cc_test( 164 | name = "phtree_box_d_test", 165 | timeout = "long", 166 | srcs = [ 167 | "phtree_box_d_test.cc", 168 | ], 169 | linkstatic = True, 170 | deps = [ 171 | ":phtree", 172 | "//phtree/testing/gtest_main", 173 | ], 174 | ) 175 | 176 | cc_test( 177 | name = "phtree_multimap_box_d_test", 178 | timeout = "long", 179 | srcs = [ 180 | "phtree_multimap_box_d_test.cc", 181 | ], 182 | linkstatic = True, 183 | deps = [ 184 | ":phtree", 185 | "//phtree/testing/gtest_main", 186 | ], 187 | ) 188 | 189 | cc_test( 190 | name = "phtree_f_test", 191 | timeout = "long", 192 | srcs = [ 193 | "phtree_f_test.cc", 194 | ], 195 | linkstatic = True, 196 | deps = [ 197 | ":phtree", 198 | "//phtree/testing/gtest_main", 199 | ], 200 | ) 201 | 202 | cc_test( 203 | name = "phtree_box_f_test", 204 | timeout = "long", 205 | srcs = [ 206 | "phtree_box_f_test.cc", 207 | ], 208 | linkstatic = True, 209 | deps = [ 210 | ":phtree", 211 | "//phtree/testing/gtest_main", 212 | ], 213 | ) 214 | -------------------------------------------------------------------------------- /phtree/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | project(phtree) 3 | 4 | add_library(phtree STATIC "") 5 | add_subdirectory(common) 6 | add_subdirectory(v16) 7 | 8 | set_target_properties(phtree PROPERTIES LINKER_LANGUAGE CXX) 9 | 10 | -------------------------------------------------------------------------------- /phtree/benchmark/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:private"]) 2 | 3 | cc_library( 4 | name = "benchmark", 5 | testonly = True, 6 | srcs = [ 7 | "logging.cc", 8 | ], 9 | hdrs = [ 10 | "benchmark_util.h", 11 | "logging.h", 12 | ], 13 | visibility = [ 14 | "//visibility:public", 15 | ], 16 | deps = [ 17 | "@gbenchmark//:benchmark", 18 | "@spdlog", 19 | ], 20 | alwayslink = 1, 21 | ) 22 | 23 | cc_binary( 24 | name = "count_mm_d_benchmark", 25 | testonly = True, 26 | srcs = [ 27 | "count_mm_d_benchmark.cc", 28 | ], 29 | linkstatic = True, 30 | deps = [ 31 | "//phtree", 32 | "//phtree/benchmark", 33 | "@gbenchmark//:benchmark", 34 | "@spdlog", 35 | ], 36 | ) 37 | 38 | cc_binary( 39 | name = "erase_benchmark", 40 | testonly = True, 41 | srcs = [ 42 | "erase_benchmark.cc", 43 | ], 44 | linkstatic = True, 45 | deps = [ 46 | "//phtree", 47 | "//phtree/benchmark", 48 | "@gbenchmark//:benchmark", 49 | "@spdlog", 50 | ], 51 | ) 52 | 53 | cc_binary( 54 | name = "erase_d_benchmark", 55 | testonly = True, 56 | srcs = [ 57 | "erase_d_benchmark.cc", 58 | ], 59 | linkstatic = True, 60 | deps = [ 61 | "//phtree", 62 | "//phtree/benchmark", 63 | "@gbenchmark//:benchmark", 64 | "@spdlog", 65 | ], 66 | ) 67 | 68 | cc_binary( 69 | name = "extent_benchmark", 70 | testonly = True, 71 | srcs = [ 72 | "extent_benchmark.cc", 73 | ], 74 | linkstatic = True, 75 | deps = [ 76 | "//phtree", 77 | "//phtree/benchmark", 78 | "@gbenchmark//:benchmark", 79 | "@spdlog", 80 | ], 81 | ) 82 | 83 | cc_binary( 84 | name = "extent_benchmark_weird", 85 | testonly = True, 86 | srcs = [ 87 | "extent_benchmark_weird.cc", 88 | ], 89 | linkstatic = True, 90 | deps = [ 91 | "//phtree", 92 | "//phtree/benchmark", 93 | "@gbenchmark//:benchmark", 94 | "@spdlog", 95 | ], 96 | ) 97 | 98 | cc_binary( 99 | name = "find_benchmark", 100 | testonly = True, 101 | srcs = [ 102 | "find_benchmark.cc", 103 | ], 104 | linkstatic = True, 105 | deps = [ 106 | "//phtree", 107 | "//phtree/benchmark", 108 | "@gbenchmark//:benchmark", 109 | "@spdlog", 110 | ], 111 | ) 112 | 113 | cc_binary( 114 | name = "insert_benchmark", 115 | testonly = True, 116 | srcs = [ 117 | "insert_benchmark.cc", 118 | ], 119 | linkstatic = True, 120 | deps = [ 121 | "//phtree", 122 | "//phtree/benchmark", 123 | "@gbenchmark//:benchmark", 124 | "@spdlog", 125 | ], 126 | ) 127 | 128 | cc_binary( 129 | name = "insert_d_benchmark", 130 | testonly = True, 131 | srcs = [ 132 | "insert_d_benchmark.cc", 133 | ], 134 | linkstatic = True, 135 | deps = [ 136 | "//phtree", 137 | "//phtree/benchmark", 138 | "@gbenchmark//:benchmark", 139 | "@spdlog", 140 | ], 141 | ) 142 | 143 | cc_binary( 144 | name = "insert_box_d_benchmark", 145 | testonly = True, 146 | srcs = [ 147 | "insert_box_d_benchmark.cc", 148 | ], 149 | linkstatic = True, 150 | deps = [ 151 | "//phtree", 152 | "//phtree/benchmark", 153 | "@gbenchmark//:benchmark", 154 | "@spdlog", 155 | ], 156 | ) 157 | 158 | cc_binary( 159 | name = "knn_d_benchmark", 160 | testonly = True, 161 | srcs = [ 162 | "knn_d_benchmark.cc", 163 | ], 164 | linkstatic = True, 165 | deps = [ 166 | "//phtree", 167 | "//phtree/benchmark", 168 | "@gbenchmark//:benchmark", 169 | "@spdlog", 170 | ], 171 | ) 172 | 173 | cc_binary( 174 | name = "query_benchmark", 175 | testonly = True, 176 | srcs = [ 177 | "query_benchmark.cc", 178 | ], 179 | linkstatic = True, 180 | deps = [ 181 | "//phtree", 182 | "//phtree/benchmark", 183 | "@gbenchmark//:benchmark", 184 | "@spdlog", 185 | ], 186 | ) 187 | 188 | cc_binary( 189 | name = "query_box_d_benchmark", 190 | testonly = True, 191 | srcs = [ 192 | "query_box_d_benchmark.cc", 193 | ], 194 | linkstatic = True, 195 | deps = [ 196 | "//phtree", 197 | "//phtree/benchmark", 198 | "@gbenchmark//:benchmark", 199 | "@spdlog", 200 | ], 201 | ) 202 | 203 | cc_binary( 204 | name = "query_d_benchmark", 205 | testonly = True, 206 | srcs = [ 207 | "query_d_benchmark.cc", 208 | ], 209 | linkstatic = True, 210 | deps = [ 211 | "//phtree", 212 | "//phtree/benchmark", 213 | "@gbenchmark//:benchmark", 214 | "@spdlog", 215 | ], 216 | ) 217 | 218 | cc_binary( 219 | name = "query_mm_d_benchmark", 220 | testonly = True, 221 | srcs = [ 222 | "query_mm_d_benchmark.cc", 223 | ], 224 | linkstatic = True, 225 | deps = [ 226 | "//phtree", 227 | "//phtree/benchmark", 228 | "@gbenchmark//:benchmark", 229 | "@spdlog", 230 | ], 231 | ) 232 | 233 | cc_binary( 234 | name = "query_mm_box_d_benchmark", 235 | testonly = True, 236 | srcs = [ 237 | "query_mm_box_d_benchmark.cc", 238 | ], 239 | linkstatic = True, 240 | deps = [ 241 | "//phtree", 242 | "//phtree/benchmark", 243 | "@gbenchmark//:benchmark", 244 | "@spdlog", 245 | ], 246 | ) 247 | 248 | cc_binary( 249 | name = "update_d_benchmark", 250 | testonly = True, 251 | srcs = [ 252 | "update_d_benchmark.cc", 253 | ], 254 | linkstatic = True, 255 | deps = [ 256 | "//phtree", 257 | "//phtree/benchmark", 258 | "@gbenchmark//:benchmark", 259 | "@spdlog", 260 | ], 261 | ) 262 | 263 | cc_binary( 264 | name = "update_mm_d_benchmark", 265 | testonly = True, 266 | srcs = [ 267 | "update_mm_d_benchmark.cc", 268 | ], 269 | linkstatic = True, 270 | deps = [ 271 | "//phtree", 272 | "//phtree/benchmark", 273 | "@gbenchmark//:benchmark", 274 | "@spdlog", 275 | ], 276 | ) 277 | 278 | cc_binary( 279 | name = "update_mm_box_d_benchmark", 280 | testonly = True, 281 | srcs = [ 282 | "update_mm_box_d_benchmark.cc", 283 | ], 284 | linkstatic = True, 285 | deps = [ 286 | "//phtree", 287 | "//phtree/benchmark", 288 | "@gbenchmark//:benchmark", 289 | "@spdlog", 290 | ], 291 | ) 292 | 293 | cc_binary( 294 | name = "update_box_d_benchmark", 295 | testonly = True, 296 | srcs = [ 297 | "update_box_d_benchmark.cc", 298 | ], 299 | linkstatic = True, 300 | deps = [ 301 | "//phtree", 302 | "//phtree/benchmark", 303 | "@gbenchmark//:benchmark", 304 | "@spdlog", 305 | ], 306 | ) 307 | -------------------------------------------------------------------------------- /phtree/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 "logging.h" 17 | #include "phtree/benchmark/benchmark_util.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 removing entries. 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 Insert(benchmark::State& state, PhTree& tree); 43 | void Remove(benchmark::State& state, PhTree& tree); 44 | 45 | const TestGenerator data_type_; 46 | const int num_entities_; 47 | 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 | state.PauseTiming(); 69 | auto* tree = new PhTree(); 70 | Insert(state, *tree); 71 | state.ResumeTiming(); 72 | 73 | Remove(state, *tree); 74 | 75 | state.PauseTiming(); 76 | // avoid measuring deallocation 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_remove_count"] = benchmark::Counter(0); 88 | state.counters["remove_rate"] = benchmark::Counter(0, benchmark::Counter::kIsRate); 89 | 90 | logging::info("World setup complete."); 91 | } 92 | 93 | template 94 | void IndexBenchmark::Insert(benchmark::State&, PhTree& tree) { 95 | for (int i = 0; i < num_entities_; ++i) { 96 | tree.emplace(points_[i], i); 97 | } 98 | } 99 | 100 | template 101 | void IndexBenchmark::Remove(benchmark::State& state, PhTree& tree) { 102 | int n = 0; 103 | for (int i = 0; i < num_entities_; ++i) { 104 | n += tree.erase(points_[i]); 105 | } 106 | 107 | state.counters["total_remove_count"] += n; 108 | state.counters["remove_rate"] += n; 109 | } 110 | 111 | } // namespace 112 | 113 | template 114 | void PhTree3D(benchmark::State& state, Arguments&&... arguments) { 115 | IndexBenchmark<3> benchmark{state, arguments...}; 116 | benchmark.Benchmark(state); 117 | } 118 | 119 | // index type, scenario name, data_type, num_entities 120 | // PhTree 3D CUBE 121 | BENCHMARK_CAPTURE(PhTree3D, REM_CU_1K, TestGenerator::CUBE, 1000)->Unit(benchmark::kMillisecond); 122 | 123 | BENCHMARK_CAPTURE(PhTree3D, REM_CU_10K, TestGenerator::CUBE, 10000)->Unit(benchmark::kMillisecond); 124 | 125 | BENCHMARK_CAPTURE(PhTree3D, REM_CU_100K, TestGenerator::CUBE, 100000) 126 | ->Unit(benchmark::kMillisecond); 127 | 128 | BENCHMARK_CAPTURE(PhTree3D, REM_CU_1M, TestGenerator::CUBE, 1000000)->Unit(benchmark::kMillisecond); 129 | 130 | BENCHMARK_CAPTURE(PhTree3D, REM_CU_10M, TestGenerator::CUBE, 10000000) 131 | ->Unit(benchmark::kMillisecond); 132 | 133 | // PhTree 3D CLUSTER 134 | BENCHMARK_CAPTURE(PhTree3D, REM_CL_1K, TestGenerator::CLUSTER, 1000)->Unit(benchmark::kMillisecond); 135 | 136 | BENCHMARK_CAPTURE(PhTree3D, REM_CL_10K, TestGenerator::CLUSTER, 10000) 137 | ->Unit(benchmark::kMillisecond); 138 | 139 | BENCHMARK_CAPTURE(PhTree3D, REM_CL_100K, TestGenerator::CLUSTER, 100000) 140 | ->Unit(benchmark::kMillisecond); 141 | 142 | BENCHMARK_CAPTURE(PhTree3D, REM_CL_1M, TestGenerator::CLUSTER, 1000000) 143 | ->Unit(benchmark::kMillisecond); 144 | 145 | BENCHMARK_CAPTURE(PhTree3D, REM_CL_10M, TestGenerator::CLUSTER, 10000000) 146 | ->Unit(benchmark::kMillisecond); 147 | 148 | BENCHMARK_MAIN(); 149 | -------------------------------------------------------------------------------- /phtree/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 "logging.h" 17 | #include "phtree/benchmark/benchmark_util.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 removing entries. 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 Insert(benchmark::State& state, PhTreeD& tree); 43 | void Remove(benchmark::State& state, PhTreeD& tree); 44 | 45 | const TestGenerator data_type_; 46 | const int 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( 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 | state.PauseTiming(); 69 | auto* tree = new PhTreeD(); 70 | Insert(state, *tree); 71 | state.ResumeTiming(); 72 | 73 | Remove(state, *tree); 74 | 75 | state.PauseTiming(); 76 | // avoid measuring deallocation 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_remove_count"] = benchmark::Counter(0); 88 | state.counters["remove_rate"] = benchmark::Counter(0, benchmark::Counter::kIsRate); 89 | 90 | logging::info("World setup complete."); 91 | } 92 | 93 | template 94 | void IndexBenchmark::Insert(benchmark::State&, PhTreeD& tree) { 95 | for (int i = 0; i < num_entities_; ++i) { 96 | tree.emplace(points_[i], i); 97 | } 98 | } 99 | 100 | template 101 | void IndexBenchmark::Remove(benchmark::State& state, PhTreeD& tree) { 102 | int n = 0; 103 | for (int i = 0; i < num_entities_; ++i) { 104 | n += tree.erase(points_[i]); 105 | } 106 | 107 | state.counters["total_remove_count"] += n; 108 | state.counters["remove_rate"] += n; 109 | } 110 | 111 | } // namespace 112 | 113 | template 114 | void PhTree3D(benchmark::State& state, Arguments&&... arguments) { 115 | IndexBenchmark<3> benchmark{state, arguments...}; 116 | benchmark.Benchmark(state); 117 | } 118 | 119 | // index type, scenario name, data_generator, num_entities 120 | // PhTree 3D CUBE 121 | BENCHMARK_CAPTURE(PhTree3D, REM_CU_1K, TestGenerator::CUBE, 1000)->Unit(benchmark::kMillisecond); 122 | 123 | BENCHMARK_CAPTURE(PhTree3D, REM_CU_10K, TestGenerator::CUBE, 10000)->Unit(benchmark::kMillisecond); 124 | 125 | BENCHMARK_CAPTURE(PhTree3D, REM_CU_100K, TestGenerator::CUBE, 100000) 126 | ->Unit(benchmark::kMillisecond); 127 | 128 | BENCHMARK_CAPTURE(PhTree3D, REM_CU_1M, TestGenerator::CUBE, 1000000)->Unit(benchmark::kMillisecond); 129 | 130 | BENCHMARK_CAPTURE(PhTree3D, REM_CU_10M, TestGenerator::CUBE, 10000000) 131 | ->Unit(benchmark::kMillisecond); 132 | 133 | // index type, scenario name, data_generator, num_entities 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 | -------------------------------------------------------------------------------- /phtree/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 "logging.h" 17 | #include "phtree/benchmark/benchmark_util.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 int 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 (int i = 0; i < num_entities_; ++i) { 77 | tree_.emplace(points_[i], 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 | -------------------------------------------------------------------------------- /phtree/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 "logging.h" 17 | #include "phtree/benchmark/benchmark_util.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 | public: 41 | IndexBenchmark( 42 | benchmark::State& state, 43 | TestGenerator data_type, 44 | int num_entities, 45 | InsertionType insertionType); 46 | 47 | void Benchmark(benchmark::State& state); 48 | 49 | private: 50 | void SetupWorld(benchmark::State& state); 51 | 52 | void Insert(benchmark::State& state, PhTree& tree); 53 | 54 | const TestGenerator data_type_; 55 | const int num_entities_; 56 | const InsertionType insertion_type_; 57 | std::vector> points_; 58 | }; 59 | 60 | template 61 | IndexBenchmark::IndexBenchmark( 62 | benchmark::State& state, TestGenerator data_type, int num_entities, InsertionType insertionType) 63 | : data_type_{data_type} 64 | , num_entities_(num_entities) 65 | , insertion_type_(insertionType) 66 | , points_(num_entities) { 67 | logging::SetupDefaultLogging(); 68 | SetupWorld(state); 69 | } 70 | 71 | template 72 | void IndexBenchmark::Benchmark(benchmark::State& state) { 73 | for (auto _ : state) { 74 | state.PauseTiming(); 75 | auto* tree = new PhTree(); 76 | state.ResumeTiming(); 77 | 78 | Insert(state, *tree); 79 | 80 | // we do this top avoid measuring deallocation 81 | state.PauseTiming(); 82 | delete tree; 83 | state.ResumeTiming(); 84 | } 85 | } 86 | 87 | template 88 | void IndexBenchmark::SetupWorld(benchmark::State& state) { 89 | logging::info("Setting up world with {} entities and {} dimensions.", num_entities_, DIM); 90 | CreatePointData(points_, data_type_, num_entities_, 0, GLOBAL_MAX); 91 | 92 | state.counters["total_put_count"] = benchmark::Counter(0); 93 | state.counters["put_rate"] = benchmark::Counter(0, benchmark::Counter::kIsRate); 94 | 95 | logging::info("World setup complete."); 96 | } 97 | 98 | template 99 | void IndexBenchmark::Insert(benchmark::State& state, PhTree& tree) { 100 | switch (insertion_type_) { 101 | case INSERT: { 102 | for (int i = 0; i < num_entities_; ++i) { 103 | tree.insert(points_[i], i); 104 | } 105 | break; 106 | } 107 | case EMPLACE: { 108 | for (int i = 0; i < num_entities_; ++i) { 109 | tree.emplace(points_[i], i); 110 | } 111 | break; 112 | } 113 | case SQUARE_BR: { 114 | for (int i = 0; i < num_entities_; ++i) { 115 | tree[points_[i]] = i; 116 | } 117 | break; 118 | } 119 | } 120 | 121 | state.counters["total_put_count"] += num_entities_; 122 | state.counters["put_rate"] += num_entities_; 123 | } 124 | 125 | } // namespace 126 | 127 | template 128 | void PhTree3D(benchmark::State& state, Arguments&&... arguments) { 129 | IndexBenchmark<3> benchmark{state, arguments...}; 130 | benchmark.Benchmark(state); 131 | } 132 | 133 | // index type, scenario name, data_generator, num_entities, function_to_call 134 | // PhTree 3D CUBE 135 | BENCHMARK_CAPTURE(PhTree3D, INS_CU_1K, TestGenerator::CUBE, 1000, INSERT) 136 | ->Unit(benchmark::kMillisecond); 137 | 138 | BENCHMARK_CAPTURE(PhTree3D, INS_CU_10K, TestGenerator::CUBE, 10000, INSERT) 139 | ->Unit(benchmark::kMillisecond); 140 | 141 | BENCHMARK_CAPTURE(PhTree3D, INS_CU_100K, TestGenerator::CUBE, 100000, INSERT) 142 | ->Unit(benchmark::kMillisecond); 143 | 144 | BENCHMARK_CAPTURE(PhTree3D, INS_CU_1M, TestGenerator::CUBE, 1000000, INSERT) 145 | ->Unit(benchmark::kMillisecond); 146 | 147 | BENCHMARK_CAPTURE(PhTree3D, INS_CU_10M, TestGenerator::CUBE, 10000000, INSERT) 148 | ->Unit(benchmark::kMillisecond); 149 | 150 | BENCHMARK_CAPTURE(PhTree3D, EMP_CU_1K, TestGenerator::CUBE, 1000, EMPLACE) 151 | ->Unit(benchmark::kMillisecond); 152 | 153 | BENCHMARK_CAPTURE(PhTree3D, EMP_CU_10K, TestGenerator::CUBE, 10000, EMPLACE) 154 | ->Unit(benchmark::kMillisecond); 155 | 156 | BENCHMARK_CAPTURE(PhTree3D, EMP_CU_100K, TestGenerator::CUBE, 100000, EMPLACE) 157 | ->Unit(benchmark::kMillisecond); 158 | 159 | BENCHMARK_CAPTURE(PhTree3D, EMP_CU_1M, TestGenerator::CUBE, 1000000, EMPLACE) 160 | ->Unit(benchmark::kMillisecond); 161 | 162 | BENCHMARK_CAPTURE(PhTree3D, EMP_CU_10M, TestGenerator::CUBE, 10000000, EMPLACE) 163 | ->Unit(benchmark::kMillisecond); 164 | 165 | BENCHMARK_CAPTURE(PhTree3D, SQB_CU_1K, TestGenerator::CUBE, 1000, SQUARE_BR) 166 | ->Unit(benchmark::kMillisecond); 167 | 168 | BENCHMARK_CAPTURE(PhTree3D, SQB_CU_10K, TestGenerator::CUBE, 10000, SQUARE_BR) 169 | ->Unit(benchmark::kMillisecond); 170 | 171 | BENCHMARK_CAPTURE(PhTree3D, SQB_CU_100K, TestGenerator::CUBE, 100000, SQUARE_BR) 172 | ->Unit(benchmark::kMillisecond); 173 | 174 | BENCHMARK_CAPTURE(PhTree3D, SQB_CU_1M, TestGenerator::CUBE, 1000000, SQUARE_BR) 175 | ->Unit(benchmark::kMillisecond); 176 | 177 | BENCHMARK_CAPTURE(PhTree3D, SQB_CU_10M, TestGenerator::CUBE, 10000000, SQUARE_BR) 178 | ->Unit(benchmark::kMillisecond); 179 | 180 | BENCHMARK_CAPTURE(PhTree3D, EMP_CL_1K, TestGenerator::CLUSTER, 1000, EMPLACE) 181 | ->Unit(benchmark::kMillisecond); 182 | 183 | BENCHMARK_CAPTURE(PhTree3D, EMP_CL_10K, TestGenerator::CLUSTER, 10000, EMPLACE) 184 | ->Unit(benchmark::kMillisecond); 185 | 186 | BENCHMARK_CAPTURE(PhTree3D, EMP_CL_100K, TestGenerator::CLUSTER, 100000, EMPLACE) 187 | ->Unit(benchmark::kMillisecond); 188 | 189 | BENCHMARK_CAPTURE(PhTree3D, EMP_CL_1M, TestGenerator::CLUSTER, 1000000, EMPLACE) 190 | ->Unit(benchmark::kMillisecond); 191 | 192 | BENCHMARK_CAPTURE(PhTree3D, EMP_CL_10M, TestGenerator::CLUSTER, 10000000, EMPLACE) 193 | ->Unit(benchmark::kMillisecond); 194 | 195 | BENCHMARK_MAIN(); 196 | -------------------------------------------------------------------------------- /phtree/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 "logging.h" 17 | #include "phtree/benchmark/benchmark_util.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 | 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 | 43 | void Insert(benchmark::State& state, PhTreeBoxD& tree); 44 | 45 | const TestGenerator data_type_; 46 | const int num_entities_; 47 | std::vector> boxes_; 48 | }; 49 | 50 | template 51 | IndexBenchmark::IndexBenchmark( 52 | benchmark::State& state, TestGenerator data_type, int num_entities) 53 | : data_type_{data_type}, num_entities_(num_entities), boxes_(num_entities) { 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 PhTreeBoxD(); 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 | CreateBoxData(boxes_, data_type_, num_entities_, 0, GLOBAL_MAX, BOX_LEN); 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, PhTreeBoxD& tree) { 87 | for (int i = 0; i < num_entities_; ++i) { 88 | PhBoxD& p = boxes_[i]; 89 | tree.emplace(p, 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 PhTree3D(benchmark::State& state, Arguments&&... arguments) { 100 | IndexBenchmark<3> benchmark{state, arguments...}; 101 | benchmark.Benchmark(state); 102 | } 103 | 104 | // index type, scenario name, data_generator, num_entities 105 | // PhTree 3D CUBE 106 | BENCHMARK_CAPTURE(PhTree3D, INS_CU_1K, TestGenerator::CUBE, 1000)->Unit(benchmark::kMillisecond); 107 | 108 | BENCHMARK_CAPTURE(PhTree3D, INS_CU_10K, TestGenerator::CUBE, 10000)->Unit(benchmark::kMillisecond); 109 | 110 | BENCHMARK_CAPTURE(PhTree3D, INS_CU_100K, TestGenerator::CUBE, 100000) 111 | ->Unit(benchmark::kMillisecond); 112 | 113 | BENCHMARK_CAPTURE(PhTree3D, INS_CU_1M, TestGenerator::CUBE, 1000000)->Unit(benchmark::kMillisecond); 114 | 115 | BENCHMARK_CAPTURE(PhTree3D, INS_CU_10M, TestGenerator::CUBE, 10000000) 116 | ->Unit(benchmark::kMillisecond); 117 | 118 | BENCHMARK_CAPTURE(PhTree3D, INS_CL_1K, TestGenerator::CLUSTER, 1000)->Unit(benchmark::kMillisecond); 119 | 120 | BENCHMARK_CAPTURE(PhTree3D, INS_CL_10K, TestGenerator::CLUSTER, 10000) 121 | ->Unit(benchmark::kMillisecond); 122 | 123 | BENCHMARK_CAPTURE(PhTree3D, INS_CL_100K, TestGenerator::CLUSTER, 100000) 124 | ->Unit(benchmark::kMillisecond); 125 | 126 | BENCHMARK_CAPTURE(PhTree3D, INS_CL_1M, TestGenerator::CLUSTER, 1000000) 127 | ->Unit(benchmark::kMillisecond); 128 | 129 | BENCHMARK_CAPTURE(PhTree3D, INS_CL_10M, TestGenerator::CLUSTER, 10000000) 130 | ->Unit(benchmark::kMillisecond); 131 | 132 | BENCHMARK_MAIN(); 133 | -------------------------------------------------------------------------------- /phtree/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 "logging.h" 17 | #include "phtree/benchmark/benchmark_util.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 | public: 35 | IndexBenchmark(benchmark::State& state, TestGenerator data_type, int num_entities); 36 | 37 | void Benchmark(benchmark::State& state); 38 | 39 | private: 40 | void SetupWorld(benchmark::State& state); 41 | 42 | void Insert(benchmark::State& state, PhTreeD& tree); 43 | 44 | const TestGenerator data_type_; 45 | const int num_entities_; 46 | std::vector> points_; 47 | }; 48 | 49 | template 50 | IndexBenchmark::IndexBenchmark( 51 | benchmark::State& state, TestGenerator data_type, int num_entities) 52 | : data_type_{data_type}, num_entities_(num_entities), points_(num_entities) { 53 | logging::SetupDefaultLogging(); 54 | SetupWorld(state); 55 | } 56 | 57 | template 58 | void IndexBenchmark::Benchmark(benchmark::State& state) { 59 | for (auto _ : state) { 60 | state.PauseTiming(); 61 | auto* tree = new PhTreeD(); 62 | state.ResumeTiming(); 63 | 64 | Insert(state, *tree); 65 | 66 | // we do this top avoid measuring deallocation 67 | state.PauseTiming(); 68 | delete tree; 69 | state.ResumeTiming(); 70 | } 71 | } 72 | 73 | template 74 | void IndexBenchmark::SetupWorld(benchmark::State& state) { 75 | logging::info("Setting up world with {} entities and {} dimensions.", num_entities_, DIM); 76 | CreatePointData(points_, data_type_, num_entities_, 0, GLOBAL_MAX); 77 | 78 | state.counters["total_put_count"] = benchmark::Counter(0); 79 | state.counters["put_rate"] = benchmark::Counter(0, benchmark::Counter::kIsRate); 80 | 81 | logging::info("World setup complete."); 82 | } 83 | 84 | template 85 | void IndexBenchmark::Insert(benchmark::State& state, PhTreeD& tree) { 86 | for (int i = 0; i < num_entities_; ++i) { 87 | PhPointD& p = points_[i]; 88 | tree.emplace(p, i); 89 | } 90 | 91 | state.counters["total_put_count"] += num_entities_; 92 | state.counters["put_rate"] += num_entities_; 93 | } 94 | 95 | } // namespace 96 | 97 | template 98 | void PhTree3D(benchmark::State& state, Arguments&&... arguments) { 99 | IndexBenchmark<3> benchmark{state, arguments...}; 100 | benchmark.Benchmark(state); 101 | } 102 | 103 | template 104 | void PhTree6D(benchmark::State& state, Arguments&&... arguments) { 105 | IndexBenchmark<6> benchmark{state, arguments...}; 106 | benchmark.Benchmark(state); 107 | } 108 | 109 | template 110 | void PhTree10D(benchmark::State& state, Arguments&&... arguments) { 111 | IndexBenchmark<10> benchmark{state, arguments...}; 112 | benchmark.Benchmark(state); 113 | } 114 | 115 | template 116 | void PhTree20D(benchmark::State& state, Arguments&&... arguments) { 117 | IndexBenchmark<20> benchmark{state, arguments...}; 118 | benchmark.Benchmark(state); 119 | } 120 | 121 | // index type, scenario name, data_generator, num_entities 122 | // PhTree 3D CUBE 123 | BENCHMARK_CAPTURE(PhTree3D, INS_CU_1K, TestGenerator::CUBE, 1000)->Unit(benchmark::kMillisecond); 124 | 125 | BENCHMARK_CAPTURE(PhTree3D, INS_CU_10K, TestGenerator::CUBE, 10000)->Unit(benchmark::kMillisecond); 126 | 127 | BENCHMARK_CAPTURE(PhTree3D, INS_CU_100K, TestGenerator::CUBE, 100000) 128 | ->Unit(benchmark::kMillisecond); 129 | 130 | BENCHMARK_CAPTURE(PhTree3D, INS_CU_1M, TestGenerator::CUBE, 1000000)->Unit(benchmark::kMillisecond); 131 | 132 | BENCHMARK_CAPTURE(PhTree3D, INS_CU_10M, TestGenerator::CUBE, 10000000) 133 | ->Unit(benchmark::kMillisecond); 134 | 135 | BENCHMARK_CAPTURE(PhTree3D, INS_CL_1K, TestGenerator::CLUSTER, 1000)->Unit(benchmark::kMillisecond); 136 | 137 | BENCHMARK_CAPTURE(PhTree3D, INS_CL_10K, TestGenerator::CLUSTER, 10000) 138 | ->Unit(benchmark::kMillisecond); 139 | 140 | BENCHMARK_CAPTURE(PhTree3D, INS_CL_100K, TestGenerator::CLUSTER, 100000) 141 | ->Unit(benchmark::kMillisecond); 142 | 143 | BENCHMARK_CAPTURE(PhTree3D, INS_CL_1M, TestGenerator::CLUSTER, 1000000) 144 | ->Unit(benchmark::kMillisecond); 145 | 146 | BENCHMARK_CAPTURE(PhTree3D, INS_CL_10M, TestGenerator::CLUSTER, 10000000) 147 | ->Unit(benchmark::kMillisecond); 148 | 149 | BENCHMARK_CAPTURE(PhTree6D, INS_CL_100K, TestGenerator::CLUSTER, 100000) 150 | ->Unit(benchmark::kMillisecond); 151 | 152 | BENCHMARK_CAPTURE(PhTree6D, INS_CL_1M, TestGenerator::CLUSTER, 1000000) 153 | ->Unit(benchmark::kMillisecond); 154 | 155 | BENCHMARK_CAPTURE(PhTree10D, INS_CL_100K, TestGenerator::CLUSTER, 100000) 156 | ->Unit(benchmark::kMillisecond); 157 | 158 | BENCHMARK_CAPTURE(PhTree10D, INS_CL_1M, TestGenerator::CLUSTER, 1000000) 159 | ->Unit(benchmark::kMillisecond); 160 | 161 | BENCHMARK_CAPTURE(PhTree20D, INS_CL_100K, TestGenerator::CLUSTER, 100000) 162 | ->Unit(benchmark::kMillisecond); 163 | 164 | BENCHMARK_CAPTURE(PhTree20D, INS_CL_1M, TestGenerator::CLUSTER, 1000000) 165 | ->Unit(benchmark::kMillisecond); 166 | 167 | BENCHMARK_MAIN(); 168 | -------------------------------------------------------------------------------- /phtree/benchmark/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 "logging.h" 17 | #include "phtree/benchmark/benchmark_util.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 | 30 | /* 31 | * Benchmark for k-nearest-neighbour queries. 32 | */ 33 | template 34 | class IndexBenchmark { 35 | public: 36 | IndexBenchmark( 37 | benchmark::State& state, TestGenerator data_type, int num_entities, int knn_result_size_); 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 int num_entities_; 48 | const double 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( 58 | benchmark::State& state, TestGenerator data_type, int num_entities, int knn_result_size) 59 | : data_type_{data_type} 60 | , num_entities_(num_entities) 61 | , knn_result_size_(knn_result_size) 62 | , random_engine_{1} 63 | , cube_distribution_{0, GLOBAL_MAX} 64 | , points_(num_entities) { 65 | logging::SetupDefaultLogging(); 66 | SetupWorld(state); 67 | } 68 | 69 | template 70 | void IndexBenchmark::Benchmark(benchmark::State& state) { 71 | for (auto _ : state) { 72 | state.PauseTiming(); 73 | PhPointD center; 74 | CreateQuery(center); 75 | state.ResumeTiming(); 76 | 77 | QueryWorld(state, center); 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 | for (int i = 0; i < num_entities_; ++i) { 86 | tree_.emplace(points_[i], i); 87 | } 88 | 89 | state.counters["total_result_count"] = benchmark::Counter(0); 90 | state.counters["total_query_count"] = benchmark::Counter(0); 91 | state.counters["query_rate"] = benchmark::Counter(0, benchmark::Counter::kIsRate); 92 | state.counters["result_rate"] = benchmark::Counter(0, benchmark::Counter::kIsRate); 93 | state.counters["avg_result_count"] = benchmark::Counter(0, benchmark::Counter::kAvgIterations); 94 | 95 | logging::info("World setup complete."); 96 | } 97 | 98 | template 99 | void IndexBenchmark::QueryWorld(benchmark::State& state, PhPointD& center) { 100 | int n = 0; 101 | for (auto q = tree_.begin_knn_query(knn_result_size_, center, DistanceEuclidean<3>()); 102 | q != tree_.end(); 103 | ++q) { 104 | ++n; 105 | } 106 | 107 | state.counters["total_query_count"] += 1; 108 | state.counters["total_result_count"] += n; 109 | state.counters["query_rate"] += 1; 110 | state.counters["result_rate"] += n; 111 | state.counters["avg_result_count"] += n; 112 | } 113 | 114 | template 115 | void IndexBenchmark::CreateQuery(PhPointD& center) { 116 | for (dimension_t d = 0; d < DIM; ++d) { 117 | center[d] = cube_distribution_(random_engine_) * GLOBAL_MAX; 118 | } 119 | } 120 | 121 | } // namespace 122 | 123 | template 124 | void PhTree3D(benchmark::State& state, Arguments&&... arguments) { 125 | IndexBenchmark<3> benchmark{state, arguments...}; 126 | benchmark.Benchmark(state); 127 | } 128 | 129 | // index type, scenario name, data_type, num_entities, query_result_size 130 | // PhTree 3D CUBE 131 | BENCHMARK_CAPTURE(PhTree3D, KNN_CU_1_of_10K, TestGenerator::CUBE, 10000, 1) 132 | ->Unit(benchmark::kMillisecond); 133 | 134 | BENCHMARK_CAPTURE(PhTree3D, KNN_CU_1_of_1M, TestGenerator::CUBE, 1000000, 1) 135 | ->Unit(benchmark::kMillisecond); 136 | 137 | BENCHMARK_CAPTURE(PhTree3D, KNN_CU_10_of_10K, TestGenerator::CUBE, 10000, 10) 138 | ->Unit(benchmark::kMillisecond); 139 | 140 | BENCHMARK_CAPTURE(PhTree3D, KNN_CU_10_of_1M, TestGenerator::CUBE, 1000000, 10) 141 | ->Unit(benchmark::kMillisecond); 142 | 143 | // index type, scenario name, data_type, num_entities, query_result_size 144 | // PhTree 3D CLUSTER 145 | BENCHMARK_CAPTURE(PhTree3D, KNN_CL_1_of_10K, TestGenerator::CLUSTER, 10000, 1) 146 | ->Unit(benchmark::kMillisecond); 147 | 148 | BENCHMARK_CAPTURE(PhTree3D, KNN_CL_1_of_1M, TestGenerator::CLUSTER, 1000000, 1) 149 | ->Unit(benchmark::kMillisecond); 150 | 151 | BENCHMARK_CAPTURE(PhTree3D, KNN_CL_10_of_10K, TestGenerator::CLUSTER, 10000, 10) 152 | ->Unit(benchmark::kMillisecond); 153 | 154 | BENCHMARK_CAPTURE(PhTree3D, KNN_CL_10_of_1M, TestGenerator::CLUSTER, 1000000, 10) 155 | ->Unit(benchmark::kMillisecond); 156 | 157 | BENCHMARK_MAIN(); 158 | -------------------------------------------------------------------------------- /phtree/benchmark/logging.cc: -------------------------------------------------------------------------------- 1 | // Copyright (c) Improbable Worlds Ltd, All Rights Reserved 2 | #include "logging.h" 3 | 4 | namespace improbable::phtree::phbenchmark::logging { 5 | 6 | void SetupDefaultLogging() { 7 | SetupLogging({}, spdlog::level::warn); 8 | } 9 | 10 | void SetupLogging(std::vector sinks, spdlog::level::level_enum log_level) { 11 | auto& console_sink = sinks.emplace_back(std::make_shared()); 12 | console_sink->set_level(log_level); 13 | 14 | // Find the minimum log level, in case one of the sinks passed to us has a lower log level. 15 | const auto& sink_with_lowest_log_level = *std::min_element( 16 | sinks.begin(), 17 | sinks.end(), 18 | [](const spdlog::sink_ptr& a, const spdlog::sink_ptr& b) -> bool { 19 | return a->level() < b->level(); 20 | }); 21 | spdlog::level::level_enum min_log_level = 22 | std::min(sink_with_lowest_log_level->level(), log_level); 23 | 24 | // Create the external logger, worker logger and the internal (default) logger from the same log 25 | // sinks. Each logsink can use `GetLoggerTypeFromMessage` to determine which logger a message 26 | // was logged to. 27 | spdlog::set_default_logger( 28 | std::make_shared(kInternalLoggerName, sinks.begin(), sinks.end())); 29 | spdlog::set_level(min_log_level); 30 | spdlog::flush_on(min_log_level); 31 | } 32 | 33 | } // namespace improbable::phtree::phbenchmark::logging 34 | -------------------------------------------------------------------------------- /phtree/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 | 27 | // Sets up default logging typically used for tests/benchmarks. Also used for default 28 | // initialization if the logging hasn't been initialized before the first logging line. 29 | void SetupDefaultLogging(); 30 | 31 | template 32 | inline void log( 33 | spdlog::source_loc source, 34 | spdlog::level::level_enum lvl, 35 | spdlog::string_view_t fmt, 36 | const Args&... args) { 37 | spdlog::log(source, lvl, fmt, args...); 38 | } 39 | 40 | template 41 | inline void log(spdlog::level::level_enum lvl, spdlog::string_view_t fmt, const Args&... args) { 42 | spdlog::log(spdlog::source_loc{}, lvl, fmt, args...); 43 | } 44 | 45 | template 46 | inline void trace(spdlog::string_view_t fmt, const Args&... args) { 47 | log(spdlog::level::level_enum::trace, fmt, args...); 48 | } 49 | 50 | template 51 | inline void debug(spdlog::string_view_t fmt, const Args&... args) { 52 | log(spdlog::level::level_enum::debug, fmt, args...); 53 | } 54 | 55 | template 56 | inline void info(spdlog::string_view_t fmt, const Args&... args) { 57 | log(spdlog::level::level_enum::info, fmt, args...); 58 | } 59 | 60 | template 61 | inline void warn(spdlog::string_view_t fmt, const Args&... args) { 62 | log(spdlog::level::level_enum::warn, fmt, args...); 63 | } 64 | 65 | template 66 | inline void error(spdlog::string_view_t fmt, const Args&... args) { 67 | log(spdlog::level::level_enum::err, fmt, args...); 68 | } 69 | 70 | template 71 | inline void critical(spdlog::string_view_t fmt, const Args&... args) { 72 | log(spdlog::level::level_enum::critical, fmt, args...); 73 | } 74 | 75 | template 76 | inline void log(spdlog::source_loc source, spdlog::level::level_enum lvl, const T& msg) { 77 | spdlog::log(source, lvl, msg); 78 | } 79 | 80 | template 81 | inline void log(spdlog::level::level_enum lvl, const T& msg) { 82 | spdlog::log(lvl, msg); 83 | } 84 | 85 | template 86 | inline void trace(const T& msg) { 87 | log(spdlog::level::level_enum::trace, msg); 88 | } 89 | 90 | template 91 | inline void debug(const T& msg) { 92 | log(spdlog::level::level_enum::debug, msg); 93 | } 94 | 95 | template 96 | inline void info(const T& msg) { 97 | log(spdlog::level::level_enum::info, msg); 98 | } 99 | 100 | template 101 | inline void warn(const T& msg) { 102 | log(spdlog::level::level_enum::warn, msg); 103 | } 104 | 105 | template 106 | inline void error(const T& msg) { 107 | log(spdlog::level::level_enum::err, msg); 108 | } 109 | 110 | template 111 | inline void critical(const T& msg) { 112 | log(spdlog::level::level_enum::critical, msg); 113 | } 114 | 115 | } // namespace improbable::phtree::phbenchmark::logging 116 | 117 | #endif // PHTREE_BENCHMARK_LOGGING_H 118 | -------------------------------------------------------------------------------- /phtree/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 "logging.h" 17 | #include "phtree/benchmark/benchmark_util.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( 37 | benchmark::State& state, 38 | TestGenerator data_type, 39 | int num_entities, 40 | double avg_query_result_size_); 41 | 42 | void Benchmark(benchmark::State& state); 43 | 44 | private: 45 | void SetupWorld(benchmark::State& state); 46 | 47 | void QueryWorld(benchmark::State& state, PhBox& query); 48 | 49 | void CreateQuery(PhBox& query); 50 | 51 | const TestGenerator data_type_; 52 | const int num_entities_; 53 | const double avg_query_result_size_; 54 | 55 | constexpr int query_endge_length() { 56 | return GLOBAL_MAX * pow(avg_query_result_size_ / (double)num_entities_, 1. / (double)DIM); 57 | }; 58 | 59 | PhTree tree_; 60 | std::default_random_engine random_engine_; 61 | std::uniform_int_distribution<> cube_distribution_; 62 | std::vector> points_; 63 | }; 64 | 65 | template 66 | IndexBenchmark::IndexBenchmark( 67 | benchmark::State& state, 68 | TestGenerator data_type, 69 | int num_entities, 70 | double avg_query_result_size) 71 | : data_type_{data_type} 72 | , num_entities_(num_entities) 73 | , avg_query_result_size_(avg_query_result_size) 74 | , random_engine_{1} 75 | , cube_distribution_{0, GLOBAL_MAX} 76 | , points_(num_entities) { 77 | logging::SetupDefaultLogging(); 78 | SetupWorld(state); 79 | } 80 | 81 | template 82 | void IndexBenchmark::Benchmark(benchmark::State& state) { 83 | for (auto _ : state) { 84 | state.PauseTiming(); 85 | PhBox query_box; 86 | CreateQuery(query_box); 87 | state.ResumeTiming(); 88 | 89 | QueryWorld(state, query_box); 90 | } 91 | } 92 | 93 | template 94 | void IndexBenchmark::SetupWorld(benchmark::State& state) { 95 | logging::info("Setting up world with {} entities and {} dimensions.", num_entities_, DIM); 96 | CreatePointData(points_, data_type_, num_entities_, 0, GLOBAL_MAX); 97 | for (int i = 0; i < num_entities_; ++i) { 98 | tree_.emplace(points_[i], i); 99 | } 100 | 101 | state.counters["total_result_count"] = benchmark::Counter(0); 102 | state.counters["query_rate"] = benchmark::Counter(0, benchmark::Counter::kIsRate); 103 | state.counters["result_rate"] = benchmark::Counter(0, benchmark::Counter::kIsRate); 104 | state.counters["avg_result_count"] = benchmark::Counter(0, benchmark::Counter::kAvgIterations); 105 | 106 | logging::info("World setup complete."); 107 | } 108 | 109 | template 110 | void IndexBenchmark::QueryWorld(benchmark::State& state, PhBox& query_box) { 111 | int n = 0; 112 | for (auto q = tree_.begin_query(query_box); q != tree_.end(); ++q) { 113 | ++n; 114 | } 115 | 116 | state.counters["total_result_count"] += n; 117 | state.counters["query_rate"] += 1; 118 | state.counters["result_rate"] += n; 119 | state.counters["avg_result_count"] += n; 120 | } 121 | 122 | template 123 | void IndexBenchmark::CreateQuery(PhBox& query_box) { 124 | int length = query_endge_length(); 125 | // scale to ensure query lies within boundary 126 | double scale = (GLOBAL_MAX - (double)length) / GLOBAL_MAX; 127 | for (dimension_t d = 0; d < DIM; ++d) { 128 | auto s = cube_distribution_(random_engine_); 129 | s = s * scale; 130 | query_box.min()[d] = s; 131 | query_box.max()[d] = s + length; 132 | } 133 | } 134 | 135 | } // namespace 136 | 137 | template 138 | void PhTree3D(benchmark::State& state, Arguments&&... arguments) { 139 | IndexBenchmark<3> benchmark{state, arguments...}; 140 | benchmark.Benchmark(state); 141 | } 142 | 143 | // index type, scenario name, data_type, num_entities, query_result_size 144 | // PhTree 3D CUBE 145 | BENCHMARK_CAPTURE(PhTree3D, WQ_CU_100_of_1K, TestGenerator::CUBE, 1000, 100.0) 146 | ->Unit(benchmark::kMillisecond); 147 | 148 | BENCHMARK_CAPTURE(PhTree3D, WQ_CU_100_of_10K, TestGenerator::CUBE, 10000, 100.0) 149 | ->Unit(benchmark::kMillisecond); 150 | 151 | BENCHMARK_CAPTURE(PhTree3D, WQ_CU_100_of_100K, TestGenerator::CUBE, 100000, 100.0) 152 | ->Unit(benchmark::kMillisecond); 153 | 154 | BENCHMARK_CAPTURE(PhTree3D, WQ_CU_100_of_1M, TestGenerator::CUBE, 1000000, 100.0) 155 | ->Unit(benchmark::kMillisecond); 156 | 157 | // index type, scenario name, data_type, num_entities, query_result_size 158 | // PhTree 3D CLUSTER 159 | BENCHMARK_CAPTURE(PhTree3D, WQ_CL_100_of_1K, TestGenerator::CLUSTER, 1000, 100.0) 160 | ->Unit(benchmark::kMillisecond); 161 | 162 | BENCHMARK_CAPTURE(PhTree3D, WQ_CL_100_of_10K, TestGenerator::CLUSTER, 10000, 100.0) 163 | ->Unit(benchmark::kMillisecond); 164 | 165 | BENCHMARK_CAPTURE(PhTree3D, WQ_CL_100_of_100K, TestGenerator::CLUSTER, 100000, 100.0) 166 | ->Unit(benchmark::kMillisecond); 167 | 168 | BENCHMARK_CAPTURE(PhTree3D, WQ_CL_100_of_1M, TestGenerator::CLUSTER, 1000000, 100.0) 169 | ->Unit(benchmark::kMillisecond); 170 | 171 | BENCHMARK_MAIN(); 172 | -------------------------------------------------------------------------------- /phtree/benchmark/update_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 "logging.h" 17 | #include "phtree/benchmark/benchmark_util.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 | constexpr int UPDATES_PER_ROUND = 1000; 28 | constexpr double MOVE_DISTANCE = 10; 29 | 30 | const double GLOBAL_MAX = 10000; 31 | const double BOX_LEN = 10; 32 | 33 | template 34 | using BoxType = PhBoxD; 35 | 36 | template 37 | using TreeType = PhTreeBoxD; 38 | 39 | template 40 | struct UpdateOp { 41 | size_t id_; 42 | BoxType old_; 43 | BoxType new_; 44 | }; 45 | 46 | /* 47 | * Benchmark for updating the position of entries. 48 | */ 49 | template 50 | class IndexBenchmark { 51 | public: 52 | IndexBenchmark( 53 | benchmark::State& state, 54 | TestGenerator data_type, 55 | int num_entities, 56 | int updates_per_round = UPDATES_PER_ROUND, 57 | double move_distance = MOVE_DISTANCE); 58 | 59 | void Benchmark(benchmark::State& state); 60 | 61 | private: 62 | void SetupWorld(benchmark::State& state); 63 | void BuildUpdates(); 64 | void UpdateWorld(benchmark::State& state); 65 | 66 | const TestGenerator data_type_; 67 | const size_t num_entities_; 68 | const size_t updates_per_round_; 69 | const double move_distance_; 70 | 71 | TreeType tree_; 72 | std::vector> boxes_; 73 | std::vector> updates_; 74 | std::default_random_engine random_engine_; 75 | std::uniform_int_distribution<> entity_id_distribution_; 76 | }; 77 | 78 | template 79 | IndexBenchmark::IndexBenchmark( 80 | benchmark::State& state, 81 | TestGenerator data_type, 82 | int num_entities, 83 | int updates_per_round, 84 | double move_distance) 85 | : data_type_{data_type} 86 | , num_entities_(num_entities) 87 | , updates_per_round_(updates_per_round) 88 | , move_distance_(move_distance) 89 | , boxes_(num_entities) 90 | , updates_(updates_per_round) 91 | , random_engine_{0} 92 | , entity_id_distribution_{0, num_entities - 1} { 93 | logging::SetupDefaultLogging(); 94 | SetupWorld(state); 95 | } 96 | 97 | template 98 | void IndexBenchmark::Benchmark(benchmark::State& state) { 99 | for (auto _ : state) { 100 | state.PauseTiming(); 101 | BuildUpdates(); 102 | state.ResumeTiming(); 103 | 104 | UpdateWorld(state); 105 | } 106 | } 107 | 108 | template 109 | void IndexBenchmark::SetupWorld(benchmark::State& state) { 110 | logging::info("Setting up world with {} entities and {} dimensions.", num_entities_, DIM); 111 | CreateBoxData(boxes_, data_type_, num_entities_, 0, GLOBAL_MAX, BOX_LEN); 112 | for (size_t i = 0; i < num_entities_; ++i) { 113 | tree_.emplace(boxes_[i], i); 114 | } 115 | 116 | state.counters["total_upd_count"] = benchmark::Counter(0); 117 | state.counters["update_rate"] = benchmark::Counter(0, benchmark::Counter::kIsRate); 118 | logging::info("World setup complete."); 119 | } 120 | 121 | template 122 | void IndexBenchmark::BuildUpdates() { 123 | for (auto& update : updates_) { 124 | int box_id = entity_id_distribution_(random_engine_); 125 | update.id_ = box_id; 126 | update.old_ = boxes_[box_id]; 127 | for (dimension_t d = 0; d < DIM; ++d) { 128 | update.new_.min()[d] = update.old_.min()[d] + move_distance_; 129 | update.new_.max()[d] = update.old_.max()[d] + move_distance_; 130 | } 131 | // update reference data 132 | boxes_[box_id] = update.new_; 133 | } 134 | } 135 | 136 | template 137 | void IndexBenchmark::UpdateWorld(benchmark::State& state) { 138 | size_t initial_tree_size = tree_.size(); 139 | size_t n = 0; 140 | for (auto& update : updates_) { 141 | size_t result_erase = tree_.erase(update.old_); 142 | auto result_emplace = tree_.emplace(update.new_, update.id_); 143 | n += result_erase == 1 && result_emplace.second; 144 | } 145 | 146 | if (n != updates_.size()) { 147 | logging::error("Invalid update count: {}/{}", updates_.size(), n); 148 | } 149 | 150 | // For normal indexes we expect num_entities==size(), but the PhTree> index has 151 | // size() as low as (num_entities-duplicates). 152 | if (tree_.size() > num_entities_ || tree_.size() + updates_per_round_ < initial_tree_size) { 153 | logging::error("Invalid index size after update: {}/{}", tree_.size(), num_entities_); 154 | } 155 | 156 | state.counters["total_upd_count"] += updates_per_round_; 157 | state.counters["update_rate"] += updates_per_round_; 158 | } 159 | 160 | } // namespace 161 | 162 | template 163 | void PhTree3D(benchmark::State& state, Arguments&&... arguments) { 164 | IndexBenchmark<3> benchmark{state, arguments...}; 165 | benchmark.Benchmark(state); 166 | } 167 | 168 | // index type, scenario name, data_type, num_entities, updates_per_round, move_distance 169 | // PhTree3D CUBE 170 | BENCHMARK_CAPTURE(PhTree3D, UPDATE_CU_100_of_1K, TestGenerator::CUBE, 1000) 171 | ->Unit(benchmark::kMillisecond); 172 | 173 | BENCHMARK_CAPTURE(PhTree3D, UPDATE_CU_100_of_10K, TestGenerator::CUBE, 10000) 174 | ->Unit(benchmark::kMillisecond); 175 | 176 | BENCHMARK_CAPTURE(PhTree3D, UPDATE_CU_100_of_100K, TestGenerator::CUBE, 100000) 177 | ->Unit(benchmark::kMillisecond); 178 | 179 | BENCHMARK_CAPTURE(PhTree3D, UPDATE_CU_100_of_1M, TestGenerator::CUBE, 1000000) 180 | ->Unit(benchmark::kMillisecond); 181 | 182 | // PhTree3D CLUSTER 183 | BENCHMARK_CAPTURE(PhTree3D, UPDATE_CL_100_of_1K, TestGenerator::CLUSTER, 1000) 184 | ->Unit(benchmark::kMillisecond); 185 | 186 | BENCHMARK_CAPTURE(PhTree3D, UPDATE_CL_100_of_10K, TestGenerator::CLUSTER, 10000) 187 | ->Unit(benchmark::kMillisecond); 188 | 189 | BENCHMARK_CAPTURE(PhTree3D, UPDATE_CL_100_of_100K, TestGenerator::CLUSTER, 100000) 190 | ->Unit(benchmark::kMillisecond); 191 | 192 | BENCHMARK_CAPTURE(PhTree3D, UPDATE_CL_100_of_1M, TestGenerator::CLUSTER, 1000000) 193 | ->Unit(benchmark::kMillisecond); 194 | 195 | BENCHMARK_MAIN(); 196 | -------------------------------------------------------------------------------- /phtree/common/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:private"]) 2 | 3 | cc_library( 4 | name = "common", 5 | hdrs = [ 6 | "base_types.h", 7 | "bits.h", 8 | "common.h", 9 | "converter.h", 10 | "debug_helper.h", 11 | "distance.h", 12 | "filter.h", 13 | "flat_array_map.h", 14 | "flat_sparse_map.h", 15 | "tree_stats.h", 16 | ], 17 | visibility = [ 18 | "//visibility:public", 19 | ], 20 | deps = [ 21 | ], 22 | ) 23 | 24 | cc_test( 25 | name = "base_types_test", 26 | timeout = "long", 27 | srcs = [ 28 | "base_types_test.cc", 29 | ], 30 | linkstatic = True, 31 | deps = [ 32 | ":common", 33 | "//phtree/testing/gtest_main", 34 | ], 35 | ) 36 | 37 | cc_test( 38 | name = "bits_test", 39 | timeout = "long", 40 | srcs = [ 41 | "bits_test.cc", 42 | ], 43 | linkstatic = True, 44 | deps = [ 45 | ":common", 46 | "//phtree/testing/gtest_main", 47 | ], 48 | ) 49 | 50 | cc_test( 51 | name = "common_test", 52 | timeout = "long", 53 | srcs = [ 54 | "common_test.cc", 55 | ], 56 | linkstatic = True, 57 | deps = [ 58 | ":common", 59 | "//phtree/testing/gtest_main", 60 | ], 61 | ) 62 | 63 | cc_test( 64 | name = "distance_test", 65 | timeout = "long", 66 | srcs = [ 67 | "distance_test.cc", 68 | ], 69 | linkstatic = True, 70 | deps = [ 71 | ":common", 72 | "//phtree/testing/gtest_main", 73 | ], 74 | ) 75 | 76 | cc_test( 77 | name = "filter_test", 78 | timeout = "long", 79 | srcs = [ 80 | "filter_test.cc", 81 | ], 82 | linkstatic = True, 83 | deps = [ 84 | ":common", 85 | "//phtree/testing/gtest_main", 86 | ], 87 | ) 88 | 89 | cc_test( 90 | name = "flat_array_map_test", 91 | timeout = "long", 92 | srcs = [ 93 | "flat_array_map_test.cc", 94 | ], 95 | linkstatic = True, 96 | deps = [ 97 | ":common", 98 | "//phtree/testing/gtest_main", 99 | ], 100 | ) 101 | 102 | cc_test( 103 | name = "flat_sparse_map_test", 104 | timeout = "long", 105 | srcs = [ 106 | "flat_sparse_map_test.cc", 107 | ], 108 | linkstatic = True, 109 | deps = [ 110 | ":common", 111 | "//phtree/testing/gtest_main", 112 | ], 113 | ) 114 | 115 | cc_test( 116 | name = "preprocessor_test", 117 | timeout = "long", 118 | srcs = [ 119 | "converter_test.cc", 120 | ], 121 | linkstatic = True, 122 | deps = [ 123 | ":common", 124 | "//phtree/testing/gtest_main", 125 | ], 126 | ) 127 | -------------------------------------------------------------------------------- /phtree/common/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | 3 | target_sources(phtree 4 | PRIVATE 5 | common.h 6 | base_types.h 7 | bits.h 8 | distance.h 9 | filter.h 10 | flat_array_map.h 11 | flat_sparse_map.h 12 | converter.h 13 | debug_helper.h 14 | tree_stats.h 15 | ) 16 | -------------------------------------------------------------------------------- /phtree/common/README.md: -------------------------------------------------------------------------------- 1 | ### Utilities used by all PH-Tree implementations 2 | 3 | -------------------------------------------------------------------------------- /phtree/common/base_types.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_BASE_TYPES_H 18 | #define PHTREE_COMMON_BASE_TYPES_H 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | /* 28 | * PLEASE do not include this file directly, it is included via common.h. 29 | * 30 | * This file contains specifications for various types used in the PH-Tree, including 31 | * PhPoint, PhPointD and PhPointBox. 32 | */ 33 | namespace improbable::phtree { 34 | 35 | // ************************************************************************ 36 | // Constants and base types 37 | // ************************************************************************ 38 | 39 | using scalar_64_t = int64_t; 40 | using scalar_32_t = int32_t; 41 | using scalar_16_t = int16_t; 42 | 43 | // Bits in a coordinate (usually a double or long has 64 bits, so uint_8 suffices) 44 | using bit_width_t = uint16_t; 45 | // Number of bit for 'scalar_64_t' or 'scalar_32_t'. Note that 'digits' does _not_ include sign bit, 46 | // so e.g. int64_t has 63 `digits`, however we need all bits, i.e. 64. 47 | template 48 | static constexpr bit_width_t MAX_BIT_WIDTH = 49 | std::numeric_limits::digits + std::numeric_limits::is_signed; 50 | // Bit mask 51 | template 52 | using bit_mask_t = typename std::make_unsigned::type; 53 | template 54 | static constexpr bit_mask_t MAX_MASK = std::numeric_limits>::max(); 55 | using dimension_t = size_t; // Number of dimensions 56 | using hc_pos_t = uint64_t; 57 | 58 | // ************************************************************************ 59 | // Basic structs and classes 60 | // ************************************************************************ 61 | 62 | // The SCALAR type needs to be a signet integer, i.e. int32_t or int64_t. 63 | template 64 | using PhPoint = std::array; 65 | 66 | template 67 | using PhPointD = std::array; 68 | 69 | template 70 | using PhPointF = std::array; 71 | 72 | template 73 | class PhBox { 74 | using Point = PhPoint; 75 | 76 | public: 77 | explicit PhBox() = default; 78 | 79 | PhBox(const PhBox& orig) = default; 80 | 81 | PhBox(const std::array& min, const std::array& max) 82 | : min_{min}, max_{max} {} 83 | 84 | [[nodiscard]] const Point& min() const { 85 | return min_; 86 | } 87 | 88 | [[nodiscard]] const Point& max() const { 89 | return max_; 90 | } 91 | 92 | [[nodiscard]] Point& min() { 93 | return min_; 94 | } 95 | 96 | [[nodiscard]] Point& max() { 97 | return max_; 98 | } 99 | 100 | void min(const std::array& new_min) { 101 | min_ = new_min; 102 | } 103 | 104 | void max(const std::array& new_max) { 105 | max_ = new_max; 106 | } 107 | 108 | auto operator==(const PhBox& other) const -> bool { 109 | return min_ == other.min_ && max_ == other.max_; 110 | } 111 | 112 | private: 113 | Point min_; 114 | Point max_; 115 | }; 116 | 117 | template 118 | using PhBoxD = PhBox; 119 | 120 | template 121 | using PhBoxF = PhBox; 122 | 123 | template 124 | std::ostream& operator<<(std::ostream& os, const PhPoint& data) { 125 | assert(DIM >= 1); 126 | os << "["; 127 | for (dimension_t i = 0; i < DIM - 1; ++i) { 128 | os << data[i] << ","; 129 | } 130 | os << data[DIM - 1] << "]"; 131 | return os; 132 | } 133 | 134 | template 135 | std::ostream& operator<<(std::ostream& os, const PhBox& data) { 136 | os << data.min() << ":" << data.max(); 137 | return os; 138 | } 139 | 140 | // Taken from boost::hash_combine 141 | template 142 | inline void hash_combine(std::size_t& seed, const T& v) { 143 | seed ^= std::hash{}(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); 144 | } 145 | 146 | } // namespace improbable::phtree 147 | 148 | namespace std { 149 | template 150 | struct hash> { 151 | size_t operator()(const improbable::phtree::PhPoint& x) const { 152 | std::size_t hash_val = 0; 153 | for (improbable::phtree::dimension_t i = 0; i < DIM; ++i) { 154 | improbable::phtree::hash_combine(hash_val, x[i]); 155 | } 156 | return hash_val; 157 | } 158 | }; 159 | template 160 | struct hash> { 161 | size_t operator()(const improbable::phtree::PhBox& x) const { 162 | std::size_t hash_val = 0; 163 | for (improbable::phtree::dimension_t i = 0; i < DIM; ++i) { 164 | improbable::phtree::hash_combine(hash_val, x.min()[i]); 165 | improbable::phtree::hash_combine(hash_val, x.max()[i]); 166 | } 167 | return hash_val; 168 | } 169 | }; 170 | } // namespace std 171 | #endif // PHTREE_COMMON_BASE_TYPES_H 172 | -------------------------------------------------------------------------------- /phtree/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 "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 | -------------------------------------------------------------------------------- /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 { 36 | 37 | namespace { 38 | inline bit_width_t NumberOfLeadingZeros(std::uint64_t bit_string) { 39 | if (bit_string == 0) { 40 | return 64; 41 | } 42 | bit_width_t n = 1; 43 | std::uint32_t x = (bit_string >> 32); 44 | if (x == 0) { 45 | n += 32; 46 | x = (int)bit_string; 47 | } 48 | if (x >> 16 == 0) { 49 | n += 16; 50 | x <<= 16; 51 | } 52 | if (x >> 24 == 0) { 53 | n += 8; 54 | x <<= 8; 55 | } 56 | if (x >> 28 == 0) { 57 | n += 4; 58 | x <<= 4; 59 | } 60 | if (x >> 30 == 0) { 61 | n += 2; 62 | x <<= 2; 63 | } 64 | n -= x >> 31; 65 | return n; 66 | } 67 | 68 | inline bit_width_t NumberOfLeadingZeros(std::uint32_t bit_string) { 69 | if (bit_string == 0) { 70 | return 32; 71 | } 72 | bit_width_t n = 1; 73 | if (bit_string >> 16 == 0) { 74 | n += 16; 75 | bit_string <<= 16; 76 | } 77 | if (bit_string >> 24 == 0) { 78 | n += 8; 79 | bit_string <<= 8; 80 | } 81 | if (bit_string >> 28 == 0) { 82 | n += 4; 83 | bit_string <<= 4; 84 | } 85 | if (bit_string >> 30 == 0) { 86 | n += 2; 87 | bit_string <<= 2; 88 | } 89 | n -= bit_string >> 31; 90 | return n; 91 | } 92 | 93 | inline bit_width_t NumberOfTrailingZeros(std::uint64_t bit_string) { 94 | if (bit_string == 0) { 95 | return 64; 96 | } 97 | uint32_t x = 0; 98 | uint32_t y = 0; 99 | uint16_t n = 63; 100 | y = (std::uint32_t)bit_string; 101 | if (y != 0) { 102 | n = n - 32; 103 | x = y; 104 | } else { 105 | x = (std::uint32_t)(bit_string >> 32); 106 | } 107 | y = x << 16; 108 | if (y != 0) { 109 | n = n - 16; 110 | x = y; 111 | } 112 | y = x << 8; 113 | if (y != 0) { 114 | n = n - 8; 115 | x = y; 116 | } 117 | y = x << 4; 118 | if (y != 0) { 119 | n = n - 4; 120 | x = y; 121 | } 122 | y = x << 2; 123 | if (y != 0) { 124 | n = n - 2; 125 | x = y; 126 | } 127 | return n - ((x << 1) >> 31); 128 | } 129 | 130 | #if defined(__clang__) || defined(__GNUC__) 131 | // See https://en.cppreference.com/w/cpp/language/types 132 | inline bit_width_t NumberOfLeadingZeros_GCC_BUILTIN(std::uint64_t bit_string) { 133 | return bit_string == 0 ? 64U : __builtin_clzll(bit_string); 134 | } 135 | 136 | inline bit_width_t NumberOfLeadingZeros_GCC_BUILTIN(std::uint32_t bit_string) { 137 | return bit_string == 0 ? 32U : __builtin_clz(bit_string); 138 | } 139 | 140 | inline bit_width_t NumberOfTrailingZeros_GCC_BUILTIN(std::uint64_t bit_string) { 141 | return bit_string == 0 ? 64U : __builtin_ctzll(bit_string); 142 | } 143 | 144 | inline bit_width_t NumberOfTrailingZeros_GCC_BUILTIN(std::uint32_t bit_string) { 145 | return bit_string == 0 ? 32U : __builtin_ctz(bit_string); 146 | } 147 | 148 | #elif defined(_MSC_VER) 149 | // https://docs.microsoft.com/en-us/cpp/intrinsics/x64-amd64-intrinsics-list?view=vs-2019 150 | inline bit_width_t NumberOfLeadingZeros_MSVC_BUILTIN(std::uint64_t bit_string) { 151 | unsigned long leading_zero = 0; 152 | return _BitScanReverse64(&leading_zero, bit_string) ? 63 - leading_zero : 64U; 153 | } 154 | 155 | inline bit_width_t NumberOfLeadingZeros_MSVC_BUILTIN(std::uint32_t bit_string) { 156 | unsigned long leading_zero = 0; 157 | return _BitScanReverse(&leading_zero, bit_string) ? 31 - leading_zero : 32U; 158 | } 159 | 160 | inline bit_width_t NumberOfTrailingZeros_MSVC_BUILTIN(std::uint64_t bit_string) { 161 | unsigned long trailing_zero = 0; 162 | return _BitScanForward64(&trailing_zero, bit_string) ? trailing_zero : 64U; 163 | } 164 | 165 | inline bit_width_t NumberOfTrailingZeros_MSVC_BUILTIN(std::uint32_t bit_string) { 166 | unsigned long trailing_zero = 0; 167 | return _BitScanForward(&trailing_zero, bit_string) ? trailing_zero : 32U; 168 | } 169 | #endif 170 | } // namespace 171 | 172 | #if defined(__clang__) || defined(__GNUC__) 173 | #define CountLeadingZeros(bits) NumberOfLeadingZeros_GCC_BUILTIN(bits) 174 | #define CountTrailingZeros(bits) NumberOfTrailingZeros_GCC_BUILTIN(bits) 175 | #elif defined(_MSC_VER) 176 | #define CountLeadingZeros(bits) NumberOfLeadingZeros_MSVC_BUILTIN(bits) 177 | #define CountTrailingZeros(bits) NumberOfTrailingZeros_MSVC_BUILTIN(bits) 178 | #else 179 | #define CountLeadingZeros(bits) NumberOfLeadingZeros(bits) 180 | #define CountTrailingZeros(bits) NumberOfTrailingZeros(bits) 181 | #endif 182 | 183 | } // namespace improbable::phtree 184 | 185 | #endif // PHTREE_COMMON_BITS_H 186 | -------------------------------------------------------------------------------- /phtree/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 "bits.h" 18 | #include 19 | #include 20 | 21 | using namespace improbable::phtree; 22 | 23 | TEST(PhTreeBitsTest, CountLeadingZeros64) { 24 | std::uint64_t x = 1; 25 | x <<= 63; 26 | for (int i = 0; i < 64; i++) { 27 | int ctz = CountLeadingZeros(x); 28 | ASSERT_EQ(i, ctz); 29 | x >>= 1; 30 | } 31 | } 32 | 33 | TEST(PhTreeBitsTest, CountTrailingZeros64) { 34 | std::uint64_t x = 1; 35 | for (int i = 0; i < 64; i++) { 36 | int ctz = CountTrailingZeros(x); 37 | ASSERT_EQ(i, ctz); 38 | x <<= 1; 39 | } 40 | } 41 | 42 | TEST(PhTreeBitsTest, CountLeadingZeros32) { 43 | std::uint32_t x = 1; 44 | x <<= 31; 45 | for (int i = 0; i < 32; i++) { 46 | int ctz = CountLeadingZeros(x); 47 | ASSERT_EQ(i, ctz); 48 | x >>= 1; 49 | } 50 | } 51 | 52 | TEST(PhTreeBitsTest, CountTrailingZeros32) { 53 | std::uint32_t x = 1; 54 | for (int i = 0; i < 32; i++) { 55 | int ctz = CountTrailingZeros(x); 56 | ASSERT_EQ(i, ctz); 57 | x <<= 1; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /phtree/common/common.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_COMMON_H 18 | #define PHTREE_COMMON_COMMON_H 19 | 20 | #include "base_types.h" 21 | #include "bits.h" 22 | #include "converter.h" 23 | #include "distance.h" 24 | #include "filter.h" 25 | #include "flat_array_map.h" 26 | #include "flat_sparse_map.h" 27 | #include "tree_stats.h" 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | namespace improbable::phtree { 35 | 36 | // This is the single-point inclusion file for common types/function/... for the PH-Tree. 37 | // 'single-point inclusion' meaning that including it provides all relevant types/functions/... . 38 | 39 | // ************************************************************************ 40 | // Bits 41 | // ************************************************************************ 42 | 43 | /* 44 | * Encode the bits at the given position of all attributes into a hyper-cube address. 45 | * Currently, the first attribute determines the left-most (high-value) bit of the address 46 | * (left to right ordered) 47 | * 48 | * @param valSet vector 49 | * @param postfix_len the postfix length 50 | * @returns Encoded HC position, which is the index in the array if the entries would be stored in 51 | * an array. 52 | */ 53 | template 54 | static hc_pos_t CalcPosInArray(const PhPoint& valSet, bit_width_t postfix_len) { 55 | // n=DIM, i={0..n-1} 56 | // i = 0 : |0|1|0|1|0|1|0|1| 57 | // i = 1 : | 0 | 1 | 0 | 1 | 58 | // i = 2 : | 0 | 1 | 59 | // len = 2^n 60 | // Following formula was for inverse ordering of current ordering... 61 | // pos = sum (i=1..n, len/2^i) = sum (..., 2^(n-i)) 62 | bit_mask_t valMask = bit_mask_t(1) << postfix_len; 63 | hc_pos_t pos = 0; 64 | for (dimension_t i = 0; i < DIM; ++i) { 65 | pos <<= 1; 66 | // set pos-bit if bit is set in value 67 | pos |= (valMask & valSet[i]) >> postfix_len; 68 | } 69 | return pos; 70 | } 71 | 72 | template 73 | static bool IsInRange( 74 | const PhPoint& candidate, 75 | const PhPoint& range_min, 76 | const PhPoint& range_max) { 77 | for (dimension_t i = 0; i < DIM; ++i) { 78 | auto k = candidate[i]; 79 | if (k < range_min[i] || k > range_max[i]) { 80 | return false; 81 | } 82 | } 83 | return true; 84 | } 85 | 86 | /* 87 | * @param v1 key 1 88 | * @param v2 key 2 89 | * @return the number of diverging bits. For each dimension we determine the most significant bit 90 | * where the two keys differ. We then count this bit plus all trailing bits (even if individual bits 91 | * may be the same). Then we return the highest number of diverging bits found in any dimension of 92 | * the two keys. In case of key1==key2 we return 0. In other words, for 64 bit keys, we return 64 93 | * minus the number of leading bits that are common in both keys across all dimensions. 94 | */ 95 | template 96 | static bit_width_t NumberOfDivergingBits( 97 | const PhPoint& v1, const PhPoint& v2) { 98 | // write all differences to diff, we just check diff afterwards 99 | bit_mask_t diff = 0; 100 | for (dimension_t i = 0; i < DIM; ++i) { 101 | diff |= (v1[i] ^ v2[i]); 102 | } 103 | assert(CountLeadingZeros(diff) <= MAX_BIT_WIDTH); 104 | return MAX_BIT_WIDTH - CountLeadingZeros(diff); 105 | } 106 | 107 | template 108 | static bool KeyEquals( 109 | const PhPoint& key_a, const PhPoint& key_b, bit_mask_t mask) { 110 | for (dimension_t i = 0; i < DIM; ++i) { 111 | if (((key_a[i] ^ key_b[i]) & mask) != 0) { 112 | return false; 113 | } 114 | } 115 | return true; 116 | } 117 | 118 | // ************************************************************************ 119 | // String helpers 120 | // ************************************************************************ 121 | 122 | template 123 | static inline std::string ToBinary(SCALAR l, bit_width_t width = MAX_BIT_WIDTH) { 124 | std::ostringstream sb; 125 | // long mask = DEPTH < 64 ? (1<<(DEPTH-1)) : 0x8000000000000000L; 126 | for (bit_width_t i = 0; i < width; ++i) { 127 | bit_mask_t mask = (bit_mask_t(1) << (width - i - 1)); 128 | sb << ((l & mask) != 0 ? "1" : "0"); 129 | if ((i + 1) % 8 == 0 && (i + 1) < width) { 130 | sb << '.'; 131 | } 132 | } 133 | return sb.str(); 134 | } 135 | 136 | template 137 | static inline std::string ToBinary( 138 | const PhPoint& la, bit_width_t width = MAX_BIT_WIDTH) { 139 | std::ostringstream sb; 140 | for (dimension_t i = 0; i < DIM; ++i) { 141 | sb << ToBinary(la[i], width) << ", "; 142 | } 143 | return sb.str(); 144 | } 145 | 146 | } // namespace improbable::phtree 147 | 148 | #endif // PHTREE_COMMON_COMMON_H 149 | -------------------------------------------------------------------------------- /phtree/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 "common.h" 18 | #include 19 | #include 20 | 21 | using namespace improbable::phtree; 22 | 23 | TEST(PhTreeCommonTest, NumberOfDivergingBits) { 24 | double d1 = -55; 25 | double d2 = 7; 26 | 27 | auto l1 = ScalarConverterIEEE::pre(d1); 28 | auto l2 = ScalarConverterIEEE::pre(d2); 29 | scalar_64_t l_min = std::numeric_limits::lowest(); 30 | scalar_64_t l_max = std::numeric_limits::max(); 31 | 32 | bit_width_t x = NumberOfDivergingBits(PhPoint<2>({l1, l1}), PhPoint<2>({l2, l2})); 33 | ASSERT_EQ(64, x); 34 | x = NumberOfDivergingBits(PhPoint<2>({-1, -1}), PhPoint<2>({l_min, l_min})); 35 | ASSERT_EQ(63, x); 36 | x = NumberOfDivergingBits(PhPoint<2>({1, 1}), PhPoint<2>({l_max, l_max})); 37 | ASSERT_EQ(63, x); 38 | 39 | x = NumberOfDivergingBits(PhPoint<2>({l1, l2}), PhPoint<2>({l1, l2})); 40 | ASSERT_EQ(0, x); 41 | 42 | // PhPointD{679.186, 519.897, 519.897} 43 | PhPoint<3> p1{0x4085397c9ffc65e8, 0x40803f2cf7158e9a, 0x40803f2cf7158e9a}; 44 | // PhPointD{35.5375, 8.69049, 8.69049} 45 | PhPoint<3> p2{0x4041c4ce0e8a359e, 0x40216187a0776fd5, 0x40216187a0776fd5}; 46 | x = NumberOfDivergingBits(p1, p2); 47 | ASSERT_EQ(56, x); 48 | 49 | // PhPointD{132.406, 219.74, 219.74} 50 | PhPoint<3> p20{0x40608cffffe5b480, 0x406b77aff096adc1, 0x406b77aff096adc1}; 51 | // PhPointD{679.186, 519.897, 519.897} 52 | PhPoint<3> p21{0x4085397c9ffc65e8, 0x40803f2cf7158e9a, 0x40803f2cf7158e9a}; 53 | x = NumberOfDivergingBits(p20, p21); 54 | ASSERT_EQ(56, x); 55 | } 56 | -------------------------------------------------------------------------------- /phtree/common/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 "converter.h" 18 | #include "common.h" 19 | #include 20 | 21 | using namespace improbable::phtree; 22 | 23 | template 24 | void test_less_than(PRE pre, POST post, float d1, float d2) { 25 | auto l1 = pre(d1); 26 | auto l2 = pre(d2); 27 | ASSERT_LT(l1, l2); 28 | ASSERT_EQ(d1, post(l1)); 29 | ASSERT_EQ(d2, post(l2)); 30 | } 31 | 32 | template 33 | void testAll(PRE pre, POST post) { 34 | test_less_than(pre, post, 55.0f, 71.0f); 35 | test_less_than(pre, post, -55.0f, 7.0f); 36 | test_less_than(pre, post, -55.0f, -7.0f); 37 | } 38 | 39 | TEST(PhTreePreprocessorTest, IEEE_Double_SmokeTest) { 40 | auto pre = [](double d) { return ScalarConverterIEEE::pre(d); }; 41 | auto post = [](scalar_64_t s) { return ScalarConverterIEEE::post(s); }; 42 | testAll(pre, post); 43 | } 44 | 45 | TEST(PhTreePreprocessorTest, IEEE_Float_SmokeTest) { 46 | auto pre = [](float f) { return ScalarConverterIEEE::pre(f); }; 47 | auto post = [](scalar_32_t s) { return ScalarConverterIEEE::post(s); }; 48 | testAll(pre, post); 49 | } 50 | -------------------------------------------------------------------------------- /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 | } 43 | 44 | /* 45 | * Collects some statistics about the tree, such as number of nodes, average depth, ... 46 | * 47 | * @return some statistics about the tree. 48 | */ 49 | template 50 | static PhTreeStats GetStats(const TREE& tree) { 51 | return tree.GetInternalTree().GetDebugHelper().GetStats(); 52 | } 53 | 54 | /* 55 | * Depending on the detail parameter this returns: 56 | * - "name" : a string that identifies the tree implementation type. 57 | * - "entries" : a string that lists all elements in the tree. 58 | * - "tree" : a string that lists all elements in the tree, pretty formatted to indicate tree 59 | * structure. 60 | * 61 | * @return a string as described above. 62 | */ 63 | template 64 | static std::string ToString(const TREE& tree, const PrintDetail& detail) { 65 | return tree.GetInternalTree().GetDebugHelper().ToString(detail); 66 | } 67 | }; 68 | 69 | } // namespace improbable::phtree 70 | #endif // PHTREE_COMMON_DEBUG_HELPER_H 71 | -------------------------------------------------------------------------------- /phtree/common/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 "base_types.h" 21 | #include "bits.h" 22 | #include "converter.h" 23 | #include "flat_array_map.h" 24 | #include "flat_sparse_map.h" 25 | #include "tree_stats.h" 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | namespace improbable::phtree { 33 | 34 | /* 35 | * The PH-Tree supports different distance functions. These can be used 36 | * by the kNN (k nearest neighbor) query facility. 37 | * 38 | * The implementations in this file are: 39 | * - DistanceEuclidean: Euclidean distance for PhPoint & PhPointD 40 | * - DistanceL1: L1 distance (Manhattan distance / taxi distance) for PhPoint & PhPointD 41 | */ 42 | 43 | template 44 | struct DistanceEuclidean { 45 | double operator()(const PhPoint& v1, const PhPoint& v2) const { 46 | double sum2 = 0; 47 | for (dimension_t i = 0; i < DIM; ++i) { 48 | assert( 49 | (v1[i] >= 0) != (v2[i] >= 0) || 50 | double(v1[i]) - double(v2[i]) < 51 | double(std::numeric_limits::max())); 52 | double d2 = double(v1[i] - v2[i]); 53 | sum2 += d2 * d2; 54 | } 55 | return sqrt(sum2); 56 | }; 57 | 58 | double operator()(const PhPointD& p1, const PhPointD& p2) const { 59 | double sum2 = 0; 60 | for (dimension_t i = 0; i < DIM; ++i) { 61 | double d2 = p1[i] - p2[i]; 62 | sum2 += d2 * d2; 63 | } 64 | return sqrt(sum2); 65 | }; 66 | 67 | double operator()(const PhPointF& v1, const PhPointF& v2) const { 68 | double sum2 = 0; 69 | for (dimension_t i = 0; i < DIM; i++) { 70 | double d2 = double(v1[i] - v2[i]); 71 | sum2 += d2 * d2; 72 | } 73 | return sqrt(sum2); 74 | }; 75 | }; 76 | 77 | template 78 | struct DistanceL1 { 79 | double operator()(const PhPoint& v1, const PhPoint& v2) const { 80 | double sum = 0; 81 | for (dimension_t i = 0; i < DIM; ++i) { 82 | assert( 83 | (v1[i] >= 0) != (v2[i] >= 0) || 84 | double(v1[i]) - double(v2[i]) < 85 | double(std::numeric_limits::max())); 86 | sum += std::abs(double(v1[i] - v2[i])); 87 | } 88 | return sum; 89 | }; 90 | 91 | double operator()(const PhPointD& v1, const PhPointD& v2) const { 92 | double sum = 0; 93 | for (dimension_t i = 0; i < DIM; ++i) { 94 | sum += std::abs(v1[i] - v2[i]); 95 | } 96 | return sum; 97 | }; 98 | }; 99 | 100 | } // namespace improbable::phtree 101 | 102 | #endif // PHTREE_COMMON_DISTANCES_H 103 | -------------------------------------------------------------------------------- /phtree/common/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 "common.h" 18 | #include 19 | #include 20 | 21 | using namespace improbable::phtree; 22 | 23 | TEST(PhTreeDistanceTest, DoubleEuclidean) { 24 | auto distance = DistanceEuclidean<2>(); 25 | ASSERT_DOUBLE_EQ(5, distance(PhPointD<2>{-1, -1}, PhPointD<2>{2, 3})); 26 | } 27 | 28 | TEST(PhTreeDistanceTest, DoubleL1) { 29 | auto distance = DistanceL1<2>(); 30 | ASSERT_DOUBLE_EQ(7, distance(PhPointD<2>{-1, -1}, PhPointD<2>{2, 3})); 31 | } 32 | 33 | TEST(PhTreeDistanceTest, LongEuclidean) { 34 | auto distance = DistanceEuclidean<2>(); 35 | ASSERT_DOUBLE_EQ(5, distance(PhPoint<2>{-1, -1}, PhPoint<2>{2, 3})); 36 | } 37 | 38 | TEST(PhTreeDistanceTest, LongL1) { 39 | auto distance = DistanceL1<2>(); 40 | ASSERT_DOUBLE_EQ(7, distance(PhPoint<2>{-1, -1}, PhPoint<2>{2, 3})); 41 | } 42 | -------------------------------------------------------------------------------- /phtree/common/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 "common.h" 18 | #include 19 | #include 20 | 21 | using namespace improbable::phtree; 22 | 23 | TEST(PhTreeFilterTest, FilterSphereTest) { 24 | FilterSphere, DistanceEuclidean<2>> filter{{5, 3}, 5}; 25 | // root is always valid 26 | ASSERT_TRUE(filter.IsNodeValid({0, 0}, 63)); 27 | // valid because node encompasses the circle 28 | ASSERT_TRUE(filter.IsNodeValid({1, 1}, 10)); 29 | // valid because circle encompasses the node 30 | ASSERT_TRUE(filter.IsNodeValid({5, 5}, 2)); 31 | // valid because circle encompasses the node AABB 32 | ASSERT_TRUE(filter.IsNodeValid({7, 7}, 1)); 33 | // valid because circle touches the edge of the node AABB 34 | ASSERT_TRUE(filter.IsNodeValid({5, 9}, 1)); 35 | // valid because circle cuts edge of node AABB 36 | ASSERT_TRUE(filter.IsNodeValid({12, 7}, 3)); 37 | ASSERT_TRUE(filter.IsNodeValid({10, 7}, 2)); 38 | // invalid because node is just outside the circle 39 | ASSERT_FALSE(filter.IsNodeValid({5, 10}, 1)); 40 | ASSERT_FALSE(filter.IsNodeValid({12, 12}, 3)); 41 | 42 | ASSERT_TRUE(filter.IsEntryValid({3, 7}, nullptr)); 43 | ASSERT_TRUE(filter.IsEntryValid({5, 8}, nullptr)); 44 | ASSERT_FALSE(filter.IsEntryValid({3, 8}, nullptr)); 45 | } 46 | 47 | TEST(PhTreeFilterTest, BoxFilterTest) { 48 | FilterAABB> filter{{3, 3}, {7, 7}}; 49 | // root is always valid 50 | ASSERT_TRUE(filter.IsNodeValid({0, 0}, 63)); 51 | // valid because node encompasses the AABB 52 | ASSERT_TRUE(filter.IsNodeValid({1, 1}, 10)); 53 | // valid 54 | ASSERT_TRUE(filter.IsNodeValid({7, 7}, 1)); 55 | // invalid 56 | ASSERT_FALSE(filter.IsNodeValid({88, 5}, 1)); 57 | 58 | ASSERT_TRUE(filter.IsEntryValid({3, 7}, nullptr)); 59 | ASSERT_FALSE(filter.IsEntryValid({2, 8}, nullptr)); 60 | } 61 | 62 | TEST(PhTreeFilterTest, FilterNoOpSmokeTest) { 63 | auto filter = FilterNoOp(); 64 | ASSERT_TRUE(filter.IsNodeValid>({3, 7, 2}, 10)); 65 | ASSERT_TRUE(filter.IsEntryValid>({3, 7, 2}, 10)); 66 | } -------------------------------------------------------------------------------- /phtree/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 "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 | 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 | 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 | 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 | -------------------------------------------------------------------------------- /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 { 32 | 33 | namespace { 34 | template 35 | using PhFlatMapPair = std::pair; 36 | 37 | using index_t = std::int32_t; 38 | } // namespace 39 | 40 | /* 41 | * The sparse_map is a flat map implementation that uses an array of *at* *most* SIZE=2^DIM. 42 | * The array contains a list sorted by key. 43 | * 44 | * It has O(log n) lookup and O(n) insertion/removal time complexity, space complexity is O(n). 45 | */ 46 | template 47 | class sparse_map { 48 | public: 49 | explicit sparse_map() : data_{} {}; 50 | 51 | [[nodiscard]] auto find(size_t key) { 52 | auto it = lower_bound(key); 53 | if (it != data_.end() && it->first == key) { 54 | return it; 55 | } 56 | return data_.end(); 57 | } 58 | 59 | [[nodiscard]] auto find(size_t key) const { 60 | auto it = lower_bound(key); 61 | if (it != data_.end() && it->first == key) { 62 | return it; 63 | } 64 | return data_.end(); 65 | } 66 | 67 | [[nodiscard]] auto lower_bound(size_t key) { 68 | return std::lower_bound( 69 | data_.begin(), data_.end(), key, [](PhFlatMapPair& left, const size_t key) { 70 | return left.first < key; 71 | }); 72 | } 73 | 74 | [[nodiscard]] auto lower_bound(size_t key) const { 75 | return std::lower_bound( 76 | data_.cbegin(), data_.cend(), key, [](const PhFlatMapPair& left, const size_t key) { 77 | return left.first < key; 78 | }); 79 | } 80 | 81 | [[nodiscard]] auto begin() { 82 | return data_.begin(); 83 | } 84 | 85 | [[nodiscard]] auto begin() const { 86 | return cbegin(); 87 | } 88 | 89 | [[nodiscard]] auto cbegin() const { 90 | return data_.cbegin(); 91 | } 92 | 93 | [[nodiscard]] auto end() { 94 | return data_.end(); 95 | } 96 | 97 | [[nodiscard]] auto end() const { 98 | return data_.end(); 99 | } 100 | 101 | template 102 | auto emplace(Args&&... args) { 103 | return try_emplace_base(std::forward(args)...); 104 | } 105 | 106 | template 107 | auto try_emplace(size_t key, Args&&... args) { 108 | return try_emplace_base(key, std::forward(args)...); 109 | } 110 | 111 | void erase(size_t key) { 112 | auto it = lower_bound(key); 113 | if (it != end() && it->first == key) { 114 | data_.erase(it); 115 | } 116 | } 117 | 118 | void erase(const typename std::vector>::iterator& iterator) { 119 | data_.erase(iterator); 120 | } 121 | 122 | [[nodiscard]] size_t size() const { 123 | return data_.size(); 124 | } 125 | 126 | private: 127 | template 128 | auto emplace_base(size_t key, Args&&... args) { 129 | auto it = lower_bound(key); 130 | if (it != end() && it->first == key) { 131 | return std::make_pair(it, false); 132 | } else { 133 | return std::make_pair(data_.emplace(it, key, std::forward(args)...), true); 134 | } 135 | } 136 | 137 | template 138 | auto try_emplace_base(size_t key, Args&&... args) { 139 | auto it = lower_bound(key); 140 | if (it != end() && it->first == key) { 141 | return std::make_pair(it, false); 142 | } else { 143 | auto x = data_.emplace( 144 | it, 145 | std::piecewise_construct, 146 | std::forward_as_tuple(key), 147 | std::forward_as_tuple(std::forward(args)...)); 148 | return std::make_pair(x, true); 149 | } 150 | } 151 | 152 | std::vector> data_; 153 | }; 154 | 155 | } // namespace improbable::phtree 156 | 157 | #endif // PHTREE_COMMON_FLAT_SPARSE_MAP_H 158 | -------------------------------------------------------------------------------- /phtree/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 "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 | 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 | 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 | -------------------------------------------------------------------------------- /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 << " (" << (MAX_BIT_WIDTH - apl) << ")" << std::endl; 43 | return s.str(); 44 | } 45 | 46 | std::string ToStringHist() { 47 | std::ostringstream s; 48 | s << " infix_len = "; 49 | to_string(s, infix_hist_) << std::endl; 50 | s << " nodeSizeLog = "; 51 | to_string(s, node_size_log_hist_) << std::endl; 52 | s << " node_depth_hist_ = "; 53 | to_string(s, node_depth_hist_) << std::endl; 54 | s << " depthHist = "; 55 | to_string(s, q_n_post_fix_n_) << std::endl; 56 | return s.str(); 57 | } 58 | 59 | /* 60 | * @return average postfix_len, including the HC/LHC bit. 61 | */ 62 | double GetAvgPostlen() { 63 | size_t total = 0; 64 | size_t num_entry = 0; 65 | for (bit_width_t i = 0; i < MAX_BIT_WIDTH; ++i) { 66 | total += (MAX_BIT_WIDTH - i) * q_n_post_fix_n_[i]; 67 | num_entry += q_n_post_fix_n_[i]; 68 | } 69 | return (double)total / (double)num_entry; 70 | } 71 | 72 | size_t GetNodeCount() { 73 | return n_nodes_; 74 | } 75 | 76 | size_t GetCalculatedMemSize() { 77 | return size_; 78 | } 79 | 80 | private: 81 | static std::ostringstream& to_string(std::ostringstream& s, std::vector& data) { 82 | s << "["; 83 | for (size_t x : data) { 84 | s << x << ","; 85 | } 86 | s << "]"; 87 | return s; 88 | } 89 | 90 | public: 91 | size_t n_nodes_ = 0; 92 | size_t n_AHC_ = 0; // AHC nodes (formerly Nodes with AHC-postfix representation) 93 | size_t n_nt_nodes_ = 0; // NtNodes (formerly Nodes with sub-HC representation) 94 | size_t n_nt_ = 0; // nodes with NT representation 95 | size_t n_total_children_ = 0; 96 | size_t size_ = 0; // calculated size in bytes 97 | size_t q_total_depth_ = 0; 98 | std::vector q_n_post_fix_n_ = 99 | std::vector(MAX_BIT_WIDTH, (size_t)0); // filled with x[current_depth] = nPost; 100 | std::vector infix_hist_ = std::vector(MAX_BIT_WIDTH, (size_t)0); // prefix len 101 | std::vector node_depth_hist_ = 102 | std::vector(MAX_BIT_WIDTH, (size_t)0); // prefix len 103 | std::vector node_size_log_hist_ = std::vector(32, (size_t)0); // log (num_entries) 104 | }; 105 | 106 | } // namespace improbable::phtree 107 | #endif // PHTREE_COMMON_TREE_STATS_H 108 | -------------------------------------------------------------------------------- /phtree/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 | template 24 | using TestPoint = PhBoxD; 25 | 26 | template 27 | using TestTree = PhTreeBoxD; 28 | 29 | TEST(PhTreeBoxDTestQueryTypes, SmokeTestQuery) { 30 | const dimension_t DIM = 2; 31 | TestTree tree; 32 | 33 | PhPointD p00{-10, -10}; 34 | PhPointD p11{10, 10}; 35 | PhPointD pm{0, 0}; 36 | 37 | PhBoxD b00{p00, pm}; 38 | PhBoxD b11{pm, p11}; 39 | 40 | tree.emplace(b00, -1); 41 | tree.emplace(b11, 1); 42 | 43 | auto query_type = QueryInclude(); 44 | 45 | // empty 46 | auto q1 = tree.begin_query({{-9, -9}, {9, 9}}, FilterNoOp(), query_type); 47 | ASSERT_EQ(q1, tree.end()); 48 | 49 | // Find box00 but not box11 50 | auto q2 = tree.begin_query({{-11, -11}, {9, 9}}, FilterNoOp(), query_type); 51 | ASSERT_NE(q2, tree.end()); 52 | ASSERT_EQ(-1, (*q2)); 53 | q2++; 54 | ASSERT_EQ(q2, tree.end()); 55 | 56 | // Find box11 but not box00 57 | auto q3 = tree.begin_query({{-9, -9}, {11, 11}}, FilterNoOp(), query_type); 58 | ASSERT_NE(q3, tree.end()); 59 | ASSERT_EQ(1, (*q3)); 60 | q3++; 61 | ASSERT_EQ(q3, tree.end()); 62 | } 63 | -------------------------------------------------------------------------------- /phtree/phtree_d_test_filter.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 | #include 21 | 22 | using namespace improbable::phtree; 23 | 24 | template 25 | using TestPoint = PhPointD; 26 | 27 | template 28 | using TestTree = PhTreeD; 29 | 30 | class DoubleRng { 31 | public: 32 | DoubleRng(double minIncl, double maxExcl) : eng(), rnd{minIncl, maxExcl} {} 33 | 34 | double next() { 35 | return rnd(eng); 36 | } 37 | 38 | private: 39 | std::default_random_engine eng; 40 | std::uniform_real_distribution rnd; 41 | }; 42 | 43 | template 44 | void generateCube(std::vector>& points, size_t N) { 45 | DoubleRng rng(-1000, 1000); 46 | auto refTree = std::map, size_t>(); 47 | 48 | points.reserve(N); 49 | for (size_t i = 0; i < N; i++) { 50 | auto point = TestPoint{rng.next(), rng.next(), rng.next()}; 51 | if (refTree.count(point) != 0) { 52 | i--; 53 | continue; 54 | } 55 | 56 | refTree.emplace(point, i); 57 | points.push_back(point); 58 | } 59 | ASSERT_EQ(refTree.size(), N); 60 | ASSERT_EQ(points.size(), N); 61 | } 62 | 63 | template 64 | void populate(TestTree& tree, std::vector>& points, size_t N) { 65 | generateCube(points, N); 66 | for (size_t i = 0; i < N; i++) { 67 | ASSERT_TRUE(tree.insert(points[i], i).second); 68 | } 69 | ASSERT_EQ(N, tree.size()); 70 | } 71 | -------------------------------------------------------------------------------- /phtree/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 | template 24 | using TestPoint = PhPointD; 25 | 26 | template 27 | using TestTree = PhTreeD>; 28 | 29 | class DoubleRng { 30 | public: 31 | DoubleRng(double minIncl, double maxExcl) : eng(), rnd{minIncl, maxExcl} {} 32 | 33 | double next() { 34 | return rnd(eng); 35 | } 36 | 37 | private: 38 | std::default_random_engine eng; 39 | std::uniform_real_distribution rnd; 40 | }; 41 | 42 | struct Id { 43 | Id() = default; 44 | 45 | explicit Id(const int i) : _i(i){}; 46 | 47 | bool operator==(Id& rhs) { 48 | return _i == rhs._i; 49 | } 50 | 51 | Id& operator=(Id const& rhs) = default; 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 | -------------------------------------------------------------------------------- /phtree/phtree_test_unique_ptr_values.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 | template 24 | using TestPoint = PhPoint; 25 | 26 | template 27 | using TestTree = PhTree; 28 | 29 | class IntRng { 30 | public: 31 | IntRng(int minIncl, int maxExcl) : eng(7), rnd{minIncl, maxExcl} {} 32 | 33 | int next() { 34 | return rnd(eng); 35 | } 36 | 37 | private: 38 | std::default_random_engine eng; 39 | std::uniform_int_distribution rnd; 40 | }; 41 | 42 | struct IdObj { 43 | IdObj() = default; 44 | 45 | explicit IdObj(const size_t i) : _i(static_cast(i)){}; 46 | 47 | bool operator==(IdObj& rhs) { 48 | return _i == rhs._i; 49 | } 50 | 51 | IdObj& operator=(IdObj const& rhs) = default; 52 | 53 | int _i; 54 | }; 55 | 56 | using Id = std::unique_ptr; 57 | 58 | struct PointDistance { 59 | PointDistance(double distance, size_t id) : _distance(distance), _id(id) {} 60 | 61 | double _distance; 62 | size_t _id; 63 | }; 64 | 65 | bool comparePointDistance(PointDistance& i1, PointDistance& i2) { 66 | return (i1._distance < i2._distance); 67 | } 68 | 69 | template 70 | double distance(const TestPoint& p1, const TestPoint& p2) { 71 | double sum2 = 0; 72 | for (dimension_t i = 0; i < DIM; i++) { 73 | double d = p1[i] - p2[i]; 74 | sum2 += d * d; 75 | } 76 | return sqrt(sum2); 77 | } 78 | 79 | template 80 | double distanceL1(const TestPoint& p1, const TestPoint& p2) { 81 | double sum = 0; 82 | for (dimension_t i = 0; i < DIM; i++) { 83 | sum += std::abs(p1[i] - p2[i]); 84 | } 85 | return sum; 86 | } 87 | 88 | template 89 | void generateCube(std::vector>& points, size_t N) { 90 | IntRng rng(-1000, 1000); 91 | auto refTree = std::map, size_t>(); 92 | 93 | points.reserve(N); 94 | for (size_t i = 0; i < N; i++) { 95 | auto point = TestPoint{rng.next(), rng.next(), rng.next()}; 96 | if (refTree.count(point) != 0) { 97 | i--; 98 | continue; 99 | } 100 | 101 | refTree.emplace(point, i); 102 | points.push_back(point); 103 | } 104 | ASSERT_EQ(refTree.size(), N); 105 | ASSERT_EQ(points.size(), N); 106 | } 107 | 108 | template 109 | void SmokeTestBasicOps(size_t N) { 110 | TestTree tree; 111 | std::vector> points; 112 | generateCube(points, N); 113 | 114 | ASSERT_EQ(0, tree.size()); 115 | ASSERT_TRUE(tree.empty()); 116 | PhTreeDebugHelper::CheckConsistency(tree); 117 | 118 | for (size_t i = 0; i < N; i++) { 119 | TestPoint& p = points.at(i); 120 | ASSERT_EQ(tree.count(p), 0); 121 | ASSERT_EQ(tree.end(), tree.find(p)); 122 | 123 | if (i % 2 == 0) { 124 | ASSERT_TRUE(tree.emplace(p, std::make_unique(i)).second); 125 | } else { 126 | Id id = std::make_unique(i); 127 | ASSERT_TRUE(tree.emplace(p, std::move(id)).second); 128 | } 129 | ASSERT_EQ(tree.count(p), 1); 130 | ASSERT_NE(tree.end(), tree.find(p)); 131 | ASSERT_EQ(i, (*tree.find(p))->_i); 132 | ASSERT_EQ(i + 1, tree.size()); 133 | 134 | // try add again 135 | ASSERT_FALSE(tree.emplace(p, std::make_unique(i)).second); 136 | ASSERT_EQ(tree.count(p), 1); 137 | ASSERT_NE(tree.end(), tree.find(p)); 138 | ASSERT_EQ(i, (*tree.find(p))->_i); 139 | ASSERT_EQ(i + 1, tree.size()); 140 | ASSERT_FALSE(tree.empty()); 141 | } 142 | 143 | for (size_t i = 0; i < N; i++) { 144 | TestPoint& p = points.at(i); 145 | auto q = tree.begin_query({p, p}); 146 | ASSERT_NE(q, tree.end()); 147 | ASSERT_EQ(i, (*q)->_i); 148 | q++; 149 | ASSERT_EQ(q, tree.end()); 150 | } 151 | 152 | PhTreeDebugHelper::CheckConsistency(tree); 153 | 154 | for (size_t i = 0; i < N; i++) { 155 | TestPoint& p = points.at(i); 156 | ASSERT_NE(tree.find(p), tree.end()); 157 | ASSERT_EQ(tree.count(p), 1); 158 | ASSERT_EQ(i, (*tree.find(p))->_i); 159 | ASSERT_EQ(1, tree.erase(p)); 160 | 161 | ASSERT_EQ(tree.count(p), 0); 162 | ASSERT_EQ(tree.end(), tree.find(p)); 163 | ASSERT_EQ(N - i - 1, tree.size()); 164 | 165 | // try remove again 166 | ASSERT_EQ(0, tree.erase(p)); 167 | ASSERT_EQ(tree.count(p), 0); 168 | ASSERT_EQ(tree.end(), tree.find(p)); 169 | ASSERT_EQ(N - i - 1, tree.size()); 170 | if (i < N - 1) { 171 | ASSERT_FALSE(tree.empty()); 172 | } 173 | } 174 | ASSERT_EQ(0, tree.size()); 175 | ASSERT_TRUE(tree.empty()); 176 | PhTreeDebugHelper::CheckConsistency(tree); 177 | } 178 | 179 | TEST(PhTreeTestUniquePtr, SmokeTestBasicOps) { 180 | SmokeTestBasicOps<3>(10000); 181 | SmokeTestBasicOps<6>(10000); 182 | SmokeTestBasicOps<10>(1000); 183 | SmokeTestBasicOps<20>(100); 184 | } 185 | -------------------------------------------------------------------------------- /phtree/testing/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:private"]) 2 | 3 | cc_library( 4 | name = "testing", 5 | testonly = True, 6 | srcs = [ 7 | ], 8 | hdrs = [ 9 | ], 10 | visibility = [ 11 | ], 12 | deps = [ 13 | ], 14 | ) 15 | -------------------------------------------------------------------------------- /phtree/testing/gtest_main/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:private"]) 2 | 3 | cc_library( 4 | name = "gtest_main", 5 | testonly = True, 6 | srcs = ["gtest_main.cc"], 7 | visibility = [ 8 | "//visibility:public", 9 | ], 10 | deps = [ 11 | "@gtest", 12 | ], 13 | alwayslink = 1, 14 | ) 15 | -------------------------------------------------------------------------------- /phtree/testing/gtest_main/gtest_main.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 18 | 19 | int main(int argc, char** argv) { 20 | testing::InitGoogleMock(&argc, argv); 21 | return RUN_ALL_TESTS(); 22 | } 23 | -------------------------------------------------------------------------------- /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_simple.h", 17 | "node.h", 18 | "phtree_v16.h", 19 | ], 20 | visibility = [ 21 | "//visibility:public", 22 | ], 23 | deps = [ 24 | "//phtree/common", 25 | ], 26 | ) 27 | -------------------------------------------------------------------------------- /phtree/v16/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | 3 | target_sources(phtree 4 | PRIVATE 5 | debug_helper_v16.h 6 | node.h 7 | entry.h 8 | iterator_base.h 9 | iterator_full.h 10 | iterator_hc.h 11 | iterator_knn_hs.h 12 | iterator_simple.h 13 | phtree_v16.h 14 | ) 15 | -------------------------------------------------------------------------------- /phtree/v16/debug_helper_v16.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_DEBUG_HELPER_H 18 | #define PHTREE_V16_DEBUG_HELPER_H 19 | 20 | #include "../common/common.h" 21 | #include "../common/debug_helper.h" 22 | #include "node.h" 23 | #include "phtree_v16.h" 24 | #include 25 | 26 | namespace improbable::phtree::v16 { 27 | 28 | template 29 | class PhTreeV16; 30 | 31 | template 32 | class DebugHelperV16 : public PhTreeDebugHelper::DebugHelper { 33 | using KeyT = PhPoint; 34 | using NodeT = Node; 35 | 36 | public: 37 | DebugHelperV16(const NodeT& root, size_t size) : root_{root}, size_{size} {} 38 | 39 | /* 40 | * Depending on the detail parameter this returns: 41 | * - "name" : a string that identifies the tree implementation type. 42 | * - "entries" : a string that lists all elements in the tree. 43 | * - "tree" : a string that lists all elements in the tree, pretty formatted to indicate tree 44 | * structure. 45 | * 46 | * @return a string that identifies the tree implementation type. 47 | */ 48 | [[nodiscard]] std::string ToString( 49 | const PhTreeDebugHelper::PrintDetail& detail) const override { 50 | using Enum = PhTreeDebugHelper::PrintDetail; 51 | std::ostringstream os; 52 | switch (detail) { 53 | case Enum::name: 54 | os << "PH-TreeV16-C++"; 55 | break; 56 | case Enum::entries: 57 | ToStringPlain(os, root_); 58 | break; 59 | case Enum::tree: 60 | ToStringTree(os, 0, root_, KeyT{}, true); 61 | break; 62 | } 63 | return os.str(); 64 | } 65 | 66 | /* 67 | * Collects some statistics about the tree, such as number of nodes, average depth, ... 68 | * 69 | * @return some statistics about the tree. 70 | */ 71 | [[nodiscard]] PhTreeStats GetStats() const override { 72 | PhTreeStats stats; 73 | root_.GetStats(stats); 74 | return stats; 75 | } 76 | 77 | /* 78 | * Checks the consistency of the tree. This function requires assertions to be enabled. 79 | */ 80 | void CheckConsistency() const override { 81 | assert(size_ == root_.CheckConsistency()); 82 | } 83 | 84 | private: 85 | void ToStringPlain(std::ostringstream& os, const NodeT& node) const { 86 | for (auto& it : node.Entries()) { 87 | const auto& o = it.second; 88 | // inner node? 89 | if (o.IsNode()) { 90 | ToStringPlain(os, o.GetNode()); 91 | } else { 92 | os << o.GetKey(); 93 | os << " v=" << (o.IsValue() ? "T" : "null") << std::endl; 94 | } 95 | } 96 | } 97 | 98 | void ToStringTree( 99 | std::ostringstream& sb, 100 | bit_width_t current_depth, 101 | const NodeT& node, 102 | const KeyT& prefix, 103 | bool printValue) const { 104 | std::string ind = "*"; 105 | for (bit_width_t i = 0; i < current_depth; ++i) { 106 | ind += "-"; 107 | } 108 | sb << ind << "il=" << node.GetInfixLen() << " pl=" << node.GetPostfixLen() 109 | << " ec=" << node.GetEntryCount() << " inf=["; 110 | 111 | // for a leaf node, the existence of a sub just indicates that the value exists. 112 | if (node.GetInfixLen() > 0) { 113 | bit_mask_t mask = MAX_MASK << node.GetInfixLen(); 114 | mask = ~mask; 115 | mask <<= node.GetPostfixLen() + 1; 116 | for (dimension_t i = 0; i < DIM; ++i) { 117 | sb << ToBinary(prefix[i] & mask) << ","; 118 | } 119 | } 120 | current_depth += node.GetInfixLen(); 121 | sb << "] " 122 | << "Node___il=" << node.GetInfixLen() << ";pl=" << node.GetPostfixLen() 123 | << ";size=" << node.Entries().size() << std::endl; 124 | 125 | // To clean previous postfixes. 126 | for (auto& it : node.Entries()) { 127 | const auto& o = it.second; 128 | hc_pos_t hcPos = it.first; 129 | if (o.IsNode()) { 130 | sb << ind << "# " << hcPos << " Node: " << std::endl; 131 | ToStringTree(sb, current_depth + 1, o.GetNode(), o.GetKey(), printValue); 132 | } else { 133 | // post-fix 134 | sb << ind << ToBinary(o.GetKey()); 135 | sb << " hcPos=" << hcPos; 136 | if (printValue) { 137 | sb << " v=" << (o.IsValue() ? "T" : "null"); 138 | } 139 | sb << std::endl; 140 | } 141 | } 142 | } 143 | 144 | const NodeT& root_; 145 | const size_t size_; 146 | }; 147 | } // namespace improbable::phtree::v16 148 | 149 | #endif // PHTREE_V16_DEBUG_HELPER_H 150 | -------------------------------------------------------------------------------- /phtree/v16/entry.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_ENTRY_H 18 | #define PHTREE_V16_ENTRY_H 19 | 20 | #include "../../phtree/common/common.h" 21 | #include "node.h" 22 | #include 23 | #include 24 | #include 25 | 26 | namespace improbable::phtree::v16 { 27 | 28 | template 29 | class Node; 30 | 31 | /* 32 | * Nodes in the PH-Tree contain up to 2^DIM PhEntries, one in each geometric quadrant. 33 | * PhEntries can contain two types of data: 34 | * - A key/value pair (value of type T) 35 | * - A prefix/child-node pair, where prefix is the prefix of the child node and the 36 | * child node is contained in a unique_ptr. 37 | */ 38 | template 39 | class Entry { 40 | using KeyT = PhPoint; 41 | using ValueT = std::remove_const_t; 42 | using NodeT = Node; 43 | 44 | public: 45 | /* 46 | * Construct entry with existing node. 47 | */ 48 | Entry(const KeyT& k, std::unique_ptr&& node_ptr) 49 | : kd_key_{k}, node_{std::move(node_ptr)}, value_{std::nullopt} {} 50 | 51 | /* 52 | * Construct entry with a new node. 53 | */ 54 | Entry(bit_width_t infix_len, bit_width_t postfix_len) 55 | : kd_key_(), node_{std::make_unique(infix_len, postfix_len)}, value_{std::nullopt} {} 56 | 57 | /* 58 | * Construct entry with existing T. 59 | */ 60 | Entry(const KeyT& k, std::optional&& value) 61 | : kd_key_{k}, node_{nullptr}, value_{std::move(value)} {} 62 | 63 | /* 64 | * Construct entry with new T or moved T. 65 | */ 66 | template 67 | explicit Entry(const KeyT& k, Args&&... args) 68 | : kd_key_{k}, node_{nullptr}, value_{std::in_place, std::forward(args)...} {} 69 | 70 | [[nodiscard]] const KeyT& GetKey() const { 71 | return kd_key_; 72 | } 73 | 74 | [[nodiscard]] bool IsValue() const { 75 | return value_.has_value(); 76 | } 77 | 78 | [[nodiscard]] bool IsNode() const { 79 | return node_.get() != nullptr; 80 | } 81 | 82 | [[nodiscard]] T& GetValue() const { 83 | assert(IsValue()); 84 | return const_cast(*value_); 85 | } 86 | 87 | [[nodiscard]] NodeT& GetNode() const { 88 | assert(IsNode()); 89 | return *node_; 90 | } 91 | 92 | void SetNode(std::unique_ptr&& node) { 93 | assert(!IsNode()); 94 | node_ = std::move(node); 95 | value_.reset(); 96 | } 97 | 98 | [[nodiscard]] std::optional&& ExtractValue() { 99 | assert(IsValue()); 100 | return std::move(value_); 101 | } 102 | 103 | [[nodiscard]] std::unique_ptr&& ExtractNode() { 104 | assert(IsNode()); 105 | return std::move(node_); 106 | } 107 | 108 | void ReplaceNodeWithDataFromEntry(Entry&& other) { 109 | assert(IsNode()); 110 | kd_key_ = other.GetKey(); 111 | 112 | if (other.IsNode()) { 113 | node_ = std::move(other.node_); 114 | } else { 115 | value_ = std::move(other.value_); 116 | node_.reset(); 117 | } 118 | } 119 | 120 | private: 121 | KeyT kd_key_; 122 | std::unique_ptr node_; 123 | std::optional value_; 124 | }; 125 | } // namespace improbable::phtree::v16 126 | 127 | #endif // PHTREE_V16_ENTRY_H 128 | -------------------------------------------------------------------------------- /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 "../common/common.h" 21 | #include "iterator_simple.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 KeyExternal = typename CONVERT::KeyExternal; 33 | using KeyInternal = typename CONVERT::KeyInternal; 34 | using SCALAR = typename CONVERT::ScalarInternal; 35 | using EntryT = Entry; 36 | using NodeT = Node; 37 | 38 | public: 39 | ForEach(const CONVERT& converter, CALLBACK_FN& callback, FILTER filter) 40 | : converter_{converter}, callback_{callback}, filter_(std::move(filter)) {} 41 | 42 | void run(const EntryT& root) { 43 | assert(root.IsNode()); 44 | TraverseNode(root.GetKey(), root.GetNode()); 45 | } 46 | 47 | private: 48 | void TraverseNode(const KeyInternal& key, const NodeT& node) { 49 | auto iter = node.Entries().begin(); 50 | auto end = node.Entries().end(); 51 | for (; iter != end; ++iter) { 52 | const auto& child = iter->second; 53 | const auto& child_key = child.GetKey(); 54 | if (child.IsNode()) { 55 | const auto& child_node = child.GetNode(); 56 | if (filter_.IsNodeValid(key, node.GetPostfixLen() + 1)) { 57 | TraverseNode(child_key, child_node); 58 | } 59 | } else { 60 | T& value = child.GetValue(); 61 | if (filter_.IsEntryValid(key, value)) { 62 | callback_(converter_.post(child_key), value); 63 | } 64 | } 65 | } 66 | } 67 | 68 | CONVERT converter_; 69 | CALLBACK_FN& callback_; 70 | FILTER filter_; 71 | }; 72 | } // namespace improbable::phtree::v16 73 | 74 | #endif // PHTREE_V16_FOR_EACH_H 75 | -------------------------------------------------------------------------------- /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 "../common/common.h" 21 | #include "entry.h" 22 | 23 | namespace improbable::phtree::v16 { 24 | 25 | template 26 | class PhTreeV16; 27 | 28 | /* 29 | * Base class for all PH-Tree iterators. 30 | */ 31 | template 32 | class IteratorBase { 33 | protected: 34 | static constexpr dimension_t DIM = CONVERT::DimInternal; 35 | using KeyInternal = typename CONVERT::KeyInternal; 36 | using SCALAR = typename CONVERT::ScalarInternal; 37 | using EntryT = Entry; 38 | friend PhTreeV16; 39 | 40 | public: 41 | explicit IteratorBase(const CONVERT& converter) 42 | : current_result_{nullptr} 43 | , current_node_{} 44 | , parent_node_{} 45 | , is_finished_{false} 46 | , converter_{converter} 47 | , filter_{FILTER()} {} 48 | 49 | explicit IteratorBase(const CONVERT& converter, FILTER filter) 50 | : current_result_{nullptr} 51 | , current_node_{} 52 | , parent_node_{} 53 | , is_finished_{false} 54 | , converter_{converter} 55 | , filter_(std::move(filter)) {} 56 | 57 | T& operator*() const { 58 | assert(current_result_); 59 | return current_result_->GetValue(); 60 | } 61 | 62 | T* operator->() const { 63 | assert(current_result_); 64 | return ¤t_result_->GetValue(); 65 | } 66 | 67 | template 68 | friend bool operator==( 69 | const IteratorBase& left, 70 | const IteratorBase& right) { 71 | // Note: The following compares pointers to Entry objects so it should be 72 | // a) fast (i.e. not comparing contents of entries) 73 | // b) return `false` when comparing apparently identical entries from different PH-Trees (as 74 | // intended) 75 | return (left.is_finished_ && right.Finished()) || 76 | (!left.is_finished_ && !right.Finished() && 77 | left.current_result_ == right.GetCurrentResult()); 78 | } 79 | 80 | template 81 | friend bool operator!=( 82 | const IteratorBase& left, 83 | const IteratorBase& right) { 84 | return !(left == right); 85 | } 86 | 87 | auto first() const { 88 | return converter_.post(current_result_->GetKey()); 89 | } 90 | 91 | T& second() const { 92 | return current_result_->GetValue(); 93 | } 94 | 95 | [[nodiscard]] bool Finished() const { 96 | return is_finished_; 97 | } 98 | 99 | const EntryT* GetCurrentResult() const { 100 | return current_result_; 101 | } 102 | 103 | protected: 104 | void SetFinished() { 105 | is_finished_ = true; 106 | current_result_ = nullptr; 107 | } 108 | 109 | [[nodiscard]] bool ApplyFilter(const EntryT& entry) const { 110 | return entry.IsNode() 111 | ? filter_.IsNodeValid(entry.GetKey(), entry.GetNode().GetPostfixLen() + 1) 112 | : filter_.IsEntryValid(entry.GetKey(), entry.GetValue()); 113 | } 114 | 115 | void SetCurrentResult(const EntryT* current_result) { 116 | current_result_ = current_result; 117 | } 118 | 119 | void SetCurrentNodeEntry(const EntryT* current_node) { 120 | assert(!current_node || current_node->IsNode()); 121 | current_node_ = current_node; 122 | } 123 | 124 | void SetParentNodeEntry(const EntryT* parent_node) { 125 | assert(!parent_node || parent_node->IsNode()); 126 | parent_node_ = parent_node; 127 | } 128 | 129 | auto post(const KeyInternal& point) { 130 | return converter_.post(point); 131 | } 132 | 133 | private: 134 | /* 135 | * The parent entry contains the parent node. The parent node is the node ABOVE the current node 136 | * which contains the current entry. 137 | */ 138 | const EntryT* GetCurrentNodeEntry() const { 139 | return current_node_; 140 | } 141 | 142 | const EntryT* GetParentNodeEntry() const { 143 | return parent_node_; 144 | } 145 | 146 | const EntryT* current_result_; 147 | const EntryT* current_node_; 148 | const EntryT* parent_node_; 149 | bool is_finished_; 150 | const CONVERT& converter_; 151 | FILTER filter_; 152 | }; 153 | 154 | } // namespace improbable::phtree::v16 155 | 156 | #endif // PHTREE_V16_ITERATOR_BASE_H 157 | -------------------------------------------------------------------------------- /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 "../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 IteratorBase { 30 | static constexpr dimension_t DIM = CONVERT::DimInternal; 31 | using SCALAR = typename CONVERT::ScalarInternal; 32 | using NodeT = Node; 33 | using EntryT = typename IteratorBase::EntryT; 34 | 35 | public: 36 | IteratorFull(const EntryT& root, const CONVERT& converter, FILTER filter) 37 | : IteratorBase(converter, filter), stack_{}, stack_size_{0} { 38 | PrepareAndPush(root.GetNode()); 39 | FindNextElement(); 40 | } 41 | 42 | IteratorFull& operator++() { 43 | FindNextElement(); 44 | return *this; 45 | } 46 | 47 | IteratorFull operator++(int) { 48 | IteratorFull iterator(*this); 49 | ++(*this); 50 | return iterator; 51 | } 52 | 53 | private: 54 | void FindNextElement() { 55 | while (!IsEmpty()) { 56 | auto* p = &Peek(); 57 | while (*p != PeekEnd()) { 58 | auto& candidate = (*p)->second; 59 | ++(*p); 60 | if (this->ApplyFilter(candidate)) { 61 | if (candidate.IsNode()) { 62 | p = &PrepareAndPush(candidate.GetNode()); 63 | } else { 64 | this->SetCurrentResult(&candidate); 65 | return; 66 | } 67 | } 68 | } 69 | // return to parent node 70 | Pop(); 71 | } 72 | // finished 73 | this->SetFinished(); 74 | } 75 | 76 | auto& PrepareAndPush(const NodeT& node) { 77 | assert(stack_size_ < stack_.size() - 1); 78 | // No '&' because this is a temp value 79 | stack_[stack_size_].first = node.Entries().cbegin(); 80 | stack_[stack_size_].second = node.Entries().end(); 81 | ++stack_size_; 82 | return stack_[stack_size_ - 1].first; 83 | } 84 | 85 | auto& Peek() { 86 | assert(stack_size_ > 0); 87 | return stack_[stack_size_ - 1].first; 88 | } 89 | 90 | auto& PeekEnd() { 91 | assert(stack_size_ > 0); 92 | return stack_[stack_size_ - 1].second; 93 | } 94 | 95 | auto& Pop() { 96 | assert(stack_size_ > 0); 97 | return stack_[--stack_size_].first; 98 | } 99 | 100 | bool IsEmpty() { 101 | return stack_size_ == 0; 102 | } 103 | 104 | std::array< 105 | std::pair, EntryIteratorC>, 106 | MAX_BIT_WIDTH> 107 | stack_; 108 | size_t stack_size_; 109 | }; 110 | 111 | } // namespace improbable::phtree::v16 112 | 113 | #endif // PHTREE_V16_ITERATOR_FULL_H 114 | -------------------------------------------------------------------------------- /phtree/v16/iterator_knn_hs.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_QUERY_KNN_HS_H 18 | #define PHTREE_V16_QUERY_KNN_HS_H 19 | 20 | #include "../common/common.h" 21 | #include "iterator_base.h" 22 | #include 23 | 24 | namespace improbable::phtree::v16 { 25 | 26 | /* 27 | * kNN query implementation that uses preprocessors and distance functions. 28 | * 29 | * Implementation after Hjaltason and Samet (with some deviations: no MinDist or MaxDist used). 30 | * G. R. Hjaltason and H. Samet., "Distance browsing in spatial databases.", ACM TODS 31 | * 24(2):265--318. 1999 32 | */ 33 | 34 | namespace { 35 | template 36 | using EntryDist = std::pair*>; 37 | 38 | template 39 | struct CompareEntryDistByDistance { 40 | bool operator()(const ENTRY& left, const ENTRY& right) const { 41 | return left.first > right.first; 42 | }; 43 | }; 44 | } // namespace 45 | 46 | template 47 | class IteratorKnnHS : public IteratorBase { 48 | static constexpr dimension_t DIM = CONVERT::DimInternal; 49 | using KeyExternal = typename CONVERT::KeyExternal; 50 | using KeyInternal = typename CONVERT::KeyInternal; 51 | using SCALAR = typename CONVERT::ScalarInternal; 52 | using EntryT = typename IteratorBase::EntryT; 53 | using EntryDistT = EntryDist; 54 | 55 | public: 56 | explicit IteratorKnnHS( 57 | const EntryT& root, 58 | size_t min_results, 59 | const KeyInternal& center, 60 | const CONVERT& converter, 61 | DISTANCE dist, 62 | FILTER filter) 63 | : IteratorBase(converter, filter) 64 | , center_{center} 65 | , center_post_{converter.post(center)} 66 | , current_distance_{std::numeric_limits::max()} 67 | , num_found_results_(0) 68 | , num_requested_results_(min_results) 69 | , distance_(std::move(dist)) { 70 | if (min_results <= 0 || root.GetNode().GetEntryCount() == 0) { 71 | this->SetFinished(); 72 | return; 73 | } 74 | 75 | // Initialize queue, use d=0 because every imaginable point lies inside the root Node 76 | queue_.emplace(0, &root); 77 | FindNextElement(); 78 | } 79 | 80 | [[nodiscard]] double distance() const { 81 | return current_distance_; 82 | } 83 | 84 | IteratorKnnHS& operator++() { 85 | FindNextElement(); 86 | return *this; 87 | } 88 | 89 | IteratorKnnHS operator++(int) { 90 | IteratorKnnHS iterator(*this); 91 | ++(*this); 92 | return iterator; 93 | } 94 | 95 | private: 96 | void FindNextElement() { 97 | while (num_found_results_ < num_requested_results_ && !queue_.empty()) { 98 | auto& candidate = queue_.top(); 99 | auto o = candidate.second; 100 | if (!o->IsNode()) { 101 | // data entry 102 | ++num_found_results_; 103 | this->SetCurrentResult(o); 104 | current_distance_ = candidate.first; 105 | // We need to pop() AFTER we processed the value, otherwise the reference is 106 | // overwritten. 107 | queue_.pop(); 108 | return; 109 | } else { 110 | // inner node 111 | auto& node = o->GetNode(); 112 | queue_.pop(); 113 | for (auto& entry : node.Entries()) { 114 | auto& e2 = entry.second; 115 | if (this->ApplyFilter(e2)) { 116 | if (e2.IsNode()) { 117 | auto& sub = e2.GetNode(); 118 | double d = DistanceToNode(e2.GetKey(), sub.GetPostfixLen() + 1); 119 | queue_.emplace(d, &e2); 120 | } else { 121 | double d = distance_(center_post_, this->post(e2.GetKey())); 122 | queue_.emplace(d, &e2); 123 | } 124 | } 125 | } 126 | } 127 | } 128 | this->SetFinished(); 129 | current_distance_ = std::numeric_limits::max(); 130 | } 131 | 132 | double DistanceToNode(const KeyInternal& prefix, int bits_to_ignore) { 133 | assert(bits_to_ignore < MAX_BIT_WIDTH); 134 | SCALAR mask_min = MAX_MASK << bits_to_ignore; 135 | SCALAR mask_max = ~mask_min; 136 | KeyInternal buf; 137 | // The following calculates the point inside of the node that is closest to center_. 138 | // If center is inside the node this returns center_, otherwise it finds a point on the 139 | // node's surface. 140 | for (dimension_t i = 0; i < DIM; ++i) { 141 | // if center_[i] is outside the node, return distance to closest edge, 142 | // otherwise return center_[i] itself (assume possible distance=0) 143 | SCALAR min = prefix[i] & mask_min; 144 | SCALAR max = prefix[i] | mask_max; 145 | buf[i] = min > center_[i] ? min : (max < center_[i] ? max : center_[i]); 146 | } 147 | return distance_(center_post_, this->post(buf)); 148 | } 149 | 150 | private: 151 | const KeyInternal center_; 152 | // center after post processing == the external representation 153 | const KeyExternal center_post_; 154 | double current_distance_; 155 | std::priority_queue, CompareEntryDistByDistance> 156 | queue_; 157 | int num_found_results_; 158 | int num_requested_results_; 159 | DISTANCE distance_; 160 | }; 161 | 162 | } // namespace improbable::phtree::v16 163 | 164 | #endif // PHTREE_V16_QUERY_KNN_HS_H 165 | -------------------------------------------------------------------------------- /phtree/v16/iterator_simple.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 "../common/common.h" 21 | #include "iterator_base.h" 22 | 23 | namespace improbable::phtree::v16 { 24 | 25 | template 26 | class IteratorSimple : public IteratorBase { 27 | static constexpr dimension_t DIM = CONVERT::DimInternal; 28 | using SCALAR = typename CONVERT::ScalarInternal; 29 | using EntryT = typename IteratorBase::EntryT; 30 | 31 | public: 32 | explicit IteratorSimple(const CONVERT& converter) : IteratorBase(converter) { 33 | this->SetFinished(); 34 | } 35 | 36 | explicit IteratorSimple( 37 | const EntryT* current_result, 38 | const EntryT* current_node, 39 | const EntryT* parent_node, 40 | CONVERT converter) 41 | : IteratorBase(converter) { 42 | if (current_result) { 43 | this->SetCurrentResult(current_result); 44 | this->SetCurrentNodeEntry(current_node); 45 | this->SetParentNodeEntry(parent_node); 46 | } else { 47 | this->SetFinished(); 48 | } 49 | } 50 | 51 | IteratorSimple& operator++() { 52 | this->SetFinished(); 53 | return *this; 54 | } 55 | 56 | IteratorSimple operator++(int) { 57 | IteratorSimple iterator(*this); 58 | ++(*this); 59 | return iterator; 60 | } 61 | }; 62 | 63 | template 64 | using IteratorEnd = IteratorSimple; 65 | 66 | } // namespace improbable::phtree::v16 67 | 68 | #endif // PHTREE_V16_ITERATOR_SIMPLE_H 69 | -------------------------------------------------------------------------------- /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/improbable-eng/phtree-cpp/141f33566449a67a51c1dcda240ff77137df2355/tools/BUILD -------------------------------------------------------------------------------- /tools/bazel: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | TOOLS_DIR="$(dirname "$0")" 4 | 5 | source "${TOOLS_DIR}"/../ci/includes/os.sh 6 | source "${TOOLS_DIR}"/../ci/includes/bazel.sh 7 | 8 | # All information required for the script to select or, if necessary, install bazel is contained 9 | # in this code block. 10 | # If a higher version of bazel is required, update `REQUIRED_BAZEL_VERSION` and the 11 | # `REQUIRED_BAZEL_SHA256` values for each platform. 12 | REQUIRED_BAZEL_VERSION="$(getBazelVersion)" 13 | BAZEL_INSTALLATION_DIR="${HOME}/.bazel_installations/${REQUIRED_BAZEL_VERSION}" 14 | if isLinux; then 15 | DOWNLOAD_CMD="wget -q --no-clobber -O bazel" 16 | BAZEL_EXE="bazel-${REQUIRED_BAZEL_VERSION}-linux-x86_64" 17 | 18 | if which clang-10 1>/dev/null; then 19 | # We follow the symlink of clang-10 here to avoid a bug with the LLVM package when combined with -no-canonical-prefixes. 20 | export CC="$(readlink -f "$(which clang-10)")" 21 | else 22 | echo -e "\033[0;33mWarning: You don't seem to have clang-9 correctly installed. Please check README.md to ensure your compiler is set up correctly. Continuing with whatever compiler bazel detects, your mileage might vary.\033[0m" 23 | fi 24 | elif isMacOS; then 25 | DOWNLOAD_CMD="wget -q --no-clobber -O bazel" 26 | BAZEL_EXE="bazel-${REQUIRED_BAZEL_VERSION}-darwin-x86_64" 27 | else 28 | DOWNLOAD_CMD="curl -L -s -o bazel.exe" 29 | # Windows does not have an installer but retrieves the executable directly. 30 | BAZEL_EXE="bazel-${REQUIRED_BAZEL_VERSION}-windows-x86_64.exe" 31 | 32 | export BAZEL_VC="C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC" 33 | if [[ ! -d "$BAZEL_VC" ]]; then 34 | export BAZEL_VC="C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\VC" 35 | fi 36 | if [[ ! -d "$BAZEL_VC" ]]; then 37 | echo -e "\033[0;33mWarning: You don't seem to have Visual Studio 2019 installed correctly. Continuing with whatever compiler bazel detects, your mileage might vary.\033[0m" 38 | fi 39 | fi 40 | 41 | BAZEL_TARGET_PATH="${BAZEL_INSTALLATION_DIR}/bin/bazel" 42 | 43 | # Check if correct version is already installed. 44 | if [[ -f "${BAZEL_TARGET_PATH}" ]]; then 45 | if [[ ! -x "${BAZEL_TARGET_PATH}" ]]; then 46 | echo "ERROR: Bazel executable at '${BAZEL_TARGET_PATH}' does not have execute permission" 47 | stat "${BAZEL_TARGET_PATH}" 48 | exit 1 49 | fi 50 | BAZEL_SUBCOMMAND="$1" 51 | shift 52 | exec -a "$0" "${BAZEL_TARGET_PATH}" "$BAZEL_SUBCOMMAND" "$@" 53 | fi 54 | 55 | cat << EOM 56 | ================================================= 57 | Bazel version ${REQUIRED_BAZEL_VERSION} is not 58 | installed under ~/.bazel_installations 59 | 60 | Installing bazel ${REQUIRED_BAZEL_VERSION} now... 61 | ================================================= 62 | EOM 63 | 64 | # Create root directory if needed. 65 | if [[ ! -d "${BAZEL_INSTALLATION_DIR}" ]]; then 66 | echo "Installation directory created." 67 | mkdir -p "${BAZEL_INSTALLATION_DIR}" 68 | fi 69 | 70 | # Install correct bazel version. 71 | # If we don't have a local Bazel install at this point we need to retrieve the right version from GitHub. 72 | mkdir -p "${BAZEL_INSTALLATION_DIR}/bin/tmp" 73 | pushd "${BAZEL_INSTALLATION_DIR}/bin/tmp" 74 | rm bazel 2>/dev/null || true # Remove bazel binary if already present in tmp dir - indicates previous failed download. 75 | echo "Starting download of bazel ${REQUIRED_BAZEL_VERSION}..." 76 | ${DOWNLOAD_CMD} "https://github.com/bazelbuild/bazel/releases/download/${REQUIRED_BAZEL_VERSION}/${BAZEL_EXE}" 77 | echo "Download finished." 78 | # Mark downloaded file executable and move out of tmp directory. 79 | chmod a+x "bazel" 80 | mv bazel .. 81 | popd 82 | 83 | echo "Executing downloaded bazel..." 84 | BAZEL_SUBCOMMAND="$1" 85 | shift 86 | exec -a "$0" "${BAZEL_TARGET_PATH}" "$BAZEL_SUBCOMMAND" "$@" 87 | -------------------------------------------------------------------------------- /tools/build_rules/BUILD: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/improbable-eng/phtree-cpp/141f33566449a67a51c1dcda240ff77137df2355/tools/build_rules/BUILD -------------------------------------------------------------------------------- /tools/runners/BUILD: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/improbable-eng/phtree-cpp/141f33566449a67a51c1dcda240ff77137df2355/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/improbable-eng/phtree-cpp/141f33566449a67a51c1dcda240ff77137df2355/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/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/improbable-eng/phtree-cpp/141f33566449a67a51c1dcda240ff77137df2355/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/improbable-eng/phtree-cpp/141f33566449a67a51c1dcda240ff77137df2355/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 | --------------------------------------------------------------------------------