-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathquick_bench_some.cpp
More file actions
592 lines (510 loc) · 18.8 KB
/
Copy pathquick_bench_some.cpp
File metadata and controls
592 lines (510 loc) · 18.8 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
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
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
// Copyright (C) Alexander Vaskov 2025
#include <concepts>
#include <cstdint> //ints
#include <cstring> //memcpy
#include <iostream>
#include <memory> // unique_ptr
#include <type_traits>
#include <utility> // forward, move, exchange
#define VX_UNREACHABLE() __builtin_unreachable()
#define VX_SOME_LOG(expr)
#define VX_HARDENED false
namespace vx {
using u16 = std::uint16_t;
using u8 = std::uint8_t;
namespace cfg {
/// ===== [ SBO storage configuration ] ======
struct SBO {
vx::u16 size { 16 };
vx::u16 alignment { alignof(std::max_align_t) };
};
struct some {
SBO sbo {};
bool copy {true};
bool move {true};
bool empty_state {true};
bool check_empty {VX_HARDENED};
};
}// namespace cfg
/// ===== [ FWD Declarations ] =====
template <class Trait, typename T>
struct impl_for;
template <typename Trait, cfg::some>
struct some;
template <typename Trait, std::size_t SBO_capacity, std::size_t alignment>
struct storage_for;
namespace detail {
template <typename> struct is_polymorphic : std::false_type {};
template <typename Trait, cfg::some config>
struct is_polymorphic< some<Trait, config> > : std::true_type {};
} // namespace detail
template <typename T>
concept polymorphic = detail::is_polymorphic<std::remove_cvref_t<T>>::value;
namespace detail {
template <typename T>
struct remove_innermost_const_impl {
using type = T;
};
template <typename T>
struct remove_innermost_const_impl<T const&> {
using type = T&;
};
template <typename T>
struct remove_innermost_const_impl<T const&&> {
using type = T&&;
};
template <typename T>
struct remove_innermost_const_impl<T const*> {
using type = T*;
};
template <typename T>
using remove_innermost_const = typename detail::remove_innermost_const_impl<T>::type;
template <typename Ptr>
concept pointer_like = std::is_pointer_v<Ptr> || requires (Ptr p) {
{ *p };
{ static_cast<bool>(p) };
{ p.operator->() } -> std::convertible_to<decltype( &*p )>;
};
}//namespace detail
template <typename... Ts> struct mix {};
template <typename T>
concept rvalue = std::is_rvalue_reference_v<T&&> && !std::is_const_v<T>;
/// ===== [ IMPL ] =====
template <class Trait, typename T>
struct impl;
namespace detail {
template <typename Trait>
struct extract_first_trait_from {
using type = Trait;
};
template <typename Trait, typename... Traits>
struct extract_first_trait_from<mix<Trait, Traits...>> {
using type = Trait;
};
template <typename>
struct mixed_traits : std::false_type {};
template <typename... Ts>
struct mixed_traits<vx::mix<Ts...>> : std::true_type {};
template <typename T>
using remove_ref_or_ptr_t = std::conditional_t<std::is_reference_v<T>,
std::remove_reference_t<T>,
std::conditional_t<std::is_pointer_v<T>,
std::remove_pointer_t<T>,
T> >;
enum class opcode : vx::u8 {
copy_into,
move_into,
fsome_move_sbo_into,
#if not VX_FSOME_ELIDE_VCALL_ON_MOVE
fsome_move_ptr_into,
#endif
cleanup
};
}//namespace detail
template <typename T>
using first_trait_from = typename detail::extract_first_trait_from<T>::type;
namespace detail {
template <typename T>
constexpr bool is_sbo_eligible_with(u16 SBO_capacity, u16 SBO_alignment) {
return sizeof(T) <= SBO_capacity //< fits into SBO buffer
&& alignof(T) <= SBO_alignment ///< and has lower alignment
&& (SBO_alignment % alignof(T) == 0)
&& (not std::is_move_constructible_v<T> || std::is_nothrow_move_constructible_v<T>);
}
}// namespace detail
struct trait {
trait() = default;
trait (trait const&) = delete;
trait (trait &&) = delete;
virtual ~trait()=default;
private:
template <class Trait, typename T> friend struct impl_for;
template <typename Trait, std::size_t, std::size_t> friend struct storage_for;
virtual void* do_action(detail::opcode, [[maybe_unused]] void* buffer, cfg::SBO, [[maybe_unused]] void* extra=nullptr) { return nullptr; }
};
template <class Trait, typename T>
struct impl_for : Trait {
using value_type = std::conditional_t<std::is_reference_v<T> || std::is_pointer_v<T>,
detail::remove_innermost_const<T>,
T
>;
using Self = std::conditional_t<std::is_reference_v<value_type>,
std::remove_reference_t<value_type>,
std::conditional_t<std::is_pointer_v<value_type>,
std::remove_pointer_t<value_type>,
value_type>
>;
constexpr impl_for() requires std::is_default_constructible_v<T> =default;
impl_for(impl_for const&) = default;
explicit impl_for(std::convertible_to<T> auto&& other)
noexcept(std::is_nothrow_constructible_v<value_type, decltype(other)>)
: self_{std::forward<decltype(other)>(other)} {}
const auto* operator->() const { return get(); }
auto* operator->() { return get(); }
auto* get() & {
if constexpr (std::is_pointer_v<value_type>) {
return self_;
} else if constexpr (detail::pointer_like<value_type>) {
return &*self_;
} else {
return &self_;
}
}
const auto* get() const& {
if constexpr (std::is_pointer_v<value_type>) {
return self_;
} else if constexpr (detail::pointer_like<value_type>) {
return &*self_;
} else {
return &self_;
}
}
auto& self() {
return *get();
}
auto const& self() const {
return *get();
}
private:
[[no_unique_address]] value_type self_{};
protected:
virtual void* do_action(detail::opcode op, [[maybe_unused]] void* buffer, [[maybe_unused]] cfg::SBO sbo, [[maybe_unused]] void* extra=nullptr) override {
switch (op) {
using enum detail::opcode;
case copy_into: if constexpr (std::is_copy_constructible_v<Self>) {
if constexpr (std::is_pointer_v<T>) {
using Data = Self; //detail::remove_ref_or_ptr_t<T>;
Data * p_object = detail::is_sbo_eligible_with<Data>(sbo.size, sbo.alignment) ?
new(buffer) Data( static_cast<Data const&>(self()) )
:
new Data( static_cast<Data const&>(self()) );
auto * p_impl { static_cast<impl<Trait, T> *>( extra ) };
new(p_impl) impl<Trait,T> (p_object);
} else if constexpr (std::is_object_v<T>) {
if (detail::is_sbo_eligible_with<T>(sbo.size, sbo.alignment)) {
return new(buffer) impl<Trait,T>(self_);
}
return new impl<Trait,T>(self_);
}
} break;
///@note move-operation for some<>
case move_into:
if constexpr (vx::rvalue<T&&> && requires { impl<Trait,T>(std::move(self_)); }) {
if constexpr (noexcept(impl<Trait,T>(std::move(self_)))) {
// if (sizeof(T) <= sbo.size && alignof(T) <= sbo.alignment) {
if (detail::is_sbo_eligible_with<T>(sbo.size, sbo.alignment)) {
return new(buffer) impl<Trait,T>(std::move(self_));
}
}
return new impl<Trait, T>(std::move(self_));
} break;
///@note the non-SBO case will be efficiently handled w/o the vcall
case fsome_move_sbo_into:
if constexpr (std::is_pointer_v<T> && std::is_move_constructible_v<Self>) {
using Data = Self; //detail::remove_ref_or_ptr_t<T>;
Data * p_object = detail::is_sbo_eligible_with<Self>(sbo.size, sbo.alignment) ?
new(buffer) Self( std::move(self()) ) // fits into new SBO buffer => in-place move construct
:
new Self( std::move(self()) ); // else, allocate memory for it on the heap
auto * p_impl { static_cast<impl<Trait, T> *>( extra ) };
new(p_impl) impl<Trait,T> (p_object);
} break;
#if not VX_FSOME_ELIDE_VCALL_ON_MOVE
case fsome_move_ptr_into: if constexpr (std::is_pointer_v<T>) {
new(extra) impl<Trait, T> (std::exchange(self_, nullptr));
} break;
#endif
//!@note: used exclusively in fsome
case cleanup: [[unlikely]] {
if constexpr (std::is_pointer_v<value_type>) {
using Data = std::remove_pointer_t<value_type>;
if (buffer != self_) {
delete static_cast<value_type>(self_);
} else {
static_cast<value_type>(self_)->~Data();
}
}
} break;
}
return nullptr;
}
};
template <class CRTP, typename Trait>
struct basic_operations_for {
auto* operator-> () noexcept { return iface(); }
const auto* operator-> () const noexcept { return iface(); }
Trait& operator*() const { return *iface(); }
template <typename Target>
Target* try_get() {
auto * impl = dynamic_cast<CRTP::template impl_type<Target>*>(iface());
return impl ? &impl->self() : nullptr;
}
template <typename Index>
decltype(auto) operator[] (Index&& index) requires requires(Trait & t){ t[std::forward<Index>(index)]; }
{
return (*iface())[std::forward<Index>(index)];
}
decltype(auto) operator! () requires requires(Trait & t){ !t; }
{
return !(*iface());
}
template <typename... Ts>
decltype(auto) operator()(Ts&&... args) requires requires(Trait && t){ t(std::forward<Ts>(args)...); }
{
return (*iface())(std::forward<Ts>(args)...);
}
protected:
auto* iface() { return static_cast<CRTP&>(*this).trait_ptr(); }
const auto* iface() const { return static_cast<CRTP const&>(*this).trait_ptr(); }
};
template <class CRTP, typename>
struct multitrait_support_for {};
template <class CRTP, typename... Traits>
struct multitrait_support_for<CRTP, vx::mix<Traits...>> {};
struct empty_some_ptr_access : std::runtime_error {
using std::runtime_error::runtime_error;
};
template <typename Trait, std::size_t SBO_capacity, std::size_t alignment>
struct storage_for {
using main_trait_t = first_trait_from<Trait>;
template <typename X>
static constexpr bool is_sbo_eligible = detail::is_sbo_eligible_with<X>(SBO_capacity, alignment);
storage_for() = default;
template <typename T>
explicit storage_for(T&& object) {
using impl_type = vx::impl< Trait, std::decay_t<T> >;
if constexpr (is_sbo_eligible<impl_type>) {
p_trait = new(&buffer) impl_type(std::forward<T>(object));
} else {
p_trait = new impl_type(std::forward<T>(object));
}
}
~storage_for() noexcept {
clear();
}
inline void clear() {
if (not p_trait) { return; }
if (this->stored_in_sbo()) {
p_trait->~main_trait_t();
} else {
delete p_trait;
}
}
template <typename T>
inline void set(T&& data) {
using impl_type = vx::impl< Trait, std::decay_t<T> >;
if constexpr (is_sbo_eligible<impl_type>) {
/// [sbo] created in-place in SBO buffer
p_trait = new(&buffer) impl_type(std::forward<T>(data));
} else {
/// [ptr] allocated and assigned to ptr
p_trait = new impl_type(std::forward<T>(data));
}
}
template <std::size_t dest_SBO, std::size_t dest_alignment>
void copy_into(storage_for<Trait, dest_SBO, dest_alignment> & dest) const {
dest.p_trait = (main_trait_t*)p_trait->do_action(detail::opcode::copy_into, (void*)&dest, {dest_SBO, dest_alignment});
}
template <std::size_t dest_SBO, std::size_t dest_alignment>
void move_into(storage_for<Trait, dest_SBO, dest_alignment> & dest) && noexcept {
if (this->stored_in_sbo()) {
dest.p_trait = (main_trait_t*)p_trait->do_action(detail::opcode::move_into, (void*)&dest.buffer, {dest_SBO, dest_alignment});
} else {
dest.p_trait = std::exchange(p_trait, nullptr);
}
}
bool stored_in_sbo() {
return (void*)p_trait == (void*)&buffer;
}
alignas(alignment) std::byte buffer[SBO_capacity];
main_trait_t * p_trait = nullptr;
};
template <typename Trait, std::size_t Alignment>
struct storage_for<Trait, 0, Alignment> {
using main_trait_t = first_trait_from<Trait>;
main_trait_t *p_trait = nullptr;
template <typename X>
static constexpr bool is_sbo_eligible = false;
storage_for() = default;
template <typename T>
explicit storage_for(T&& object) {
using impl_type = vx::impl< Trait, std::decay_t<T> >;
p_trait = new impl_type(std::forward<T>(object));
}
~storage_for() {
clear();
}
inline void clear() {
if (p_trait) delete p_trait;
}
template <typename T>
inline void set(T&& data) {
using impl_type = vx::impl< Trait, std::decay_t<T> >;
p_trait = new impl_type(std::forward<T>(data));
}
template <std::size_t dest_SBO, std::size_t dest_alignment>
void copy_into(storage_for<Trait, dest_SBO, dest_alignment> & dest) const {
dest.p_trait = (main_trait_t*)p_trait->do_action(detail::opcode::copy_into, (void*)&dest.buffer, {dest_SBO, dest_alignment});
}
template <std::size_t dest_SBO, std::size_t dest_alignment>
void move_into(storage_for<Trait, dest_SBO, dest_alignment> & dest) && noexcept {
dest.p_trait = std::exchange(p_trait, nullptr);
}
};
template <typename Trait=vx::trait, cfg::some config=cfg::some{}>
struct some : basic_operations_for<some<Trait, config>, Trait>,
multitrait_support_for<some<Trait, config>, Trait>
{
template <typename X>
using impl_type = vx::impl< Trait, std::remove_cvref_t<X> >;
template <typename T2, cfg::some c2>
friend struct some;
some() requires(config.empty_state) =default;
template <typename T>
requires (not polymorphic<T>)
some (T && obj) requires ((not config.copy || std::is_copy_constructible_v<std::remove_cvref_t<T>>)
&&
(not config.move || std::is_move_constructible_v<std::remove_cvref_t<T>>))
: storage{std::forward<T>(obj)} {}
~some() = default;
some(some const& other) {
other.storage.copy_into(this->storage);
}
some& operator= (some const& other) {
storage.clear();
other.storage.copy_into(this->storage);
return *this;
}
template <cfg::some config2>
some(some<Trait, config2> const& other) {
other.storage.copy_into(this->storage);
}
template <cfg::some config2>
some& operator= (some<Trait, config2> const& other) {
storage.clear();
other.storage.copy_into(this->storage);
return *this;
}
template <cfg::some config2>
some(some<Trait, config2> && other) noexcept {
std::move(other).storage.move_into(this->storage);
}
template <cfg::some config2>
some& operator= (some<Trait, config2> && other) noexcept {
storage.clear();
std::move(other).storage.move_into(this->storage);
return *this;
}
protected:
friend struct basic_operations_for<some<Trait, config>, Trait>;
friend struct multitrait_support_for<some<Trait, config>, Trait>;
const auto* trait_ptr() const { return storage.p_trait; }
auto* trait_ptr() { return storage.p_trait; }
private:
storage_for<Trait, config.sbo.size, config.sbo.alignment> storage;
};
} // namespace vx
#include <random>
/// A classic approach
struct IShape {
virtual ~IShape() = default;
virtual unsigned sides() const noexcept = 0;
virtual void bump() noexcept = 0;
};
struct VSquare final : public IShape {
int side_ = 0;
unsigned sides() const noexcept override { return 4; }
void bump() noexcept override { side_ += 1; }
};
struct VCircle final : public IShape {
int radius_ = 0;
unsigned sides() const noexcept override { return std::numeric_limits<unsigned>::max(); }
void bump() noexcept override { radius_ += 1; }
};
/// Dynamic polymorphism
struct Shape : vx::trait {
virtual unsigned sides() const noexcept = 0;
virtual void bump() noexcept = 0;
};
struct Square { // no inheritance
int side_ = 0;
unsigned sides() const noexcept { return 4; }
void bump() noexcept { side_ += 1; }
};
struct Circle {
int radius_ = 0;
unsigned sides() const noexcept { return std::numeric_limits<unsigned>::max(); }
void bump() noexcept { radius_ += 1; }
};
/// impl for dynamic polymorphism
template <typename T>
struct vx::impl<Shape, T> final : impl_for<Shape, T> {
using impl_for<Shape, T>::impl_for; // pull in the ctors
using impl_for<Shape, T>::self;
unsigned sides() const noexcept override { return self().sides(); }
void bump() noexcept override { self().bump(); }
};
static constexpr std::size_t N = 1'000'000;
static void iterate_and_call_classic(benchmark::State& state) {
std::vector<std::unique_ptr<IShape>> shapes;
shapes.reserve(N);
std::mt19937 mt{}; // default initialized for all tests
for (std::size_t i = 0; i < N; ++i) {
if (mt() % 2 == 0) {
shapes.push_back(std::make_unique<VCircle>());
} else {
shapes.push_back(std::make_unique<VSquare>());
}
}
// Testing the access times when iterating throught the vector
for (auto _ : state) {
std::size_t sides = 0;
for (auto && p_shape : shapes) {
sides += p_shape->sides();
}
benchmark::DoNotOptimize(sides);
}
}
static void iterate_and_call_some(benchmark::State& state) {
std::vector<vx::some<Shape>> shapes;
shapes.reserve(N);
// std::random_device rd;
std::mt19937 mt {};
for (std::size_t i = 0; i < N; ++i) {
if (mt() % 2 == 0) {
shapes.emplace_back(Circle{});
} else {
shapes.emplace_back(Square{});
}
}
for (auto _ : state) {
std::size_t sides = 0;
for (auto && shape : shapes) {
sides += shape->sides();
}
benchmark::DoNotOptimize(sides);
}
}
static void iterate_and_call_some_no_sbo(benchmark::State& state) {
std::vector<vx::some<Shape, vx::cfg::some{.sbo{0}}>> shapes;
shapes.reserve(N);
// std::random_device rd;
std::mt19937 mt {};
for (std::size_t i = 0; i < N; ++i) {
if (mt() % 2 == 0) {
shapes.emplace_back(Circle{});
} else {
shapes.emplace_back(Square{});
}
}
for (auto _ : state) {
std::size_t sides = 0;
for (auto && shape : shapes) {
sides += shape->sides();
}
benchmark::DoNotOptimize(sides);
}
}
BENCHMARK(iterate_and_call_classic);
BENCHMARK(iterate_and_call_some);
BENCHMARK(iterate_and_call_some_no_sbo);