Why is dynamic polymorphism faster than using std::variant?

111 views Asked by At

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:

Benchmark result

quick-bench links:

https://quick-bench.com/q/M5jLqEZjHWorV8soKmWNa3M5_ng https://quick-bench.com/q/I8C4qq4nv8Z7c9u9PaWcIBH-t1k

0

There are 0 answers