-
-
Notifications
You must be signed in to change notification settings - Fork 7.8k
Expand file tree
/
Copy pathlinear_allocator.hpp
More file actions
160 lines (139 loc) · 5.2 KB
/
linear_allocator.hpp
File metadata and controls
160 lines (139 loc) · 5.2 KB
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
/**
* @file
* @brief Linear Allocator
* @details Implementation of a linear allocator that pre-allocates memory
* for high speed access. See [Region-based memory
* management](https://en.wikipedia.org/wiki/Region-based_memory_management).
*
* A linear (also known a bump) allocator works by allocating a buffer of
* memory upon initialization, and then handing it out per allocation call. An
* index is used to indicate from where to hand out memory, after doing so it
* will be "bumped up" to the next spot. For this implementation, the next spot
* is ensured to be aligned to the largest type on this plaform.
*
* Note: CPUs access data most efficiently when it is aligned (i.e. data of
* size N is located at a memory address that is a multiple of N). Alignment of
* data matters because depending on the platform, when accessing un-aligned
* data a CPU will have reduced performance or straight up crash. See
* https://en.wikipedia.org/wiki/Data_structure_alignment for more info.
*
* One of the biggest reasons for using a compile-time linear allocator is the
* fact that lifetime of data is limited to the scope of use. We don't have to
* worry about freeing up memory as the stack will take care of that for us, and
* for that reason there is no need for a de-allocate function.
*
*
* Key constraints and limitations of this example:
* - As mentioned above, this implementation has the buffer aligned to the
* std::max_align_t which ensures that fundamental types are aligned to the
* largest type on the current platform. However, when trying to allocate for
* complex types which may be larger, this doesn't work. One solution would be
* to take a note from
* [std::pmr::memory_resource::allocate](https://en.cppreference.com/cpp/memory/memory_resource/allocate)
* and have the alignment as a default parameter.
*
* - It is up to the user to ensure that they do not write past the returned
* bytes. This is the same behaviour as "malloc" or "new".
*
*
* @author [Abhi Patel](https://github.com/B33Boy/)
*/
#pragma once
#include <cstddef> /// for std::byte
/**
* @brief Memory algorithms
* @namespace memory
*/
namespace memory {
/**
* @brief allocator algorithm
* @namespace allocator
*/
namespace allocator {
/**
* @class linear_allocator
* @brief Stack based allocator that is used to hand out bytes of memory
*
* @tparam N
*/
template <size_t N>
class linear_allocator {
public:
constexpr linear_allocator() = default;
// ================= Special Member Functions =================
/**
* For simplicity, we will disable copying or moving the allocator.
*/
~linear_allocator() = default;
linear_allocator(linear_allocator const&) =
delete; ///< disable copy constructor
linear_allocator(linear_allocator&&) noexcept =
delete; ///< disable move constructor
linear_allocator& operator=(linear_allocator const&) =
delete; ///< disable copy assignment operator
linear_allocator& operator=(linear_allocator&&) noexcept =
delete; ///< disable move assignment operator
// ================= Allocataor API =================
/**
* @brief Allocates num_bytes from the internal buffer
* @details Returns a pointer to the next aligned position in the
* buffer.
*
* @param num_bytes number of bytes to allocate
* @return std::byte* to allocate memory, or nullptr upon failure
*/
[[nodiscard]] auto constexpr allocate(size_t num_bytes) noexcept
-> std::byte* {
size_t aligned_next = linear_allocator::align(next_ + num_bytes);
if (aligned_next > N) {
return nullptr;
}
auto* mem = &buffer_[next_];
next_ = aligned_next;
return mem;
}
/**
* @brief resets allocator to the start
*
*/
constexpr void reset() noexcept { next_ = 0; }
/**
* @brief return the offset of the data allocated
*
* @return size_t offset
*/
constexpr auto offset() const noexcept -> size_t { return next_; }
/**
* @brief return the capacity of the allocator
*
* @return size_t capacity
*/
constexpr auto cap() const noexcept -> size_t { return N; }
private:
static constexpr size_t alignment = alignof(std::max_align_t);
alignas(alignment) std::byte buffer_[N];
size_t next_{0};
/**
* @brief return the next aligned offset
*
* @details
* Given any offset, the next aligned value is a multiple of alignment (must
* be power of 2). For example, for an alignment requirement of 8, if the
* offset is in range [0,8] it returns 8, if it's in range [9,16] it
* returns 16.
*
* This works by first overshooting the offset by `alignment - 1`, then
* masking off the low bits to round down to the nearest aligned value.
*
* This trick is only valid when alignment is a power of 2 because there is
* only 1 set bit (e.g. 8 is 0x1000, and 4 is 0x0100)
*
* @param offset byte offset in a buffer
* @return size_t aligned offset
*/
static constexpr auto align(size_t offset) noexcept -> size_t {
return (offset + (alignment - 1)) & ~(alignment - 1);
}
};
} // namespace allocator
} // namespace memory