You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何动态多态比std::variant更快?测试结果存疑求解

动态多态与std::variant性能对比的疑问

我原本认为,动态多态会因为虚表查找、缓存缺失以及基类指针的内存分配问题,性能弱于静态多态(比如用std::variant实现的)。于是我编写了测试用例对比两者性能,但测试结果却和预期相反——无论是否开启编译器优化,std::variant的性能要么和动态多态相当,要么更差。这是为什么?难道创建lambda的开销真的这么大?


测试用例代码

动态多态的接口与派生类

struct IValue {
    virtual double GetValue(double) const  = 0;
    virtual ~IValue() = default;
};

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);
    }
};

静态多态的无虚函数类

struct First {
    double GetValue(double p) const {
        return std::sin(p);
    }
};

struct Second {
    double GetValue(double p) const {
        return std::cos(p);
    }
};

测试函数实现

动态多态测试函数

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);
        }
    }
}

第一种std::variant测试函数

template<class... Ts>
struct Overloaded : Ts... { using Ts::operator()...; };

template<class... Ts>
Overloaded(Ts...) -> Overloaded<Ts...>;

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);
        }
    }
}

第二种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);
        }
    }
}

性能不符合预期的原因
  1. 核心计算开销主导:测试中std::sin和std::cos属于高耗时的数学运算,它们的执行开销远大于动态多态的虚函数调用或std::variant的类型分支判断。整体性能被三角函数计算完全主导,两种多态方式的差异被彻底掩盖。

  2. 编译器的去虚拟化优化:现代编译器在开启优化后,会对可预测的虚函数调用做去虚拟化处理。你的测试中,DerivedFirst和DerivedSecond的实例是按固定顺序添加到vector的,编译器可以推断出每个指针的实际类型,直接将虚函数调用替换为普通函数调用,完全消除了动态多态的开销。

  3. std::visit的额外开销:虽然std::variant是静态分发,但std::visit的实现需要处理类型分支逻辑,再加上你在循环内每次创建Overloaded和lambda对象(即便编译器会优化部分,但仍可能产生额外指令开销),这部分开销在核心计算占比低的场景下不明显,但在当前测试中,核心计算已经占满CPU,这部分额外开销会让std::variant的表现不如被优化后的动态多态。

  4. 缓存友好性未体现差异:std::variant的对象直接存储在vector中,而动态多态的vector存储的是指向堆对象的指针。但你的测试中,堆对象是连续创建的,内存布局较为连续,缓存缺失的问题并不严重,所以动态多态的缓存劣势没有显现。如果vector中的对象类型是随机分布的,缓存缺失的影响才会凸显出来。


内容的提问来源于stack exchange,提问作者OHtuzh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 01:35:02