I was experimenting with an algorithm for arbitrary precision arithmetic and wanted to implement short Integer optimization using std::array. And since I am using std::vector as a dynamic storage for long Integers, I decided to use std::variant<std::array, std::vector> to implement this short Integer optimization.
I wasn't sure about the performance of std::visit, so I tested it using Google benchmark.
It turns out std::visit is faster than normal direct access of an element inside a container.
std::visit is implemented as a switch case on MSVC and besides that std::variant has a larger memory footprint because of the tagged union, how is it possible that std::variant is faster?
Does anybody have an explanation for that?
The test code:
#include <array>
#include <variant>
#include "benchmark/benchmark.h"
static void VecOf_VariantOf_Vec_Arr(benchmark::State& state) {
using ST = std::array<uint8_t, 20>;
std::vector<std::variant<std::vector<uint8_t>, ST>> vec;
vec.reserve(1024 * 1024);
for (auto i = 0u; i < 1024 * 1024; ++i)
vec.emplace_back(std::in_place_type<ST>);
for (auto idx = 0u; auto _ : state) {
std::visit([idx]<typename T0>(T0& e)
{
if constexpr (std::same_as<T0, ST>)
e.fill(static_cast<uint8_t>(idx));
}, vec[idx]);
idx += 1;
idx %= vec.size();
benchmark::DoNotOptimize(vec);
benchmark::DoNotOptimize(idx);
benchmark::ClobberMemory();
}
}
BENCHMARK(VecOf_VariantOf_Vec_Arr);
static void VecOf_Arr(benchmark::State& state) {
std::vector<std::array<uint8_t, 20>> vec;
vec.reserve(1024 * 1024);
for (auto i = 0u; i < 1024 * 1024; ++i)
vec.emplace_back();
for (auto idx = 0u; auto _ : state) {
auto& e = vec[idx];
e.fill(static_cast<uint8_t>(idx));
idx += 1;
idx %= vec.size();
benchmark::DoNotOptimize(vec);
benchmark::DoNotOptimize(idx);
benchmark::ClobberMemory();
}
}
BENCHMARK(VecOf_Arr);
Results on MSVC:
Results on GCC:
Results on Clang:
Here's the benchmark


