#include "benchmark/benchmark.h" | |
#include <cstdlib> | |
#include <random> | |
#define LIMIT 5*1024*1024 | |
enum class Variant { eA, eB }; | |
struct Wrapper { | |
Variant v; | |
char payload[]; | |
}; | |
struct WrapperWithOffset { | |
Variant v; | |
size_t offset; | |
char payload[]; | |
}; | |
struct __attribute__((packed)) A { | |
int64_t a; | |
int64_t b; | |
char arr[16]; | |
int64_t c; | |
}; | |
struct __attribute__((packed)) B { | |
int64_t a; | |
int64_t b; | |
char arr[17]; | |
int64_t c; | |
}; | |
template <typename T> | |
void writeFields(T* t) | |
{ | |
t->a = 12; | |
t->b = 25; | |
t->c = 16; | |
} | |
void writeFieldsWithOffset(A* t, size_t c_offset) | |
{ | |
// make sure a and b are always at the same offset in struct A and struct B | |
static_assert(offsetof(A, a) == offsetof(B, a), "!"); | |
static_assert(offsetof(A, b) == offsetof(B, b), "!"); | |
t->a = 12; | |
t->b = 25; | |
// c will be at the offset we've provided | |
*(int64_t*)(((char*)t + c_offset)) = 16; | |
} | |
void __attribute__((noinline)) writeLessSafe(WrapperWithOffset* w) | |
{ | |
A* a = reinterpret_cast<A*>(w->payload); | |
writeFieldsWithOffset(a, w->offset); | |
} | |
void __attribute__((noinline)) write(Wrapper* w) | |
{ | |
if (w->v == Variant::eA) { | |
writeFields<A>(reinterpret_cast<A*>(w->payload)); | |
} | |
else { | |
writeFields<B>(reinterpret_cast<B*>(w->payload)); | |
} | |
} | |
static void extra(Wrapper*) { } | |
static void extra(WrapperWithOffset* w) | |
{ | |
w->offset = w->v == Variant::eA ? offsetof(A, c) : offsetof(B, c); | |
} | |
template <typename T = Wrapper> | |
static T* populateBuffer(char* buffer, Variant v) | |
{ | |
auto* w = reinterpret_cast<T*>(buffer); | |
new (w) T; | |
w->v = v; | |
if (w->v == Variant::eA) { | |
new (w->payload) A; | |
} | |
else { | |
new (w->payload) B; | |
} | |
// building with gcc-6.3, this isn't supported | |
// if constexpr (std::is_same<T, WrapperWithOffset>::value) { | |
// w->offset = w->v == Variant::eA ? offsetof(A, c) : offsetof(B, c); | |
// } | |
extra(w); | |
return w; | |
} | |
static void BMA(benchmark::State& state) { | |
char buffer[sizeof(Wrapper) + sizeof(A)]; | |
auto* w = populateBuffer(buffer, Variant::eA); | |
while (state.KeepRunning()) { | |
for (size_t i = 0; i < LIMIT; ++i) { | |
write(w); | |
} | |
} | |
auto* v = reinterpret_cast<A*>(w->payload); | |
assert(v->a == 12); | |
assert(v->b == 25); | |
assert(v->c == 16); | |
} | |
static void BMB(benchmark::State& state) { | |
char buffer[sizeof(Wrapper) + sizeof(B)]; | |
auto* w = populateBuffer(buffer, Variant::eB); | |
while (state.KeepRunning()) { | |
for (size_t i = 0; i < LIMIT; ++i) { | |
write(w); | |
} | |
} | |
auto* v = reinterpret_cast<B*>(w->payload); | |
assert(v->a == 12); | |
assert(v->b == 25); | |
assert(v->c == 16); | |
} | |
static void BMA_less_safe(benchmark::State& state) { | |
char buffer[sizeof(WrapperWithOffset) + sizeof(A)]; | |
auto* w = populateBuffer<WrapperWithOffset>(buffer, Variant::eA); | |
while (state.KeepRunning()) { | |
for (size_t i = 0; i < LIMIT; ++i) { | |
writeLessSafe(w); | |
} | |
} | |
auto* v = reinterpret_cast<A*>(w->payload); | |
assert(v->a == 12); | |
assert(v->b == 25); | |
assert(v->c == 16); | |
} | |
static void BMB_less_safe(benchmark::State& state) { | |
char buffer[sizeof(WrapperWithOffset) + sizeof(B)]; | |
auto* w = populateBuffer<WrapperWithOffset>(buffer, Variant::eB); | |
while (state.KeepRunning()) { | |
for (size_t i = 0; i < LIMIT; ++i) { | |
writeLessSafe(w); | |
} | |
} | |
auto* v = reinterpret_cast<B*>(w->payload); | |
assert(v->a == 12); | |
assert(v->b == 25); | |
assert(v->c == 16); | |
} | |
Variant pick() { | |
static std::random_device rd; | |
static std::mt19937 gen(rd()); | |
std::uniform_int_distribution<> dis(1, 2); | |
auto d = dis(gen); | |
if (d == 1) { | |
return Variant::eA; | |
} | |
else if (d == 2) { | |
return Variant::eB; | |
} | |
else { | |
abort(); | |
} | |
} | |
static void BM_switch(benchmark::State& state) { | |
char buffer[sizeof(Wrapper) + std::max(sizeof(A), sizeof(B))]; | |
auto* w = populateBuffer(buffer, Variant::eA); | |
while (state.KeepRunning()) { | |
state.PauseTiming(); | |
auto v = pick(); | |
w = populateBuffer(buffer, v); | |
state.ResumeTiming(); | |
write(w); | |
state.PauseTiming(); | |
if (v == Variant::eA) { | |
auto* v = reinterpret_cast<A*>(w->payload); | |
assert(v->a == 12); | |
assert(v->b == 25); | |
assert(v->c == 16); | |
} | |
else { | |
auto* v = reinterpret_cast<B*>(w->payload); | |
assert(v->a == 12); | |
assert(v->b == 25); | |
assert(v->c == 16); | |
} | |
state.ResumeTiming(); | |
} | |
} | |
static void BM_less_safe_switch(benchmark::State& state) { | |
char buffer[sizeof(WrapperWithOffset) + std::max(sizeof(A), sizeof(B))]; | |
auto* w = populateBuffer<WrapperWithOffset>(buffer, Variant::eA); | |
while (state.KeepRunning()) { | |
state.PauseTiming(); | |
auto v = pick(); | |
w = populateBuffer<WrapperWithOffset>(buffer, v); | |
state.ResumeTiming(); | |
writeLessSafe(w); | |
state.PauseTiming(); | |
if (v == Variant::eA) { | |
auto* v = reinterpret_cast<A*>(w->payload); | |
assert(v->a == 12); | |
assert(v->b == 25); | |
assert(v->c == 16); | |
} | |
else { | |
auto* v = reinterpret_cast<B*>(w->payload); | |
assert(v->a == 12); | |
assert(v->b == 25); | |
assert(v->c == 16); | |
} | |
state.ResumeTiming(); | |
} | |
} | |
BENCHMARK(BMA); | |
BENCHMARK(BMA_less_safe); | |
BENCHMARK(BMB); | |
BENCHMARK(BMB_less_safe); | |
BENCHMARK(BM_switch); | |
BENCHMARK(BM_less_safe_switch); | |
BENCHMARK_MAIN(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment