AI 24/25 Project Software
Documentation for the AI 24/25 course programming project software
Loading...
Searching...
No Matches
segmented_vector.h
1#ifndef ALGORITHMS_SEGMENTED_VECTOR_H
2#define ALGORITHMS_SEGMENTED_VECTOR_H
3
4#include <algorithm>
5#include <cassert>
6#include <compare>
7#include <iostream>
8#include <vector>
9
10/*
11 SegmentedVector is a vector-like class with the following advantages over
12 vector:
13 1. Resizing has no memory spike. (*)
14 2. Should work more nicely with fragmented memory because data is
15 partitioned into fixed-size chunks of size SEGMENT_BYTES.
16 3. Overallocation is only additive (by SEGMENT_BYTES), not multiplicative
17 as in vector. (*)
18 4. References stay stable forever, so there is no need to be careful about
19 invalidating references upon growing the vector.
20
21 (*) Assumes that the size of the "segments" vector can be neglected, which is
22 true if SEGMENT_BYTES isn't chosen too small. For example, with 1 GB of data
23 and SEGMENT_BYTES = 8192, we can have 131072 segments.
24
25 The main disadvantage to vector is that there is an additional indirection
26 for each lookup, but we hope that the first lookup will usually hit the cache.
27 The implementation is basically identical to that of deque (at least the
28 g++ version), but with the advantage that we can control SEGMENT_BYTES. A
29 test on all optimal planning instances with several planner configurations
30 showed a modest advantage over deque.
31
32 The class can also be used as a simple "memory pool" to reduce allocation
33 costs (time and memory) when allocating many objects of the same type.
34
35 SegmentedArrayVector is a similar class that can be used for compactly
36 storing many fixed-size arrays. It's essentially a variant of SegmentedVector
37 where the size of the stored data is only known at runtime, not at compile
38 time.
39*/
40
41/*
42 There is currently a significant amount of duplication between the
43 two classes. We decided to live with this for the time being,
44 but this could certainly be made prettier.
45*/
46
47/*
48 For documentation on classes relevant to storing and working with registered
49 states see the file state_registry.h.
50*/
51
52namespace segmented_vector {
53template <class Entry, class Allocator = std::allocator<Entry>>
54class SegmentedVector {
55 using ATraits = std::allocator_traits<Allocator>;
56 using EntryAllocator = typename ATraits::template rebind_alloc<Entry>;
57
58 static const size_t SEGMENT_BYTES = 8192;
59
60 static const size_t SEGMENT_ELEMENTS = (SEGMENT_BYTES / sizeof(Entry)) >= 1
61 ? (SEGMENT_BYTES / sizeof(Entry))
62 : 1;
63
64 EntryAllocator entry_allocator;
65
66 std::vector<Entry*> segments;
67 size_t the_size;
68
69 size_t get_segment(size_t index) const { return index / SEGMENT_ELEMENTS; }
70
71 size_t get_offset(size_t index) const { return index % SEGMENT_ELEMENTS; }
72
73 void add_segment()
74 {
75 Entry* new_segment = entry_allocator.allocate(SEGMENT_ELEMENTS);
76 segments.push_back(new_segment);
77 }
78
79 SegmentedVector(const SegmentedVector<Entry>&) = delete;
80 SegmentedVector& operator=(const SegmentedVector<Entry>&) = delete;
81
82 template <bool is_const>
83 class sviterator {
84 std::vector<Entry*>::iterator current_segment;
85 Entry* current_entry = nullptr;
86 Entry* first_entry = nullptr;
87 Entry* last_entry = nullptr;
88
89 public:
90 using value_type = std::conditional_t<is_const, const Entry, Entry>;
91 using pointer = std::conditional_t<is_const, const Entry*, Entry*>;
92 using reference = std::conditional_t<is_const, const Entry&, Entry&>;
93 using difference_type = std::ptrdiff_t;
94 using iterator_category = std::random_access_iterator_tag;
95
96 sviterator() = default;
97
98 sviterator(
99 std::vector<Entry*>::iterator current_segment,
100 Entry* current_entry)
101 : current_segment(current_segment)
102 , current_entry(current_entry)
103 , first_entry(*current_segment)
104 , last_entry(first_entry + SEGMENT_ELEMENTS)
105 {
106 }
107
108 reference operator*() const { return *current_entry; }
109 pointer operator->() { return current_entry; };
110
111 sviterator& operator++()
112 {
113 ++current_entry;
114
115 if (current_entry == last_entry) {
116 ++current_segment;
117 first_entry = *current_segment;
118 last_entry = *current_segment + SEGMENT_ELEMENTS;
119 current_entry = first_entry;
120 }
121
122 return *this;
123 }
124
125 sviterator& operator--()
126 {
127 if (current_entry == first_entry) {
128 --current_segment;
129 first_entry = *current_segment;
130 last_entry = *current_segment + SEGMENT_ELEMENTS;
131 current_entry = last_entry;
132 }
133
134 --current_entry;
135
136 return *this;
137 }
138
139 friend difference_type
140 operator-(const sviterator& lhs, const sviterator& rhs)
141 {
142 return (lhs.current_segment - rhs.current_segment) *
143 SEGMENT_ELEMENTS +
144 (lhs.current_entry - rhs.current_entry);
145 }
146
147 sviterator& operator+=(int n)
148 {
149 int offset = n + (current_entry - first_entry);
150
151 if (offset > 0 && offset < SEGMENT_ELEMENTS) {
152 current_entry += n;
153 return *this;
154 }
155
156 const difference_type segment_offset =
157 offset > 0
158 ? offset / difference_type(SEGMENT_ELEMENTS)
159 : -difference_type((-offset - 1) / SEGMENT_ELEMENTS) - 1;
160
161 current_segment += segment_offset;
162 first_entry = *current_segment;
163 last_entry = *current_segment + SEGMENT_ELEMENTS;
164 current_entry =
165 first_entry +
166 (offset - segment_offset * difference_type(SEGMENT_ELEMENTS));
167
168 return *this;
169 }
170
171 sviterator operator++(int)
172 {
173 auto r = *this;
174 ++(*this);
175 return r;
176 }
177
178 sviterator operator--(int)
179 {
180 auto r = *this;
181 --(*this);
182 return r;
183 }
184
185 friend sviterator operator+(const sviterator& it, int n)
186 {
187 auto r = it;
188 r += n;
189 return r;
190 }
191
192 friend sviterator operator+(int n, const sviterator& it)
193 {
194 auto r = it;
195 r += n;
196 return r;
197 }
198
199 friend sviterator operator-(const sviterator& it, int n)
200 {
201 auto r = it;
202 r -= n;
203 return r;
204 }
205
206 sviterator& operator-=(int n) { return (*this) += -n; }
207
208 reference operator[](int n) { return *(*this + n); };
209
210 friend auto operator<=>(const sviterator&, const sviterator&) = default;
211
212 friend void swap(sviterator& left, sviterator& right)
213 {
214 using std::swap;
215 swap(left.current_segment, right.current_segment);
216 swap(left.current_entry, right.current_entry);
217 }
218 };
219
220public:
221 using iterator = sviterator<false>;
222 using const_iterator = sviterator<true>;
223
224 SegmentedVector()
225 : the_size(0)
226 {
227 // Add an initial segment to make iterator implementation easier
228 add_segment();
229 }
230
231 SegmentedVector(std::size_t size, const Entry& entry = Entry())
232 : the_size(0)
233 {
234 while (size > the_size) {
235 push_back(entry);
236 }
237 }
238
239 SegmentedVector(const EntryAllocator& allocator_)
240 : entry_allocator(allocator_)
241 , the_size(0)
242 {
243 add_segment();
244 }
245
246 ~SegmentedVector()
247 {
248 for (size_t i = 0; i < the_size; ++i) {
249 ATraits::destroy(entry_allocator, &operator[](i));
250 }
251 for (size_t segment = 0; segment < segments.size(); ++segment) {
252 entry_allocator.deallocate(segments[segment], SEGMENT_ELEMENTS);
253 }
254 }
255
256 Entry& operator[](size_t index)
257 {
258 assert(index < the_size);
259 size_t segment = get_segment(index);
260 size_t offset = get_offset(index);
261 return segments[segment][offset];
262 }
263
264 const Entry& operator[](size_t index) const
265 {
266 assert(index < the_size);
267 size_t segment = get_segment(index);
268 size_t offset = get_offset(index);
269 return segments[segment][offset];
270 }
271
272 size_t size() const { return the_size; }
273
274 void push_back(const Entry& entry)
275 {
276 size_t segment = get_segment(the_size);
277 size_t offset = get_offset(the_size);
278 if (segment == segments.size() - 1) {
279 assert(offset == 0);
280 // Must add a new segment.
281 add_segment();
282 }
283 ATraits::construct(entry_allocator, segments[segment] + offset, entry);
284 ++the_size;
285 }
286
287 void pop_back()
288 {
289 ATraits::destroy(entry_allocator, &operator[](the_size - 1));
290 --the_size;
291 /*
292 If the removed element was the last in its segment, the segment
293 is not removed (memory is not deallocated). This way a subsequent
294 push_back does not have to allocate the memory again.
295 */
296 }
297
298 void resize(size_t new_size, Entry entry = Entry())
299 {
300 while (new_size < the_size) {
301 pop_back();
302 }
303 while (new_size > the_size) {
304 push_back(entry);
305 }
306 }
307
308 void clear()
309 {
310 for (size_t i = 0; i < the_size; ++i) {
311 ATraits::destroy(entry_allocator, &operator[](i));
312 }
313
314 the_size = 0;
315 }
316
317 iterator begin() { return iterator(segments.begin(), segments.front()); }
318
319 iterator end()
320 {
321 return iterator(segments.end() - 1, *(segments.end() - 1));
322 }
323
324 const_iterator begin() const
325 {
326 return const_iterator(segments.begin(), *segments.front());
327 }
328
329 const_iterator end() const
330 {
331 return const_iterator(segments.end() - 1, *(segments.end() - 1));
332 }
333};
334
335template <class Element, class Allocator = std::allocator<Element>>
336class SegmentedArrayVector {
337 using ATraits = std::allocator_traits<Allocator>;
338 using ElementAllocator = typename ATraits::template rebind_alloc<Element>;
339
340 static const size_t SEGMENT_BYTES = 8192;
341
342 const size_t elements_per_array;
343 const size_t arrays_per_segment;
344 const size_t elements_per_segment;
345
346 ElementAllocator element_allocator;
347
348 std::vector<Element*> segments;
349 size_t the_size;
350
351 size_t get_segment(size_t index) const
352 {
353 return index / arrays_per_segment;
354 }
355
356 size_t get_offset(size_t index) const
357 {
358 return (index % arrays_per_segment) * elements_per_array;
359 }
360
361 void add_segment()
362 {
363 Element* new_segment = element_allocator.allocate(elements_per_segment);
364 segments.push_back(new_segment);
365 }
366
367 SegmentedArrayVector(const SegmentedArrayVector<Element>&) = delete;
368 SegmentedArrayVector&
369 operator=(const SegmentedArrayVector<Element>&) = delete;
370
371public:
372 SegmentedArrayVector(size_t elements_per_array_)
373 : elements_per_array(elements_per_array_)
374 , arrays_per_segment(std::max(
375 SEGMENT_BYTES / (elements_per_array * sizeof(Element)),
376 size_t(1)))
377 , elements_per_segment(elements_per_array * arrays_per_segment)
378 , the_size(0)
379 {
380 }
381
382 SegmentedArrayVector(
383 size_t elements_per_array_,
384 const ElementAllocator& allocator_)
385 : element_allocator(allocator_)
386 , elements_per_array(elements_per_array_)
387 , arrays_per_segment(std::max(
388 SEGMENT_BYTES / (elements_per_array * sizeof(Element)),
389 size_t(1)))
390 , elements_per_segment(elements_per_array * arrays_per_segment)
391 , the_size(0)
392 {
393 }
394
395 ~SegmentedArrayVector()
396 {
397 for (size_t i = 0; i < the_size; ++i) {
398 for (size_t offset = 0; offset < elements_per_array; ++offset) {
399 ATraits::destroy(element_allocator, operator[](i) + offset);
400 }
401 }
402 for (size_t i = 0; i < segments.size(); ++i) {
403 element_allocator.deallocate(segments[i], elements_per_segment);
404 }
405 }
406
407 Element* operator[](size_t index)
408 {
409 assert(index < the_size);
410 size_t segment = get_segment(index);
411 size_t offset = get_offset(index);
412 return segments[segment] + offset;
413 }
414
415 const Element* operator[](size_t index) const
416 {
417 assert(index < the_size);
418 size_t segment = get_segment(index);
419 size_t offset = get_offset(index);
420 return segments[segment] + offset;
421 }
422
423 size_t size() const { return the_size; }
424
425 void push_back(const Element* entry)
426 {
427 size_t segment = get_segment(the_size);
428 size_t offset = get_offset(the_size);
429 if (segment == segments.size()) {
430 assert(offset == 0);
431 // Must add a new segment.
432 add_segment();
433 }
434 Element* dest = segments[segment] + offset;
435 for (size_t i = 0; i < elements_per_array; ++i)
436 ATraits::construct(element_allocator, dest++, *entry++);
437 ++the_size;
438 }
439
440 void pop_back()
441 {
442 for (size_t offset = 0; offset < elements_per_array; ++offset) {
443 ATraits::destroy(
444 element_allocator,
445 operator[](the_size - 1) + offset);
446 }
447 --the_size;
448 /*
449 If the removed element was the last in its segment, the segment
450 is not removed (memory is not deallocated). This way a subsequent
451 push_back does not have to allocate the memory again.
452 */
453 }
454
455 void resize(size_t new_size, const Element* entry)
456 {
457 while (new_size < the_size) {
458 pop_back();
459 }
460 while (new_size > the_size) {
461 push_back(entry);
462 }
463 }
464};
465} // namespace segmented_vector
466
467#endif
STL namespace.