Skip to content

Commit 257f8dc

Browse files
authored
Embed Node into Entry by flatteningcontainers (#96)
1 parent 9f8c0b9 commit 257f8dc

File tree

7 files changed

+200
-121
lines changed

7 files changed

+200
-121
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
1010
- Added B+tree multimap for internal (future) use. [#93](https://github.com/tzaeschke/phtree-cpp/issues/93)
1111

1212
### Changed
13+
- Improved performance by eliminating memory indirection for DIM > 3.
14+
This was enabled by referencing "Node" directly in "Entry" which was enabled by
15+
implanting an indirection in array_map. [#96](https://github.com/tzaeschke/phtree-cpp/pull/96)
1316
- Improved performance of window queries by executing them partially as point queries.
1417
This works best for point datasets, and somewhat for box datasets with "include" queries.
1518
There is no benefit for "intersection" queries. [#88](https://github.com/tzaeschke/phtree-cpp/issues/88)

include/phtree/common/b_plus_tree_map.h

+4
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,10 @@ class b_plus_tree_map {
146146
return leaf != nullptr ? leaf->lower_bound_as_iter(key) : IterT{};
147147
}
148148

149+
[[nodiscard]] auto lower_bound(KeyT key) const noexcept {
150+
return const_cast<b_plus_tree_map&>(*this).lower_bound(key);
151+
}
152+
149153
[[nodiscard]] auto begin() noexcept {
150154
return IterT(root_);
151155
}
+154-86
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/*
22
* Copyright 2020 Improbable Worlds Limited
3+
* Copyright 2022 Tilmann Zäschke
34
*
45
* Licensed under the Apache License, Version 2.0 (the "License");
56
* you may not use this file except in compliance with the License.
@@ -30,16 +31,60 @@
3031
*/
3132
namespace improbable::phtree {
3233

33-
namespace {
3434
template <typename T, std::size_t SIZE>
35-
class PhFlatMapIterator;
35+
class flat_array_map;
36+
37+
namespace detail {
3638

3739
template <typename T>
38-
using PhFlatMapPair = std::pair<size_t, T>;
40+
using flat_map_pair = std::pair<size_t, T>;
41+
42+
template <typename T, std::size_t SIZE>
43+
class flat_map_iterator {
44+
friend flat_array_map<T, SIZE>;
45+
46+
public:
47+
flat_map_iterator() : first{0}, map_{nullptr} {};
48+
49+
explicit flat_map_iterator(size_t index, const flat_array_map<T, SIZE>* map)
50+
: first{index}, map_{map} {
51+
assert(index <= SIZE);
52+
}
53+
54+
auto& operator*() const {
55+
assert(first < SIZE && map_->occupied(first));
56+
return const_cast<flat_map_pair<T>&>(map_->data(first));
57+
}
58+
59+
auto* operator->() const {
60+
assert(first < SIZE && map_->occupied(first));
61+
return const_cast<flat_map_pair<T>*>(&map_->data(first));
62+
}
3963

40-
using bit_string_t = std::uint64_t;
41-
constexpr bit_string_t U64_ONE = bit_string_t(1);
42-
} // namespace
64+
auto& operator++() {
65+
first = (first + 1) >= SIZE ? SIZE : map_->lower_bound_index(first + 1);
66+
return *this;
67+
}
68+
69+
auto operator++(int) {
70+
flat_map_iterator it(first, map_);
71+
++(*this);
72+
return it;
73+
}
74+
75+
friend bool operator==(const flat_map_iterator& left, const flat_map_iterator& right) {
76+
return left.first == right.first;
77+
}
78+
79+
friend bool operator!=(const flat_map_iterator& left, const flat_map_iterator& right) {
80+
return left.first != right.first;
81+
}
82+
83+
private:
84+
size_t first;
85+
const flat_array_map<T, SIZE>* map_;
86+
};
87+
} // namespace detail
4388

4489
/*
4590
* The array_map is a flat map implementation that uses an array of SIZE=2^DIM. The key is
@@ -49,30 +94,20 @@ constexpr bit_string_t U64_ONE = bit_string_t(1);
4994
* when DIM is low and/or the map is known to have a high fill ratio.
5095
*/
5196
template <typename T, std::size_t SIZE>
52-
class array_map {
53-
friend PhFlatMapIterator<T, SIZE>;
54-
static_assert(SIZE <= 64); // or else we need to adapt 'occupancy'
55-
static_assert(SIZE > 0);
97+
class flat_array_map {
98+
using map_pair = detail::flat_map_pair<T>;
99+
using iterator = detail::flat_map_iterator<T, SIZE>;
100+
friend iterator;
56101

57102
public:
58-
~array_map() {
59-
if (occupancy != 0) {
60-
for (size_t i = 0; i < SIZE; ++i) {
61-
if (occupied(i)) {
62-
data(i).~pair();
63-
}
64-
}
65-
}
66-
}
67-
68-
[[nodiscard]] auto find(size_t index) const {
69-
return occupied(index) ? PhFlatMapIterator<T, SIZE>{index, *this} : end();
103+
[[nodiscard]] auto find(size_t index) noexcept {
104+
return occupied(index) ? iterator{index, this} : end();
70105
}
71106

72107
[[nodiscard]] auto lower_bound(size_t index) const {
73108
size_t index2 = lower_bound_index(index);
74109
if (index2 < SIZE) {
75-
return PhFlatMapIterator<T, SIZE>{index2, *this};
110+
return iterator{index2, this};
76111
}
77112
return end();
78113
}
@@ -81,52 +116,38 @@ class array_map {
81116
size_t index = CountTrailingZeros(occupancy);
82117
// Assert index points to a valid position or outside the map if the map is empty
83118
assert((size() == 0 && index >= SIZE) || occupied(index));
84-
return PhFlatMapIterator<T, SIZE>{index < SIZE ? index : SIZE, *this};
119+
return iterator{index < SIZE ? index : SIZE, this};
85120
}
86121

87122
[[nodiscard]] auto cbegin() const {
88123
size_t index = CountTrailingZeros(occupancy);
89124
// Assert index points to a valid position or outside the map if the map is empty
90125
assert((size() == 0 && index >= SIZE) || occupied(index));
91-
return PhFlatMapIterator<T, SIZE>{index < SIZE ? index : SIZE, *this};
126+
return iterator{index < SIZE ? index : SIZE, this};
92127
}
93128

94129
[[nodiscard]] auto end() const {
95-
return PhFlatMapIterator<T, SIZE>{SIZE, *this};
96-
}
97-
98-
template <typename... Args>
99-
auto emplace(Args&&... args) {
100-
return try_emplace_base(std::forward<Args>(args)...);
101-
}
102-
103-
template <typename... Args>
104-
auto try_emplace(size_t index, Args&&... args) {
105-
return try_emplace_base(index, std::forward<Args>(args)...);
130+
return iterator{SIZE, this};
106131
}
107132

108-
bool erase(size_t index) {
109-
if (occupied(index)) {
110-
data(index).~pair();
111-
occupied(index, false);
112-
return true;
133+
~flat_array_map() noexcept {
134+
if (occupancy != 0) {
135+
for (size_t i = 0; i < SIZE; ++i) {
136+
if (occupied(i)) {
137+
data(i).~pair();
138+
}
139+
}
113140
}
114-
return false;
115-
}
116-
117-
bool erase(PhFlatMapIterator<T, SIZE>& iterator) {
118-
return erase(iterator.first);
119141
}
120142

121143
[[nodiscard]] size_t size() const {
122144
return std::bitset<64>(occupancy).count();
123145
}
124146

125-
private:
126147
template <typename... Args>
127-
std::pair<PhFlatMapPair<T>*, bool> try_emplace_base(size_t index, Args&&... args) {
148+
std::pair<map_pair*, bool> try_emplace_base(size_t index, Args&&... args) {
128149
if (!occupied(index)) {
129-
new (reinterpret_cast<void*>(&data_[index])) PhFlatMapPair<T>(
150+
new (reinterpret_cast<void*>(&data_[index])) map_pair(
130151
std::piecewise_construct,
131152
std::forward_as_tuple(index),
132153
std::forward_as_tuple(std::forward<Args>(args)...));
@@ -136,17 +157,31 @@ class array_map {
136157
return {&data(index), false};
137158
}
138159

160+
bool erase(size_t index) {
161+
if (occupied(index)) {
162+
data(index).~pair();
163+
occupied(index, false);
164+
return true;
165+
}
166+
return false;
167+
}
168+
169+
bool erase(const iterator& iterator) {
170+
return erase(iterator.first);
171+
}
172+
173+
private:
139174
/*
140175
* This returns the element at the given index, which is _not_ the n'th element (for n = index).
141176
*/
142-
PhFlatMapPair<T>& data(size_t index) {
177+
map_pair& data(size_t index) {
143178
assert(occupied(index));
144-
return *std::launder(reinterpret_cast<PhFlatMapPair<T>*>(&data_[index]));
179+
return *std::launder(reinterpret_cast<map_pair*>(&data_[index]));
145180
}
146181

147-
const PhFlatMapPair<T>& data(size_t index) const {
182+
const map_pair& data(size_t index) const {
148183
assert(occupied(index));
149-
return *std::launder(reinterpret_cast<const PhFlatMapPair<T>*>(&data_[index]));
184+
return *std::launder(reinterpret_cast<const map_pair*>(&data_[index]));
150185
}
151186

152187
[[nodiscard]] size_t lower_bound_index(size_t index) const {
@@ -161,69 +196,102 @@ class array_map {
161196
assert(index < SIZE);
162197
assert(occupied(index) != flag);
163198
// flip the bit
164-
occupancy ^= (U64_ONE << index);
199+
occupancy ^= (1ul << index);
165200
assert(occupied(index) == flag);
166201
}
167202

168203
[[nodiscard]] bool occupied(size_t index) const {
169-
return (occupancy >> index) & U64_ONE;
204+
return (occupancy >> index) & 1ul;
170205
}
171206

172-
bit_string_t occupancy = 0;
207+
std::uint64_t occupancy = 0;
173208
// We use an untyped array to avoid implicit calls to constructors and destructors of entries.
174-
std::aligned_storage_t<sizeof(PhFlatMapPair<T>), alignof(PhFlatMapPair<T>)> data_[SIZE];
209+
std::aligned_storage_t<sizeof(map_pair), alignof(map_pair)> data_[SIZE];
175210
};
176211

177-
namespace {
212+
/*
213+
* array_map is a wrapper around flat_array_map. It introduces one layer of indirection.
214+
* This is useful to decouple instantiation of a node from instantiation of it's descendants
215+
* (the flat_array_map directly instantiates an array of descendants).
216+
*/
178217
template <typename T, std::size_t SIZE>
179-
class PhFlatMapIterator {
180-
friend array_map<T, SIZE>;
218+
class array_map {
219+
static_assert(SIZE <= 64); // or else we need to adapt 'occupancy'
220+
static_assert(SIZE > 0);
221+
using iterator = improbable::phtree::detail::flat_map_iterator<T, SIZE>;
181222

182223
public:
183-
PhFlatMapIterator() : first{0}, map_{nullptr} {};
224+
array_map() {
225+
data_ = new flat_array_map<T, SIZE>();
226+
}
184227

185-
explicit PhFlatMapIterator(size_t index, const array_map<T, SIZE>& map)
186-
: first{index}, map_{&map} {
187-
assert(index <= SIZE);
228+
array_map(const array_map& other) = delete;
229+
array_map& operator=(const array_map& other) = delete;
230+
231+
array_map(array_map&& other) noexcept : data_{other.data_} {
232+
other.data_ = nullptr;
188233
}
189234

190-
auto& operator*() const {
191-
assert(first < SIZE && map_->occupied(first));
192-
return const_cast<PhFlatMapPair<T>&>(map_->data(first));
235+
array_map& operator=(array_map&& other) noexcept {
236+
data_ = other.data_;
237+
other.data_ = nullptr;
238+
return *this;
193239
}
194240

195-
auto* operator->() const {
196-
assert(first < SIZE && map_->occupied(first));
197-
return const_cast<PhFlatMapPair<T>*>(&map_->data(first));
241+
~array_map() {
242+
delete data_;
198243
}
199244

200-
auto& operator++() {
201-
first = (first + 1) >= SIZE ? SIZE : map_->lower_bound_index(first + 1);
202-
return *this;
245+
[[nodiscard]] auto find(size_t index) noexcept {
246+
return data_->find(index);
203247
}
204248

205-
auto operator++(int) {
206-
PhFlatMapIterator iterator(first, *map_);
207-
++(*this);
208-
return iterator;
249+
[[nodiscard]] auto find(size_t key) const noexcept {
250+
return const_cast<array_map&>(*this).find(key);
209251
}
210252

211-
friend bool operator==(
212-
const PhFlatMapIterator<T, SIZE>& left, const PhFlatMapIterator<T, SIZE>& right) {
213-
return left.first == right.first;
253+
[[nodiscard]] auto lower_bound(size_t index) const {
254+
return data_->lower_bound(index);
214255
}
215256

216-
friend bool operator!=(
217-
const PhFlatMapIterator<T, SIZE>& left, const PhFlatMapIterator<T, SIZE>& right) {
218-
return !(left == right);
257+
[[nodiscard]] auto begin() const {
258+
return data_->begin();
259+
}
260+
261+
[[nodiscard]] iterator cbegin() const {
262+
return data_->cbegin();
263+
}
264+
265+
[[nodiscard]] auto end() const {
266+
return data_->end();
267+
}
268+
269+
template <typename... Args>
270+
auto emplace(Args&&... args) {
271+
return data_->try_emplace_base(std::forward<Args>(args)...);
272+
}
273+
274+
template <typename... Args>
275+
auto try_emplace(size_t index, Args&&... args) {
276+
return data_->try_emplace_base(index, std::forward<Args>(args)...);
277+
}
278+
279+
bool erase(size_t index) {
280+
return data_->erase(index);
281+
}
282+
283+
bool erase(const iterator& iterator) {
284+
return data_->erase(iterator);
285+
}
286+
287+
[[nodiscard]] size_t size() const {
288+
return data_->size();
219289
}
220290

221291
private:
222-
size_t first;
223-
const array_map<T, SIZE>* map_;
292+
flat_array_map<T, SIZE>* data_;
224293
};
225294

226-
} // namespace
227295
} // namespace improbable::phtree
228296

229297
#endif // PHTREE_COMMON_FLAT_ARRAY_MAP_H

0 commit comments

Comments
 (0)