Skip to content

Commit 7a6b1d8

Browse files
authored
Fix/issue 14 b tree (#15)
1 parent 671f603 commit 7a6b1d8

17 files changed

Lines changed: 1666 additions & 16 deletions

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
66

77
## [Unreleased]
88
### Changed
9+
- DIM>8 now uses custom b_plus_tree_map instead of std::map. This improves performance for all operations, e.g.
10+
window queries on large datasets are up to 4x faster. Benchmarks results can be found in the issue.
11+
[#14](https://github.com/tzaeschke/phtree-cpp/issues/14)
912
- postfix/infix field moved from Node to Entry. This avoids indirections and improves performance of most by ~10%.
1013
operations by 5-15%. [#11](https://github.com/tzaeschke/phtree-cpp/issues/11)
1114
- Entries now use 'union' to store children. [#9](https://github.com/tzaeschke/phtree-cpp/issues/9)

LICENSE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@
188188
identification within third-party archives.
189189

190190
Copyright 2020 Improbable Worlds Limited
191+
Copyright 2022 Tilmann Zäschke
191192

192193
Licensed under the Apache License, Version 2.0 (the "License");
193194
you may not use this file except in compliance with the License.

TODO.txt

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
Fix const-ness
2+
==============
3+
- operator[] should have a const overload
4+
- find() should have a non-const overload
5+
- test:
6+
7+
TEST(PhTreeTest, SmokeTestConstTree) {
8+
// Test edge case: only one entry in tree
9+
PhPoint<3> p{1, 2, 3};
10+
TestTree<3, Id> tree1;
11+
tree1.emplace(p, Id{1});
12+
tree1.emplace(p, Id{2});
13+
Id id3{3};
14+
tree1.insert(p, id3);
15+
Id id4{4};
16+
tree1.insert(p, id4);
17+
const auto& tree = tree1;
18+
ASSERT_EQ(tree.size(), 1);
19+
ASSERT_EQ(tree.find(p).second()._i, 1);
20+
ASSERT_EQ(tree[p]._i, 1);
21+
22+
auto q_window = tree.begin_query({p, p});
23+
ASSERT_EQ(1, q_window->_i);
24+
++q_window;
25+
ASSERT_EQ(q_window, tree.end());
26+
27+
auto q_extent = tree.begin();
28+
ASSERT_EQ(1, q_extent->_i);
29+
++q_extent;
30+
ASSERT_EQ(q_extent, tree.end());
31+
32+
auto q_knn = tree.begin_knn_query(10, p, DistanceEuclidean<3>());
33+
ASSERT_EQ(1, q_knn->_i);
34+
++q_knn;
35+
ASSERT_EQ(q_knn, tree.end());
36+
37+
ASSERT_EQ(1, tree1.erase(p));
38+
ASSERT_EQ(0, tree.size());
39+
ASSERT_EQ(0, tree1.erase(p));
40+
ASSERT_EQ(0, tree.size());
41+
ASSERT_TRUE(tree.empty());
42+
}
43+
44+
45+
b_plus_tree_map - binary search
46+
===============
47+
Use custom binary search:
48+
49+
// return BptEntry* ?!?!?
50+
template <typename E>
51+
[[nodiscard]] auto lower_bound(key_t key, std::vector<E>& data) noexcept {
52+
return std::lower_bound(data.begin(), data.end(), key, [](E& left, const key_t key) {
53+
return left.first < key;
54+
});
55+
// auto pos = __lower_bound(&*data_leaf_.begin(), &*data_leaf_.end(), key);
56+
// return data_leaf_.begin() + pos;
57+
}
58+
59+
template <typename TT>
60+
inline auto __lower_bound(const TT* __first, const TT* __last, key_t __val) const noexcept {
61+
const TT* const_first = __first;
62+
auto __len = __last - __first;
63+
64+
while (__len > 0) {
65+
auto __half = __len >> 1;
66+
const TT* __middle = __first + __half;
67+
if (__middle->first < __val) {
68+
__first = __middle;
69+
++__first;
70+
__len = __len - __half - 1;
71+
} else
72+
__len = __half;
73+
}
74+
return __first - const_first;
75+
}
76+

WORKSPACE

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@ http_archive(
3434

3535
http_archive(
3636
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",
37+
sha256 = "6132883bc8c9b0df5375b16ab520fac1a85dc9e4cf5be59480448ece74b278d4",
38+
strip_prefix = "benchmark-1.6.1",
39+
url = "https://github.com/google/benchmark/archive/v1.6.1.tar.gz",
4040
)
4141

4242
http_archive(

phtree/benchmark/BUILD

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,3 +304,63 @@ cc_binary(
304304
"@spdlog",
305305
],
306306
)
307+
308+
cc_binary(
309+
name = "hd_insert_d_benchmark",
310+
testonly = True,
311+
srcs = [
312+
"hd_insert_d_benchmark.cc",
313+
],
314+
linkstatic = True,
315+
deps = [
316+
"//phtree",
317+
"//phtree/benchmark",
318+
"@gbenchmark//:benchmark",
319+
"@spdlog",
320+
],
321+
)
322+
323+
cc_binary(
324+
name = "hd_erase_d_benchmark",
325+
testonly = True,
326+
srcs = [
327+
"hd_erase_d_benchmark.cc",
328+
],
329+
linkstatic = True,
330+
deps = [
331+
"//phtree",
332+
"//phtree/benchmark",
333+
"@gbenchmark//:benchmark",
334+
"@spdlog",
335+
],
336+
)
337+
338+
cc_binary(
339+
name = "hd_query_d_benchmark",
340+
testonly = True,
341+
srcs = [
342+
"hd_query_d_benchmark.cc",
343+
],
344+
linkstatic = True,
345+
deps = [
346+
"//phtree",
347+
"//phtree/benchmark",
348+
"@gbenchmark//:benchmark",
349+
"@spdlog",
350+
],
351+
)
352+
353+
cc_binary(
354+
name = "hd_knn_d_benchmark",
355+
testonly = True,
356+
srcs = [
357+
"hd_knn_d_benchmark.cc",
358+
],
359+
linkstatic = True,
360+
deps = [
361+
"//phtree",
362+
"//phtree/benchmark",
363+
"@gbenchmark//:benchmark",
364+
"@spdlog",
365+
],
366+
)

phtree/benchmark/benchmark_util.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ auto CreateDuplicates =
9191
};
9292
} // namespace
9393

94-
enum TestGenerator { CUBE, CLUSTER };
94+
enum TestGenerator { CUBE = 4, CLUSTER = 7 };
9595

9696
template <dimension_t DIM>
9797
auto CreatePointDataMinMax = [](auto& points,
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
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 <benchmark/benchmark.h>
20+
#include <random>
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 <dimension_t DIM>
34+
class IndexBenchmark {
35+
public:
36+
IndexBenchmark(benchmark::State& state);
37+
void Benchmark(benchmark::State& state);
38+
39+
private:
40+
void SetupWorld(benchmark::State& state);
41+
void Insert(benchmark::State& state, PhTreeD<DIM, int>& tree);
42+
void Remove(benchmark::State& state, PhTreeD<DIM, int>& tree);
43+
44+
const TestGenerator data_type_;
45+
const int num_entities_;
46+
47+
std::default_random_engine random_engine_;
48+
std::uniform_real_distribution<> cube_distribution_;
49+
std::vector<PhPointD<DIM>> points_;
50+
};
51+
52+
template <dimension_t DIM>
53+
IndexBenchmark<DIM>::IndexBenchmark(benchmark::State& state)
54+
: data_type_{static_cast<TestGenerator>(state.range(1))}
55+
, num_entities_(state.range(0))
56+
, random_engine_{1}
57+
, cube_distribution_{0, GLOBAL_MAX}
58+
, points_(state.range(0)) {
59+
logging::SetupDefaultLogging();
60+
SetupWorld(state);
61+
}
62+
63+
template <dimension_t DIM>
64+
void IndexBenchmark<DIM>::Benchmark(benchmark::State& state) {
65+
for (auto _ : state) {
66+
state.PauseTiming();
67+
auto* tree = new PhTreeD<DIM, int>();
68+
Insert(state, *tree);
69+
state.ResumeTiming();
70+
71+
Remove(state, *tree);
72+
73+
state.PauseTiming();
74+
// avoid measuring deallocation
75+
delete tree;
76+
state.ResumeTiming();
77+
}
78+
}
79+
80+
template <dimension_t DIM>
81+
void IndexBenchmark<DIM>::SetupWorld(benchmark::State& state) {
82+
logging::info("Setting up world with {} entities and {} dimensions.", num_entities_, DIM);
83+
CreatePointData<DIM>(points_, data_type_, num_entities_, 0, GLOBAL_MAX);
84+
85+
state.counters["total_remove_count"] = benchmark::Counter(0);
86+
state.counters["remove_rate"] = benchmark::Counter(0, benchmark::Counter::kIsRate);
87+
88+
logging::info("World setup complete.");
89+
}
90+
91+
template <dimension_t DIM>
92+
void IndexBenchmark<DIM>::Insert(benchmark::State&, PhTreeD<DIM, int>& tree) {
93+
for (int i = 0; i < num_entities_; ++i) {
94+
tree.emplace(points_[i], i);
95+
}
96+
}
97+
98+
template <dimension_t DIM>
99+
void IndexBenchmark<DIM>::Remove(benchmark::State& state, PhTreeD<DIM, int>& tree) {
100+
int n = 0;
101+
for (int i = 0; i < num_entities_; ++i) {
102+
n += tree.erase(points_[i]);
103+
}
104+
105+
state.counters["total_remove_count"] += n;
106+
state.counters["remove_rate"] += n;
107+
}
108+
109+
} // namespace
110+
111+
template <typename... Arguments>
112+
void PhTree6D(benchmark::State& state, Arguments&&...) {
113+
IndexBenchmark<6> benchmark{state};
114+
benchmark.Benchmark(state);
115+
}
116+
117+
template <typename... Arguments>
118+
void PhTree10D(benchmark::State& state, Arguments&&...) {
119+
IndexBenchmark<10> benchmark{state};
120+
benchmark.Benchmark(state);
121+
}
122+
123+
template <typename... Arguments>
124+
void PhTree20D(benchmark::State& state, Arguments&&...) {
125+
IndexBenchmark<20> benchmark{state};
126+
benchmark.Benchmark(state);
127+
}
128+
129+
// index type, scenario name, data_generator, num_entities
130+
BENCHMARK_CAPTURE(PhTree6D, ERASE, 0)
131+
->RangeMultiplier(10)
132+
->Ranges({{1000, 1000 * 1000}, {TestGenerator::CLUSTER, TestGenerator::CUBE}})
133+
->Unit(benchmark::kMillisecond);
134+
135+
BENCHMARK_CAPTURE(PhTree10D, ERASE, 0)
136+
->RangeMultiplier(10)
137+
->Ranges({{1000, 1000 * 1000}, {TestGenerator::CLUSTER, TestGenerator::CUBE}})
138+
->Unit(benchmark::kMillisecond);
139+
140+
BENCHMARK_CAPTURE(PhTree20D, ERASE, 0)
141+
->RangeMultiplier(10)
142+
->Ranges({{1000, 1000 * 1000}, {TestGenerator::CLUSTER, TestGenerator::CUBE}})
143+
->Unit(benchmark::kMillisecond);
144+
145+
BENCHMARK_MAIN();

0 commit comments

Comments
 (0)