r/cpp • • 1d ago

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

5 Upvotes

6 comments sorted by

2

u/HarrierDuBoisX 22h ago

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)

2

u/azswcowboy 1d ago

I’m struggling to understand the wall of numbers in the table. If we just take ‘01 Baseline: Direct Call (AoS)’ labeled runs there’s wide divergence in the nanoseconds per operation — so I’m not sure what I’m seeing?

1

u/HarrierDuBoisX 23h ago

I apologize for forgetting to rename it. This benchmark came from another set of benchmarks where I measured performance for arrays of structures (AoS), structures of arrays (SoA), or arrays of structures of arrays (AoSoA).

In this test, this test serves as one of the simplest cases - calling a class member funtcion within a loop.

1

u/JVApen Clever is an insult, not a compliment. - T. Winters 1d ago

I see you are using a recent msvc compiler on Windows. It would be interesting to see how this behaves with other compilers as well. Same for other platforms.

Why? Because different compilers optimize differently, so what might be better on one could be worse for the other. Different platforms are useful due to calling conventions, which also can have a measurable impact. (For example: On Linux string_view is passed via 2 registers why on Windows this is passed as a pointer)

Not saying you have to do this all yourself, though you should be aware of that. The easiest way to continue would be by installing LLVM and using clang-cl. Either from the website or an older version via the visual studio installer.

2

u/HarrierDuBoisX 23h ago

Yes, you're right, but unfortunately I only have access to the Windows operating system and this compiler. I can run everything else in a virtual machine, but that would affect the quality of the benchmark. However, the overall structure of the benchmark is fairly simple, and it can be ported to other compilers and platforms fairly easily.

2

u/ChatFrais 17h ago

Agree,
Library and compiler used have different way to optimise the same dispatch.
this small bench show gcc/llvm and msvc (in comment)
https://stackoverflow.com/questions/57726401/stdvariant-vs-inheritance-vs-other-ways-performance/65632611