timemory 3.3.0
Modular C++ Toolkit for Performance Analysis and Logging. Profiling API and Tools for C, C++, CUDA, Fortran, and Python. The C++ template API is essentially a framework to creating tools: it is designed to provide a unifying interface for recording various performance measurements alongside data logging and interfaces to other tools.
ring_buffer_allocator.hpp
Go to the documentation of this file.
1// MIT License
2//
3// Copyright (c) 2020, The Regents of the University of California,
4// through Lawrence Berkeley National Laboratory (subject to receipt of any
5// required approvals from the U.S. Dept. of Energy). All rights reserved.
6//
7// Permission is hereby granted, free of charge, to any person obtaining a copy
8// of this software and associated documentation files (the "Software"), to deal
9// in the Software without restriction, including without limitation the rights
10// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11// copies of the Software, and to permit persons to whom the Software is
12// furnished to do so, subject to the following conditions:
13//
14// The above copyright notice and this permission notice shall be included in all
15// copies or substantial portions of the Software.
16//
17// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23// SOFTWARE.
24
25#pragma once
26
29#include "timemory/units.hpp"
31
32#include <cstddef>
33#include <functional>
34#include <memory>
35#include <type_traits>
36#include <utility>
37#include <vector>
38
39namespace tim
40{
41namespace data
42{
43/// \class tim::data::ring_buffer_allocator
44/// \tparam Tp The data type for the allocator
45/// \tparam MMapV Whether to use mmap (if available)
46/// \tparam BuffCntV The default buffer count (will be rounded up to multiple of page
47/// size)
48///
49/// \brief allocator that uses array of (ring) buffers to coalesce memory. Requires
50/// This allocator propagates on container swap and container move assignment.
51/// Use TIMEMORY_RING_BUFFER_ALLOCATOR_BUFFER_COUNT env variable to specify the default
52/// number of allocations or use the `set_buffer_count` / `set_buffer_count_cb`.
53/// When a reserve is requested and the request is greater than the free
54/// spaces in the buffer, the free spaces are stored in a "dangling" array of spaces
55/// which are used when single allocations are requested.
56template <typename Tp, bool MMapV, size_t BuffCntV>
57class ring_buffer_allocator : public std::allocator<Tp>
58{
59public:
60 // The following will be the same for virtually all allocators.
61 using value_type = Tp;
62 using pointer = Tp*;
63 using reference = Tp&;
64 using const_pointer = const Tp*;
65 using const_reference = const Tp&;
66 using size_type = size_t;
67 using difference_type = ptrdiff_t;
68 using base_type = std::allocator<Tp>;
71 using propagate_on_container_swap = std::true_type;
72
73public:
74 // constructors and destructors
79
80public:
81 // operators
82 ring_buffer_allocator& operator=(const ring_buffer_allocator&) = default;
83 ring_buffer_allocator& operator=(ring_buffer_allocator&&) noexcept = default;
84 bool operator==(const ring_buffer_allocator& rhs) const { return (this == &rhs); }
85 bool operator!=(const ring_buffer_allocator& rhs) const { return (this != &rhs); }
86
87public:
88 Tp* address(Tp& _r) const { return &_r; }
89 const Tp* address(const Tp& _r) const { return &_r; }
90
91 size_t max_size() const
92 {
93 // avoid signed/unsigned warnings independent of size_t definition
94 return (static_cast<size_t>(0) - static_cast<size_t>(1)) / sizeof(Tp);
95 }
96
97 // The following must be the same for all allocators.
98 template <typename U>
99 struct rebind
100 {
102 };
103
104 void construct(Tp* const _p, const Tp& _v) const { ::new((void*) _p) Tp{ _v }; }
105
106 void construct(Tp* const _p, Tp&& _v) const { ::new((void*) _p) Tp{ std::move(_v) }; }
107
108 template <typename... ArgsT>
109 void construct(Tp* const _p, ArgsT&&... _args) const
110 {
111 ::new((void*) _p) Tp{ std::forward<ArgsT>(_args)... };
112 }
113
114 void destroy(Tp* const _p) const { _p->~Tp(); }
115
116 Tp* allocate(const size_t n) const
117 {
118 if(n == 0)
119 return nullptr;
120
121 // integer overflow check that throws std::length_error in case of overflow
122 if(n > max_size())
123 {
124 throw std::length_error(
125 "ring_buffer_allocator<Tp>::allocate() - Integer overflow.");
126 }
127
128 // for a single allocation, try to reuse dangling allocations
129 if(n == 1 && !m_buffer_data->dangles.empty())
130 {
131 Tp* _p = m_buffer_data->dangles.back();
132 m_buffer_data->dangles.pop_back();
133 return _p;
134 }
135
136 // ensure m_buffer_data->current is assigned and a buffer has been created
137 init_current(n);
138
139 // when n is greater than the remainder of the ring buffer allocation,
140 // add the remainder of the allocation to the dangling allocations and
141 // then reinitialize current with n to ensure that the memory is contiguous
142 if(n > m_buffer_data->current->free())
143 {
144 m_buffer_data->dangles.reserve(m_buffer_data->dangles.size() +
145 m_buffer_data->current->free());
146 for(size_t i = 0; i < m_buffer_data->current->free(); ++i)
147 {
148 auto _req = m_buffer_data->current->request();
149 if(_req)
150 break;
151 m_buffer_data->dangles.emplace_back(_req);
152 }
153 // ensure a new buffer is created
154 m_buffer_data->current = nullptr;
155 // new buffer will have at least n pages of contiguous memory
156 init_current(n);
157 }
158
159 // get first entry
160 auto* _p = m_buffer_data->current->request();
161
162 // ensure that extra remainder is marked as used by ring buffer
163 for(size_t i = 1; i < n; ++i)
164 m_buffer_data->current->request();
165
166 return _p;
167 }
168
169 void deallocate(Tp* const ptr, const size_t n) const
170 {
171 // reserve if n > 1
172 if(n > 1)
173 m_buffer_data->dangles.reserve(m_buffer_data->dangles.size() + n);
174 // place all "deallocated" pointers in dangling array
175 for(size_t i = 0; i < n; ++i)
176 {
177 Tp* _p = ptr + i;
178 m_buffer_data->dangles.emplace_back(_p);
179 }
180 }
181
182 Tp* allocate(const size_t n, const void* const /* hint */) const
183 {
184 return allocate(n);
185 }
186
187 void reserve(const size_t n) { init_current(n); }
188
189 /// define a callback function for initializing the buffer size. Will throw
190 /// if a request for the buffer size has already occured.
191 template <typename FuncT>
192 static void set_buffer_count_cb(FuncT&& _f)
193 {
194 if(BuffCntV > 0 || get_config_data().initialized)
195 throw std::runtime_error("Error! Buffer size has already been fixed");
196 get_config_data().buffer_count_cb = std::forward<FuncT>(_f);
197 }
198
199 /// set the minimum number of objects for the ring buffer. Will throw if a request for
200 /// the buffer size has already occured.
201 static void set_buffer_count(size_t _buff_sz)
202 {
203 set_buffer_count_cb([_buff_sz]() { return _buff_sz; });
204 }
205
206 /// transfers the buffers to another allocator
208 {
209 m_buffer_data->buffers.reserve(m_buffer_data->buffers.size() +
210 rhs.m_buffer_this.buffers.size());
211 m_buffer_data->dangles.reserve(m_buffer_data->dangles.size() +
212 rhs.m_buffer_this.dangles.size());
213 for(auto& itr : rhs.m_buffer_this.buffers)
214 m_buffer_data->buffers.emplace_back(std::move(itr));
215 for(auto& itr : rhs.m_buffer_this.dangles)
216 m_buffer_data->dangles.emplace_back(itr);
217 if(m_buffer_data->current == nullptr)
218 m_buffer_data->current = rhs.m_buffer_this.current;
219 rhs.m_buffer_data = m_buffer_data;
220 }
221
222#if !defined(TIMEMORY_INTERNAL_TESTING)
223private:
224#endif
225
226 struct config_data
227 {
228 bool initialized = false;
229 size_t buffer_count = 0;
230 std::function<size_t()> buffer_count_cb = []() {
231 return get_env<size_t>("TIMEMORY_RING_BUFFER_ALLOCATOR_BUFFER_COUNT",
232 units::get_page_size() / sizeof(Tp));
233 };
234
235 size_t get()
236 {
237 initialized = true;
238 return (BuffCntV > 0) ? BuffCntV : buffer_count_cb();
239 }
240 };
241
242 struct buffer_data
243 {
244 buffer_type* current = nullptr;
245 std::vector<std::unique_ptr<buffer_type>> buffers = {};
246 std::vector<Tp*> dangles = {};
247 };
248
249 static config_data& get_config_data()
250 {
251 static config_data _v{};
252 return _v;
253 }
254
255 static size_t get_buffer_count()
256 {
257 // comma operator to set init to true
258 static auto _v = get_config_data().get();
259 return _v;
260 }
261
262 buffer_data* get_buffer_data() const { return m_buffer_data; }
263
264private:
265 void init_current(size_t n) const
266 {
267 if(m_buffer_data->current == nullptr || m_buffer_data->current->is_full())
268 {
269 auto _n = std::max<size_t>(n, get_buffer_count());
270 auto _uniq = std::make_unique<buffer_type>(_n, MMapV);
271 m_buffer_data->buffers.emplace_back(std::move(_uniq));
272 m_buffer_data->current = m_buffer_data->buffers.back().get();
273 }
274 }
275
276 mutable buffer_data m_buffer_this{};
277 mutable buffer_data* m_buffer_data = &m_buffer_this;
278};
279} // namespace data
280} // namespace tim
allocator that uses array of (ring) buffers to coalesce memory. Requires This allocator propagates on...
void construct(Tp *const _p, ArgsT &&... _args) const
void construct(Tp *const _p, Tp &&_v) const
ring_buffer_allocator(ring_buffer_allocator &&) noexcept=default
ring_buffer_allocator(const ring_buffer_allocator &)=default
data_storage::ring_buffer< Tp > buffer_type
void construct(Tp *const _p, const Tp &_v) const
bool operator!=(const ring_buffer_allocator &rhs) const
void deallocate(Tp *const ptr, const size_t n) const
static void set_buffer_count_cb(FuncT &&_f)
define a callback function for initializing the buffer size. Will throw if a request for the buffer s...
Tp * allocate(const size_t n, const void *const) const
static void set_buffer_count(size_t _buff_sz)
set the minimum number of objects for the ring buffer. Will throw if a request for the buffer size ha...
const Tp * address(const Tp &_r) const
void steal_resources(ring_buffer_allocator &rhs)
transfers the buffers to another allocator
Definition: kokkosp.cpp:39
std::array< char *, 4 > _args
auto get(const auto_bundle< Tag, Types... > &_obj)
Ring buffer wrapper around tim::base::ring_buffer for data of type Tp. If the data object size is lar...