Cpp dispatch benchmark

A few months ago, I came across this article on this subreddit:

Four Ways to Dispatch a Runtime-Selected Strategy in C++

Most articles on this topic discuss dispatching methods in isolation, focusing heavily on the implementation details of a single approach. However, I wanted to see the bigger picture—not just pitting virtual functions against switch statements, but measuring the actual performance footprint of each strategy in real-world scenarios.

This benchmark does not claim to be academic or to represent absolute truth; I was simply curious to compare fundamentally different approaches. That is why I included not only standard comparisons like virtual functions vs. CRTP, but also deeply nested virtual calls that deliberately stress the instruction cache and branch predictor.

The project is still a work in progress. Living in Ukraine, frequent power outages caused by shelling have significantly limited my available development time.

AI Disclosure: Did I use AI for this project? Yes. Benchmarks starting from #12 were suggested by an LLM, which also helped refine the AVX2 search routine and proofread my English.

You can read about Eytzinger’s binary search here: https://algorithmica.org/en/eytzinger

The benchmark results are here: https://github.com/HarrierDuBoisX/Cpp-dispatch-benchmark/blob/main/result.md

You can find the code itself here: https://github.com/HarrierDuBoisX/Cpp-dispatch-benchmark

6 comments · 65% upvoted · other discussions

comments

HarrierDuBoisX [OP]

I apologize for not providing a detailed enough description of what’s going on here. I assumed that since the source code is available, anyone interested could review it and offer their own feedback. However, if you don’t have the time or desire to do that, I can explain the basic principles and describe how it all works.

The benchmark consists of a file that you run to compile and execute the benchmark: 'run_bench.bat'. A configuration file where you can set the benchmark parameters - such as the number of objects, inheritance depth, and so on: 'bench_config.hpp'. The benchmark generator: 'gen_bench.py'. A file containing functions for selecting and binding the entire benchmark to a single core: 'bench_stabilize.hpp'. A file containing the necessary support functionality: 'bench_core.hpp'.

Actually, out of all the tests, most people will only be interested in the first three variants: sequential invocation of functional objects in a loop, sequential invocation from a sorted array, and invocation from an array with a specified random sequence: Mono, PolySorted, PolyRnd. The remaining cases involve testing different arrangements within the heap, objects of different sizes, and so on.

The Baseline set includes the simplest function calls, function calls within a loop, and so on.

The Virtual set - runs benchmarks for standard virtual function calls, nested virtual calls, multiple and virtual inheritance scenarios, and large vtables.

The Variant set - covers dispatch overhead using std::variant paired with std::visit.

Fat Ptr (Trait Object) - implements dynamic dispatch via two-word pointers (a data pointer and a vtable pointer), mimicking how dyn Trait behaves in Rust.

Additional benchmarks include CRTP (static polymorphism), std::function, direct switch statements, the effect of devirtualization via the final specifier, and binary search (frequently emitted by compilers like GCC, Clang, and MSVC when evaluating sparse switch jump tables)