I know that because of virtual tables, cache misses and memory allocation for pointers to the base class, dynamic polymorphism is slow. Especially when compared to static polymorphism.
I decided to write a test that compares which is faster: dynamic polymorphism or std::variant.
I wrote an interface:
struct IValue {
virtual double GetValue(double) const = 0;
virtual ~IValue() = default;
};
As well as his two heirs:
struct DerivedFirst : public IValue {
double GetValue(double p) const override {
return std::sin(p);
}
};
struct DerivedSecond : public IValue {
double GetValue(double p) const override {
return std::cos(p);
}
};
I also wrote 2 similar classes but without virtual methods:
struct First {
double GetValue(double p) const {
return std::sin(p);
}
};
struct Second {
double GetValue(double p) const {
return std::cos(p);
}
};
And a couple comparisons were written:
template<class... Ts>
struct Overloaded : Ts... { using Ts::operator()...; };
template<class... Ts>
Overloaded(Ts...) -> Overloaded<Ts...>;
void TestPol() {
std::vector<std::unique_ptr<IValue>> pol;
for (int i = 0; i < 10'000; ++i) {
pol.push_back(std::make_unique<DerivedFirst>());
pol.push_back(std::make_unique<DerivedSecond>());
}
int res = 0;
for (double p = 0.0; p <= 1000.0; p += 1.0) {
for (const auto& el: pol) {
res += el->GetValue(p);
}
}
}
void TestVar() {
std::vector<std::variant<First, Second>> var;
for (int i = 0; i < 10'000; ++i) {
var.push_back(First());
var.push_back(Second());
}
int res = 0;
for (double p = 0.0; p <= 1000.0; p += 1.0) {
for (const auto& v: var) {
res += std::visit(Overloaded{
[p] (const First& f) { return f.GetValue(p); },
[p] (const Second& s) { return s.GetValue(p); }
}, v);
}
}
}
And the same, but no value is returned from std::variant:
void TestVar() {
std::vector<std::variant<First, Second>> var;
for (int i = 0; i < 10'000; ++i) {
var.push_back(First());
var.push_back(Second());
}
int res = 0;
for (double p = 0.0; p <= 1000.0; p += 1.0) {
for (const auto& v: var) {
std::visit(Overloaded{
[&res, p] (const First& f) { res += f.GetValue(p); },
[&res, p] (const Second& s) { res += s.GetValue(p); }
}, v);
}
}
}
Running a quick-bench test I in all cases (with and without compiler optimizations) std::variant was either comparable or losing. Why does this happen? After all, by all factors std::variant should be faster. Is it really so expensive to create a lambda?
Example of benchmark result:
quick-bench links:
https://quick-bench.com/q/M5jLqEZjHWorV8soKmWNa3M5_ng https://quick-bench.com/q/I8C4qq4nv8Z7c9u9PaWcIBH-t1k