sparrow
2.4.0
C++20 idiomatic APIs for the Apache Arrow Columnar Format
Toggle main menu visibility
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
23
#include "
sparrow/details/3rdparty/xsimd_aligned_allocator.hpp
"
24
#include "
sparrow/utils/variant_visitor.hpp
"
25
26
namespace
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>
54
concept
can_any_allocator_sbo
=
allocator<A>
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>
66
class
any_allocator
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
95
[[nodiscard]]
constexpr
any_allocator
select_on_container_copy_construction
()
const
;
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(
167
overloaded
{
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>
209
constexpr
any_allocator<T>::any_allocator
()
210
: m_storage(make_storage(
xsimd
::aligned_allocator<T>()))
211
{
212
}
213
214
template
<
class
T>
215
constexpr
any_allocator<T>::any_allocator
(
const
any_allocator
& rhs)
216
: m_storage(copy_storage(rhs.m_storage))
217
{
218
}
219
220
template
<
class
T>
221
constexpr
any_allocator<T>::any_allocator
(
any_allocator
&& rhs) noexcept
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>
276
constexpr
any_allocator<T>
any_allocator<T>::select_on_container_copy_construction
()
const
277
{
278
return
any_allocator
(*
this
);
279
}
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
}
sparrow::any_allocator
Definition
allocator.hpp:67
sparrow::any_allocator::deallocate
constexpr void deallocate(T *p, std::size_t n)
Definition
allocator.hpp:257
sparrow::any_allocator::any_allocator
constexpr any_allocator()
Definition
allocator.hpp:209
sparrow::any_allocator::select_on_container_copy_construction
constexpr any_allocator select_on_container_copy_construction() const
Definition
allocator.hpp:276
sparrow::any_allocator::equal
constexpr bool equal(const any_allocator &rhs) const
Definition
allocator.hpp:282
sparrow::any_allocator::value_type
T value_type
Definition
allocator.hpp:70
sparrow::any_allocator::allocator_value_type
typename std::allocator_traits< std::decay_t< A > >::value_type allocator_value_type
Definition
allocator.hpp:73
sparrow::any_allocator::allocate
constexpr T * allocate(std::size_t n)
Definition
allocator.hpp:236
xsimd::aligned_allocator
Allocator for aligned memory.
Definition
xsimd_aligned_allocator.hpp:50
sparrow::allocator
Definition
allocator.hpp:33
sparrow::can_any_allocator_sbo
Definition
allocator.hpp:54
sparrow::impl
Definition
data_type.hpp:847
sparrow
Definition
array.hpp:21
sparrow::operator==
SPARROW_API bool operator==(const array &lhs, const array &rhs)
Compares the content of two arrays.
sparrow::overloaded
overloaded(Ts...) -> overloaded< Ts... >
std
Extensions to the C++ standard library.
Definition
float16_t.hpp:1916
xsimd
Definition
xsimd_aligned_allocator.hpp:28
variant_visitor.hpp
xsimd_aligned_allocator.hpp
sparrow
buffer
allocator.hpp
Generated by
1.17.0