Performance of visiting a variant vs accessing single element

Viewed 179

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:

MSVC

Results on GCC:

GCC

Results on Clang:

Clang

Here's the benchmark

0 Answers
Related