aboutsummaryrefslogtreecommitdiff
path: root/src/util/vecdeque.h
blob: a9264a5ad64f98b935d7c76d0cb702df7802103a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
// Copyright (c) The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#ifndef BITCOIN_UTIL_VECDEQUE_H
#define BITCOIN_UTIL_VECDEQUE_H

#include <util/check.h>

#include <cstring>
#include <memory>
#include <type_traits>

/** Data structure largely mimicking std::deque, but using single preallocated ring buffer.
 *
 * - More efficient and better memory locality than std::deque.
 * - Most operations ({push_,pop_,emplace_,}{front,back}(), operator[], ...) are O(1),
 *   unless reallocation is needed (in which case they are O(n)).
 * - Supports reserve(), capacity(), shrink_to_fit() like vectors.
 * - No iterator support.
 * - Data is not stored in a single contiguous block, so no data().
 */
template<typename T>
class VecDeque
{
    /** Pointer to allocated memory. Can contain constructed and uninitialized T objects. */
    T* m_buffer{nullptr};
    /** m_buffer + m_offset points to first object in queue. m_offset = 0 if m_capacity is 0;
     *  otherwise 0 <= m_offset < m_capacity. */
    size_t m_offset{0};
    /** Number of objects in the container. 0 <= m_size <= m_capacity. */
    size_t m_size{0};
    /** The size of m_buffer, expressed as a multiple of the size of T. */
    size_t m_capacity{0};

    /** Returns the number of populated objects between m_offset and the end of the buffer. */
    size_t FirstPart() const noexcept { return std::min(m_capacity - m_offset, m_size); }

    void Reallocate(size_t capacity)
    {
        Assume(capacity >= m_size);
        Assume((m_offset == 0 && m_capacity == 0) || m_offset < m_capacity);
        // Allocate new buffer.
        T* new_buffer = capacity ? std::allocator<T>().allocate(capacity) : nullptr;
        if (capacity) {
            if constexpr (std::is_trivially_copyable_v<T>) {
                // When T is trivially copyable, just copy the data over from old to new buffer.
                size_t first_part = FirstPart();
                if (first_part != 0) {
                    std::memcpy(new_buffer, m_buffer + m_offset, first_part * sizeof(T));
                }
                if (first_part != m_size) {
                    std::memcpy(new_buffer + first_part, m_buffer, (m_size - first_part) * sizeof(T));
                }
            } else {
                // Otherwise move-construct in place in the new buffer, and destroy old buffer objects.
                size_t old_pos = m_offset;
                for (size_t new_pos = 0; new_pos < m_size; ++new_pos) {
                    std::construct_at(new_buffer + new_pos, std::move(*(m_buffer + old_pos)));
                    std::destroy_at(m_buffer + old_pos);
                    ++old_pos;
                    if (old_pos == m_capacity) old_pos = 0;
                }
            }
        }
        // Deallocate old buffer and update housekeeping.
        std::allocator<T>().deallocate(m_buffer, m_capacity);
        m_buffer = new_buffer;
        m_offset = 0;
        m_capacity = capacity;
        Assume((m_offset == 0 && m_capacity == 0) || m_offset < m_capacity);
    }

    /** What index in the buffer does logical entry number pos have? */
    size_t BufferIndex(size_t pos) const noexcept
    {
        Assume(pos < m_capacity);
        // The expression below is used instead of the more obvious (pos + m_offset >= m_capacity),
        // because the addition there could in theory overflow with very large deques.
        if (pos >= m_capacity - m_offset) {
            return (m_offset + pos) - m_capacity;
        } else {
            return m_offset + pos;
        }
    }

    /** Specialization of resize() that can only shrink. Separate so that clear() can call it
     *  without requiring a default T constructor. */
    void ResizeDown(size_t size) noexcept
    {
        Assume(size <= m_size);
        if constexpr (std::is_trivially_destructible_v<T>) {
            // If T is trivially destructible, we do not need to do anything but update the
            // housekeeping record. Default constructor or zero-filling will be used when
            // the space is reused.
            m_size = size;
        } else {
            // If not, we need to invoke the destructor for every element separately.
            while (m_size > size) {
                std::destroy_at(m_buffer + BufferIndex(m_size - 1));
                --m_size;
            }
        }
    }

public:
    VecDeque() noexcept = default;

    /** Resize the deque to be exactly size size (adding default-constructed elements if needed). */
    void resize(size_t size)
    {
        if (size < m_size) {
            // Delegate to ResizeDown when shrinking.
            ResizeDown(size);
        } else if (size > m_size) {
            // When growing, first see if we need to allocate more space.
            if (size > m_capacity) Reallocate(size);
            while (m_size < size) {
                std::construct_at(m_buffer + BufferIndex(m_size));
                ++m_size;
            }
        }
    }

    /** Resize the deque to be size 0. The capacity will remain unchanged. */
    void clear() noexcept { ResizeDown(0); }

    /** Destroy a deque. */
    ~VecDeque()
    {
        clear();
        Reallocate(0);
    }

    /** Copy-assign a deque. */
    VecDeque& operator=(const VecDeque& other)
    {
        if (&other == this) [[unlikely]] return *this;
        clear();
        Reallocate(other.m_size);
        if constexpr (std::is_trivially_copyable_v<T>) {
            size_t first_part = other.FirstPart();
            Assume(first_part > 0 || m_size == 0);
            if (first_part != 0) {
                std::memcpy(m_buffer, other.m_buffer + other.m_offset, first_part * sizeof(T));
            }
            if (first_part != other.m_size) {
                std::memcpy(m_buffer + first_part, other.m_buffer, (other.m_size - first_part) * sizeof(T));
            }
            m_size = other.m_size;
        } else {
            while (m_size < other.m_size) {
                std::construct_at(m_buffer + BufferIndex(m_size), other[m_size]);
                ++m_size;
            }
        }
        return *this;
    }

    /** Swap two deques. */
    void swap(VecDeque& other) noexcept
    {
        std::swap(m_buffer, other.m_buffer);
        std::swap(m_offset, other.m_offset);
        std::swap(m_size, other.m_size);
        std::swap(m_capacity, other.m_capacity);
    }

    /** Non-member version of swap. */
    friend void swap(VecDeque& a, VecDeque& b) noexcept { a.swap(b); }

    /** Move-assign a deque. */
    VecDeque& operator=(VecDeque&& other) noexcept
    {
        swap(other);
        return *this;
    }

    /** Copy-construct a deque. */
    VecDeque(const VecDeque& other) { *this = other; }
    /** Move-construct a deque. */
    VecDeque(VecDeque&& other) noexcept { swap(other); }

    /** Equality comparison between two deques (only compares size+contents, not capacity). */
    bool friend operator==(const VecDeque& a, const VecDeque& b)
    {
        if (a.m_size != b.m_size) return false;
        for (size_t i = 0; i < a.m_size; ++i) {
            if (a[i] != b[i]) return false;
        }
        return true;
    }

    /** Comparison between two deques, implementing lexicographic ordering on the contents. */
    std::strong_ordering friend operator<=>(const VecDeque& a, const VecDeque& b)
    {
        size_t pos_a{0}, pos_b{0};
        while (pos_a < a.m_size && pos_b < b.m_size) {
            auto cmp = a[pos_a++] <=> b[pos_b++];
            if (cmp != 0) return cmp;
        }
        return a.m_size <=> b.m_size;
    }

    /** Increase the capacity to capacity. Capacity will not shrink. */
    void reserve(size_t capacity)
    {
        if (capacity > m_capacity) Reallocate(capacity);
    }

    /** Make the capacity equal to the size. The contents does not change. */
    void shrink_to_fit()
    {
        if (m_capacity > m_size) Reallocate(m_size);
    }

    /** Construct a new element at the end of the deque. */
    template<typename... Args>
    void emplace_back(Args&&... args)
    {
        if (m_size == m_capacity) Reallocate((m_size + 1) * 2);
        std::construct_at(m_buffer + BufferIndex(m_size), std::forward<Args>(args)...);
        ++m_size;
    }

    /** Move-construct a new element at the end of the deque. */
    void push_back(T&& elem) { emplace_back(std::move(elem)); }

    /** Copy-construct a new element at the end of the deque. */
    void push_back(const T& elem) { emplace_back(elem); }

    /** Construct a new element at the beginning of the deque. */
    template<typename... Args>
    void emplace_front(Args&&... args)
    {
        if (m_size == m_capacity) Reallocate((m_size + 1) * 2);
        std::construct_at(m_buffer + BufferIndex(m_capacity - 1), std::forward<Args>(args)...);
        if (m_offset == 0) m_offset = m_capacity;
        --m_offset;
        ++m_size;
    }

    /** Copy-construct a new element at the beginning of the deque. */
    void push_front(const T& elem) { emplace_front(elem); }

    /** Move-construct a new element at the beginning of the deque. */
    void push_front(T&& elem) { emplace_front(std::move(elem)); }

    /** Remove the first element of the deque. Requires !empty(). */
    void pop_front()
    {
        Assume(m_size);
        std::destroy_at(m_buffer + m_offset);
        --m_size;
        ++m_offset;
        if (m_offset == m_capacity) m_offset = 0;
    }

    /** Remove the last element of the deque. Requires !empty(). */
    void pop_back()
    {
        Assume(m_size);
        std::destroy_at(m_buffer + BufferIndex(m_size - 1));
        --m_size;
    }

    /** Get a mutable reference to the first element of the deque. Requires !empty(). */
    T& front() noexcept
    {
        Assume(m_size);
        return m_buffer[m_offset];
    }

    /** Get a const reference to the first element of the deque. Requires !empty(). */
    const T& front() const noexcept
    {
        Assume(m_size);
        return m_buffer[m_offset];
    }

    /** Get a mutable reference to the last element of the deque. Requires !empty(). */
    T& back() noexcept
    {
        Assume(m_size);
        return m_buffer[BufferIndex(m_size - 1)];
    }

    /** Get a const reference to the last element of the deque. Requires !empty(). */
    const T& back() const noexcept
    {
        Assume(m_size);
        return m_buffer[BufferIndex(m_size - 1)];
    }

    /** Get a mutable reference to the element in the deque at the given index. Requires idx < size(). */
    T& operator[](size_t idx) noexcept
    {
        Assume(idx < m_size);
        return m_buffer[BufferIndex(idx)];
    }

    /** Get a const reference to the element in the deque at the given index. Requires idx < size(). */
    const T& operator[](size_t idx) const noexcept
    {
        Assume(idx < m_size);
        return m_buffer[BufferIndex(idx)];
    }

    /** Test whether the contents of this deque is empty. */
    bool empty() const noexcept { return m_size == 0; }
    /** Get the number of elements in this deque. */
    size_t size() const noexcept { return m_size; }
    /** Get the capacity of this deque (maximum size it can have without reallocating). */
    size_t capacity() const noexcept { return m_capacity; }
};

#endif // BITCOIN_UTIL_VECDEQUE_H