diff --git a/.bazelrc b/.bazelrc
index ce4a80e..c3f61f9 100644
--- a/.bazelrc
+++ b/.bazelrc
@@ -28,6 +28,7 @@ build:ci --announce_rc
#build:linux --copt="-march=skylake"
#build:linux --copt="-march=haswell"
#build:linux --copt="-march=native"
+#build:linux --copt="-fno-inline"
build:linux --copt="-fvisibility=hidden"
build:linux --copt="-fno-omit-frame-pointer" # for friendlier stack traces
build:linux --copt="-Wno-error"
diff --git a/README.md b/README.md
index 412e257..45ddc63 100644
--- a/README.md
+++ b/README.md
@@ -62,6 +62,10 @@ More information about PH-Trees (including a Java implementation) is available [
* [Theory](#theory)
+## License
+
+
+
----------------------------------
## API Usage
@@ -674,3 +678,15 @@ The PH-Tree is discussed in the following publications and reports:
- T. Zaeschke: "The PH-Tree Revisited", (2015)
- T. Zaeschke, M.C. Norrie: "Efficient Z-Ordered Traversal of Hypercube Indexes" (BTW 2017).
+## License
+
+
+
+The PH-tree is licensed under [Apache APL 2.0](LICENSE), except for code in
+[include/phtree/aux](include/phtree/aux).
+
+The code in [include/phtree/aux](include/phtree/aux) is based on code by
+Malte Skarupke (Copyright 2020) and is licensed under the
+[Boost Software License 1.0](https://www.boost.org/LICENSE_1_0.txt).
+
+
diff --git a/benchmark/BUILD b/benchmark/BUILD
index 4b84294..1152c5d 100644
--- a/benchmark/BUILD
+++ b/benchmark/BUILD
@@ -248,6 +248,21 @@ cc_binary(
],
)
+cc_binary(
+ name = "knn_mm_d_benchmark",
+ testonly = True,
+ srcs = [
+ "knn_mm_d_benchmark.cc",
+ ],
+ linkstatic = True,
+ deps = [
+ ":benchmark",
+ "//:phtree",
+ "@gbenchmark//:benchmark",
+ "@spdlog",
+ ],
+)
+
cc_binary(
name = "query_benchmark",
testonly = True,
diff --git a/benchmark/bpt_insert_benchmark.cc b/benchmark/bpt_insert_benchmark.cc
index 9e25b7b..da4a3b1 100644
--- a/benchmark/bpt_insert_benchmark.cc
+++ b/benchmark/bpt_insert_benchmark.cc
@@ -1,5 +1,5 @@
/*
-* Copyright 2022-2023 Tilmann Zäschke
+ * Copyright 2022-2023 Tilmann Zäschke
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
#include "benchmark_util.h"
#include "logging.h"
#include "phtree/common/b_plus_tree_hash_map.h"
+#include "phtree/common/b_plus_tree_heap.h"
#include "phtree/common/b_plus_tree_map.h"
#include "phtree/common/b_plus_tree_multimap.h"
#include
@@ -30,6 +31,7 @@ const int GLOBAL_MAX = 10000;
enum Scenario {
MAP,
MULTIMAP,
+ MULTIMAP2,
HASH_MAP,
STD_MAP,
STD_MULTIMAP,
@@ -118,7 +120,22 @@ void IndexBenchmark::SetupWorld(benchmark::State& state) {
template
void IndexBenchmark::Insert(benchmark::State& state, Index& tree) {
switch (TYPE) {
- default: {
+ case MAP: {
+ for (size_t i = 0; i < num_entities_; ++i) {
+ tree.emplace(points_[i][0], (payload_t)i);
+ }
+ break;
+ }
+ case MULTIMAP:
+ case MULTIMAP2:
+ case STD_MULTIMAP: {
+ for (size_t i = 0; i < num_entities_; ++i) {
+ tree.emplace(points_[i][0], (payload_t)i);
+ }
+ break;
+ }
+ case HASH_MAP:
+ case STD_MAP: {
for (size_t i = 0; i < num_entities_; ++i) {
tree.emplace(points_[i][0], (payload_t)i);
}
diff --git a/benchmark/hd_erase_d_benchmark.cc b/benchmark/hd_erase_d_benchmark.cc
index f2650c1..4f03e24 100644
--- a/benchmark/hd_erase_d_benchmark.cc
+++ b/benchmark/hd_erase_d_benchmark.cc
@@ -34,7 +34,7 @@ using payload_t = std::uint32_t;
template
class IndexBenchmark {
public:
- IndexBenchmark(benchmark::State& state);
+ explicit IndexBenchmark(benchmark::State& state);
void Benchmark(benchmark::State& state);
private:
diff --git a/benchmark/knn_d_benchmark.cc b/benchmark/knn_d_benchmark.cc
index dcf5abf..5c3b9f0 100644
--- a/benchmark/knn_d_benchmark.cc
+++ b/benchmark/knn_d_benchmark.cc
@@ -140,6 +140,12 @@ BENCHMARK_CAPTURE(PhTree3D, KNN_CU_10_of_10K, TestGenerator::CUBE, 10000, 10)
BENCHMARK_CAPTURE(PhTree3D, KNN_CU_10_of_1M, TestGenerator::CUBE, 1000000, 10)
->Unit(benchmark::kMillisecond);
+BENCHMARK_CAPTURE(PhTree3D, KNN_CU_100_of_10K, TestGenerator::CUBE, 10000, 100)
+ ->Unit(benchmark::kMillisecond);
+
+BENCHMARK_CAPTURE(PhTree3D, KNN_CU_100_of_1M, TestGenerator::CUBE, 1000000, 100)
+ ->Unit(benchmark::kMillisecond);
+
// index type, scenario name, data_type, num_entities, query_result_size
// PhTree 3D CLUSTER
BENCHMARK_CAPTURE(PhTree3D, KNN_CL_1_of_10K, TestGenerator::CLUSTER, 10000, 1)
@@ -154,4 +160,10 @@ BENCHMARK_CAPTURE(PhTree3D, KNN_CL_10_of_10K, TestGenerator::CLUSTER, 10000, 10)
BENCHMARK_CAPTURE(PhTree3D, KNN_CL_10_of_1M, TestGenerator::CLUSTER, 1000000, 10)
->Unit(benchmark::kMillisecond);
+BENCHMARK_CAPTURE(PhTree3D, KNN_CL_100_of_10K, TestGenerator::CLUSTER, 10000, 100)
+ ->Unit(benchmark::kMillisecond);
+
+BENCHMARK_CAPTURE(PhTree3D, KNN_CL_100_of_1M, TestGenerator::CLUSTER, 1000000, 100)
+ ->Unit(benchmark::kMillisecond);
+
BENCHMARK_MAIN();
diff --git a/benchmark/knn_mm_d_benchmark.cc b/benchmark/knn_mm_d_benchmark.cc
new file mode 100644
index 0000000..391fc59
--- /dev/null
+++ b/benchmark/knn_mm_d_benchmark.cc
@@ -0,0 +1,282 @@
+/*
+ * Copyright 2020 Improbable Worlds Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "benchmark_util.h"
+#include "logging.h"
+#include "phtree/phtree.h"
+#include "phtree/phtree_multimap.h"
+#include
+#include
+
+using namespace improbable;
+using namespace improbable::phtree;
+using namespace improbable::phtree::phbenchmark;
+
+/*
+ * Benchmark for k-nearest-neighbour queries in multi-map implementations.
+ */
+namespace {
+
+const double GLOBAL_MAX = 10000;
+const dimension_t DIM = 3;
+
+enum Scenario {
+ TREE_SET,
+ PHTREE_MM,
+ PHTREE_MM_STD,
+ PHTREE2,
+ TS_KD,
+ TS_QT,
+};
+
+using payload_t = int64_t;
+using payload2_t = uint32_t;
+
+using TestPoint = PhPointD;
+using QueryBox = PhBoxD;
+using BucketType = std::set;
+
+template
+using CONVERTER = ConverterIEEE;
+
+template
+using TestMap = typename std::conditional_t<
+ SCENARIO == TREE_SET,
+ PhTreeD>,
+ typename std::conditional_t<
+ SCENARIO == PHTREE_MM,
+ PhTreeMultiMap, b_plus_tree_hash_set>,
+// typename std::conditional_t<
+// SCENARIO == PHTREE2,
+// PhTreeMultiMap2D,
+ typename std::conditional_t<
+ SCENARIO == PHTREE_MM_STD,
+ PhTreeMultiMap, BucketType>,
+ void>>>;
+
+template
+class IndexBenchmark {
+ public:
+ IndexBenchmark(benchmark::State& state, int knn_result_size_);
+
+ void Benchmark(benchmark::State& state);
+
+ private:
+ void SetupWorld(benchmark::State& state);
+ void QueryWorld(benchmark::State& state, TestPoint& center);
+ void CreateQuery(TestPoint& center);
+
+ const TestGenerator data_type_;
+ const size_t num_entities_;
+ const size_t knn_result_size_;
+
+ TestMap tree_;
+ std::default_random_engine random_engine_;
+ std::uniform_real_distribution<> cube_distribution_;
+ std::vector points_;
+};
+
+template
+IndexBenchmark::IndexBenchmark(benchmark::State& state, int knn_result_size)
+: data_type_{static_cast(state.range(1))}
+, num_entities_(state.range(0))
+, knn_result_size_(knn_result_size)
+, random_engine_{1}
+, cube_distribution_{0, GLOBAL_MAX}
+, points_(state.range(0)) {
+ logging::SetupDefaultLogging();
+ SetupWorld(state);
+}
+
+template
+void IndexBenchmark::Benchmark(benchmark::State& state) {
+ for (auto _ : state) {
+ state.PauseTiming();
+ TestPoint center;
+ CreateQuery(center);
+ state.ResumeTiming();
+
+ QueryWorld(state, center);
+ }
+}
+
+template <
+ dimension_t DIM,
+ Scenario SCENARIO,
+ std::enable_if_t<(SCENARIO == Scenario::TREE_SET), int> = 0>
+void InsertEntries(TestMap& tree, const std::vector& points) {
+ for (size_t i = 0; i < points.size(); ++i) {
+ BucketType& bucket = tree.emplace(points[i]).first;
+ bucket.emplace((payload_t)i);
+ }
+}
+
+template = 0>
+void InsertEntries(TestMap& tree, const std::vector& points) {
+ for (size_t i = 0; i < points.size(); ++i) {
+ tree.emplace(points[i], (payload_t)i);
+ }
+}
+
+template
+size_t QueryAll(TestMap& tree, const TestPoint& center, const size_t k) {
+ size_t n = 0;
+ for (auto q = tree.begin_knn_query(k, center, DistanceEuclidean()); q != tree.end(); ++q) {
+ ++n;
+ }
+ return n;
+}
+
+struct CounterTreeWithMap {
+ void operator()(const TestPoint&, const BucketType& value) {
+ for (auto& x : value) {
+ (void)x;
+ n_ += 1;
+ }
+ }
+ size_t n_;
+};
+
+struct CounterMultiMap {
+ void operator()(const TestPoint&, const payload_t&) {
+ n_ += 1;
+ }
+ size_t n_;
+};
+
+template
+void IndexBenchmark::SetupWorld(benchmark::State& state) {
+ logging::info("Setting up world with {} entities and {} dimensions.", num_entities_, DIM);
+ CreatePointData(points_, data_type_, num_entities_, 0, GLOBAL_MAX);
+ InsertEntries(tree_, points_);
+
+ state.counters["query_rate"] = benchmark::Counter(0, benchmark::Counter::kIsRate);
+ state.counters["result_rate"] = benchmark::Counter(0, benchmark::Counter::kIsRate);
+ state.counters["avg_result_count"] = benchmark::Counter(0, benchmark::Counter::kAvgIterations);
+ logging::info("World setup complete.");
+}
+
+template
+void IndexBenchmark::QueryWorld(benchmark::State& state, TestPoint& center) {
+ size_t n = QueryAll(tree_, center, knn_result_size_);
+
+ state.counters["query_rate"] += 1;
+ state.counters["result_rate"] += n;
+ state.counters["avg_result_count"] += n;
+}
+
+template
+void IndexBenchmark::CreateQuery(TestPoint& center) {
+ for (dimension_t d = 0; d < DIM; ++d) {
+ center[d] = cube_distribution_(random_engine_) * GLOBAL_MAX;
+ }
+}
+
+} // namespace
+
+// template
+// void TinspinKDTree(benchmark::State& state, Arguments&&... arguments) {
+// IndexBenchmark benchmark{state, arguments...};
+// benchmark.Benchmark(state);
+// }
+//
+// template
+// void TinspinQuadtree(benchmark::State& state, Arguments&&... arguments) {
+// IndexBenchmark benchmark{state, arguments...};
+// benchmark.Benchmark(state);
+// }
+
+template
+void PhTree3D(benchmark::State& state, Arguments&&... arguments) {
+ IndexBenchmark benchmark{state, arguments...};
+ benchmark.Benchmark(state);
+}
+
+template
+void PhTreeMM(benchmark::State& state, Arguments&&... arguments) {
+ IndexBenchmark benchmark{state, arguments...};
+ benchmark.Benchmark(state);
+}
+
+//template
+//void PhTreeMM2(benchmark::State& state, Arguments&&... arguments) {
+// IndexBenchmark benchmark{state, arguments...};
+// benchmark.Benchmark(state);
+//}
+
+template
+void PhTreeMMStdSet(benchmark::State& state, Arguments&&... arguments) {
+ IndexBenchmark benchmark{state, arguments...};
+ benchmark.Benchmark(state);
+}
+
+// index type, scenario name, data_type, num_entities, query_result_size
+
+// PhTree multi-map 1.0
+BENCHMARK_CAPTURE(PhTreeMM, KNN_1, 1)
+ ->RangeMultiplier(10)
+ ->Ranges({{1000, 1000 * 1000}, {TestGenerator::CUBE, TestGenerator::CLUSTER}})
+ ->Unit(benchmark::kMillisecond);
+
+BENCHMARK_CAPTURE(PhTreeMM, KNN_10, 10)
+ ->RangeMultiplier(10)
+ ->Ranges({{1000, 1000 * 1000}, {TestGenerator::CUBE, TestGenerator::CLUSTER}})
+ ->Unit(benchmark::kMillisecond);
+
+//// Multimap 2.0
+//BENCHMARK_CAPTURE(PhTreeMM2, KNN_1, 1)
+// ->RangeMultiplier(10)
+// ->Ranges({{1000, 1000 * 1000}, {TestGenerator::CUBE, TestGenerator::CLUSTER}})
+// ->Unit(benchmark::kMillisecond);
+//
+//BENCHMARK_CAPTURE(PhTreeMM2, KNN_10, 10)
+// ->RangeMultiplier(10)
+// ->Ranges({{1000, 1000 * 1000}, {TestGenerator::CUBE, TestGenerator::CLUSTER}})
+// ->Unit(benchmark::kMillisecond);
+
+//// KD-tree
+// BENCHMARK_CAPTURE(TinspinKDTree, KNN_1, 1)
+// ->RangeMultiplier(10)
+// ->Ranges({{1000, 1000 * 1000}, {TestGenerator::CUBE, TestGenerator::CLUSTER}})
+// ->Unit(benchmark::kMillisecond);
+//
+// BENCHMARK_CAPTURE(TinspinKDTree, KNN_10, 10)
+// ->RangeMultiplier(10)
+// ->Ranges({{1000, 1000 * 1000}, {TestGenerator::CUBE, TestGenerator::CLUSTER}})
+// ->Unit(benchmark::kMillisecond);
+//
+//// Quadtree
+// BENCHMARK_CAPTURE(TinspinQuadtree, KNN_1, 1)
+// ->RangeMultiplier(10)
+// ->Ranges({{1000, 1000 * 1000}, {TestGenerator::CUBE, TestGenerator::CLUSTER}})
+// ->Unit(benchmark::kMillisecond);
+//
+// BENCHMARK_CAPTURE(TinspinQuadtree, KNN_10, 10)
+// ->RangeMultiplier(10)
+// ->Ranges({{1000, 1000 * 1000}, {TestGenerator::CUBE, TestGenerator::CLUSTER}})
+// ->Unit(benchmark::kMillisecond);
+
+// PhTree 3D with set
+BENCHMARK_CAPTURE(PhTree3D, KNN_1, 1)
+ ->RangeMultiplier(10)
+ ->Ranges({{1000, 1000 * 1000}, {TestGenerator::CUBE, TestGenerator::CLUSTER}})
+ ->Unit(benchmark::kMillisecond);
+
+BENCHMARK_CAPTURE(PhTree3D, KNN_10, 10)
+ ->RangeMultiplier(10)
+ ->Ranges({{1000, 1000 * 1000}, {TestGenerator::CUBE, TestGenerator::CLUSTER}})
+ ->Unit(benchmark::kMillisecond);
+
+BENCHMARK_MAIN();
diff --git a/fuzzer/b_plus_heap_fuzzer.cc b/fuzzer/b_plus_heap_fuzzer.cc
new file mode 100644
index 0000000..c92eb86
--- /dev/null
+++ b/fuzzer/b_plus_heap_fuzzer.cc
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2023 Tilmann Zäschke
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include
+#include
+#include
+#include
+#include
+
+#include