sparrow 1.4.0
C++20 idiomatic APIs for the Apache Arrow Columnar Format
Loading...
Searching...
No Matches
allocator.hpp
Go to the documentation of this file.
1// Copyright 2024 Man Group Operations Limited
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#pragma once
16
17#include <memory>
18#include <memory_resource>
19#include <type_traits>
20#include <typeindex>
21#include <variant>
22
25
26namespace sparrow
27{
28 /*
29 * allocator concept, based on the requirements specified by the standard:
30 * https://en.cppreference.com/w/cpp/named_req/Allocator.
31 */
32 template <class T>
33 concept allocator = std::copy_constructible<std::remove_cvref_t<T>>
34 and std::move_constructible<std::remove_cvref_t<T>>
35 and std::equality_comparable<std::remove_cvref_t<T>>
36 and requires(
37 std::remove_cvref_t<T>& alloc,
38 typename std::remove_cvref_t<T>::value_type* p,
39 std::size_t n
40 ) {
41 typename std::remove_cvref_t<T>::value_type;
42 {
43 alloc.allocate(n)
44 } -> std::same_as<typename std::remove_cvref_t<T>::value_type*>;
45 { alloc.deallocate(p, n) } -> std::same_as<void>;
46 };
47
48 /*
49 * When the allocator A with value_type T satisfies this concept, any_allocator
50 * can store it as a value in a small buffer instead of having to type-erased it
51 * (Small Buffer Optimization).
52 */
53 template <class A, class T>
55 && (std::same_as<std::remove_cvref_t<A>, std::allocator<T>>
56 || std::same_as<std::remove_cvref_t<A>, std::pmr::polymorphic_allocator<T>>
57 || std::same_as<std::remove_cvref_t<A>, xsimd::aligned_allocator<T>>);
58
59 /*
60 * Type erasure class for allocators. This allows to use any kind of allocator
61 * (standard, polymorphic) without having to expose it as a template parameter.
62 *
63 * @tparam T value_type of the allocator
64 */
65 template <class T>
67 {
68 public:
69
70 using value_type = T;
71
72 template <allocator A>
73 using allocator_value_type = typename std::allocator_traits<std::decay_t<A>>::value_type;
74
75 constexpr any_allocator();
76 constexpr any_allocator(const any_allocator& rhs);
77 constexpr any_allocator(any_allocator&&) noexcept;
78
79 constexpr any_allocator& operator=(const any_allocator& rhs) = delete;
80 constexpr any_allocator& operator=(any_allocator&& rhs) = delete;
81
82 template <class A>
83 constexpr any_allocator(A&& alloc)
84 requires(
85 not std::same_as<std::remove_cvref_t<A>, any_allocator> and allocator<A>
86 and std::same_as<allocator_value_type<A>, T>
87 )
88 : m_storage(make_storage(std::forward<A>(alloc)))
89 {
90 }
91
92 [[nodiscard]] constexpr T* allocate(std::size_t n);
93 constexpr void deallocate(T* p, std::size_t n);
94
96
97 [[nodiscard]] constexpr bool equal(const any_allocator& rhs) const;
98
99 private:
100
101 struct interface
102 {
103 [[nodiscard]] virtual T* allocate(std::size_t) = 0;
104 virtual void deallocate(T*, std::size_t) = 0;
105 [[nodiscard]] virtual std::unique_ptr<interface> clone() const = 0;
106 [[nodiscard]] virtual bool equal(const interface&) const = 0;
107 virtual ~interface() = default;
108 };
109
110 template <allocator A>
111 struct impl : interface
112 {
113 A m_alloc;
114
115 constexpr explicit impl(A alloc)
116 : m_alloc(std::move(alloc))
117 {
118 }
119
120 [[nodiscard]] constexpr T* allocate(std::size_t n) override
121 {
122 return m_alloc.allocate(n);
123 }
124
125 constexpr void deallocate(T* p, std::size_t n) override
126 {
127 m_alloc.deallocate(p, n);
128 }
129
130 [[nodiscard]] std::unique_ptr<interface> clone() const override
131 {
132 return std::make_unique<impl<A>>(m_alloc);
133 }
134
135 [[nodiscard]] constexpr bool equal(const interface& rhs) const override
136 {
137 if (std::type_index(typeid(*this)) == std::type_index(typeid(rhs)))
138 {
139 return m_alloc == static_cast<const impl<A>*>(std::addressof(rhs))->m_alloc;
140 }
141 return false;
142 }
143 };
144
145 using storage_type = std::variant<
146 std::allocator<T>,
147 std::pmr::polymorphic_allocator<T>,
148 xsimd::aligned_allocator<T>,
149 std::unique_ptr<interface>>;
150
151 template <class A>
152 [[nodiscard]] std::unique_ptr<interface> make_storage(A&& alloc) const
153 {
154 return std::make_unique<impl<std::decay_t<A>>>(std::forward<A>(alloc));
155 }
156
157 template <class A>
158 requires can_any_allocator_sbo<A, T>
159 [[nodiscard]] constexpr A&& make_storage(A&& alloc) const
160 {
161 return std::forward<A>(alloc);
162 }
163
164 [[nodiscard]] constexpr storage_type copy_storage(const storage_type& rhs) const
165 {
166 return std::visit(
168 [](const auto& arg) -> storage_type
169 {
170 return {std::decay_t<decltype(arg)>(arg)};
171 },
172 [](const std::unique_ptr<interface>& arg) -> storage_type
173 {
174 return {arg->clone()};
175 }
176 },
177 rhs
178 );
179 }
180
181 template <class F>
182 [[nodiscard]] constexpr decltype(auto) visit_storage(F&& f)
183 {
184 return std::visit(
185 [&f](auto&& arg)
186 {
187 using A = std::decay_t<decltype(arg)>;
188 if constexpr (can_any_allocator_sbo<A, T>)
189 {
190 return f(arg);
191 }
192 else
193 {
194 return f(*arg);
195 }
196 },
197 m_storage
198 );
199 }
200
201 storage_type m_storage;
202 };
203
204 /********************************
205 * any_allocator implementation *
206 ********************************/
207
208 template <class T>
210 : m_storage(make_storage(xsimd::aligned_allocator<T>()))
211 {
212 }
213
214 template <class T>
216 : m_storage(copy_storage(rhs.m_storage))
217 {
218 }
219
220 template <class T>
222 : m_storage(std::move(rhs.m_storage))
223 {
224 // The possible fix would be to not propagate the allocator,
225 // and copy it when the container using it is moved.
226 // However, this means potentially cloning a polymorphic
227 // allocator, hitting performance drastically. A moved
228 // allocator might be used for trying to deallocate an empty
229 // memory zone (the memory of the moved container), wich is
230 // a noop, but the object must exist so that alloc.deallocate
231 // is valid.
232 rhs.m_storage = xsimd::aligned_allocator<T>();
233 }
234
235 template <class T>
236 [[nodiscard]] constexpr T* any_allocator<T>::allocate(std::size_t n)
237 {
238 return visit_storage(
239 [n](auto& allocator)
240 {
241 return allocator.allocate(n);
242 }
243 );
244 }
245
246 template <class T>
247 // This is needed to avoid AddressSanitizer false positives
248#if defined(_MSC_VER) && !defined(__clang__) // MSVC
249 __declspec(no_sanitize_address)
250#else
251# if defined(__has_feature)
252# if __has_feature(address_sanitizer)
253 __attribute__((no_sanitize("address")))
254# endif
255# endif
256#endif
257 void constexpr any_allocator<T>::deallocate(T* p, std::size_t n)
258 {
259 return visit_storage(
260 [n, p](auto& allocator)
261 {
262#if defined(__GNUC__)
263# pragma GCC diagnostic push
264# pragma GCC diagnostic ignored "-Wmismatched-new-delete"
265# pragma GCC diagnostic ignored "-Wnull-dereference"
266#endif
267 return allocator.deallocate(p, n);
268#if defined(__GNUC__)
269# pragma GCC diagnostic pop
270#endif
271 }
272 );
273 }
274
275 template <class T>
280
281 template <class T>
282 constexpr bool any_allocator<T>::equal(const any_allocator& rhs) const
283 {
284 // YOLO!!
285 return std::visit(
286 [&rhs](auto&& arg)
287 {
288 using A = std::decay_t<decltype(arg)>;
289 if constexpr (can_any_allocator_sbo<A, T>)
290 {
291 return std::visit(
292 [&arg](auto&& arg2)
293 {
294 using A2 = std::decay_t<decltype(arg2)>;
295 if constexpr (can_any_allocator_sbo<A2, T> && std::same_as<A, A2>)
296 {
297 return arg == arg2;
298 }
299 else
300 {
301 return false;
302 }
303 },
304 rhs.m_storage
305 );
306 }
307 else
308 {
309 return std::visit(
310 [&arg](auto&& arg2)
311 {
312 using A2 = std::decay_t<decltype(arg2)>;
313 if constexpr (can_any_allocator_sbo<A2, T>)
314 {
315 return false;
316 }
317 else
318 {
319 return arg->equal(*arg2);
320 }
321 },
322 rhs.m_storage
323 );
324 }
325 },
326 m_storage
327 );
328 }
329
330 template <class T>
331 constexpr bool operator==(const any_allocator<T>& lhs, const any_allocator<T>& rhs)
332 {
333 return lhs.equal(rhs);
334 }
335}
constexpr void deallocate(T *p, std::size_t n)
constexpr any_allocator()
constexpr any_allocator(any_allocator &&) noexcept
constexpr any_allocator select_on_container_copy_construction() const
constexpr bool equal(const any_allocator &rhs) const
typename std::allocator_traits< std::decay_t< A > >::value_type allocator_value_type
Definition allocator.hpp:73
constexpr T * allocate(std::size_t n)
constexpr any_allocator(const any_allocator &rhs)
Allocator for aligned memory.
SPARROW_API bool operator==(const array &lhs, const array &rhs)
Compares the content of two arrays.
overloaded(Ts...) -> overloaded< Ts... >
Extensions to the C++ standard library.