C++17 std::variant实现为何比动态多态性能慢1.3-1.5倍?
性能差异原因分析
- 调度实现开销不同
虚函数调用的逻辑非常简洁:从对象指针头部读取虚表指针,按固定偏移取对应函数地址直接跳转调用,只有一次内存访问开销。在你的测试场景中,每列对应的比较函数类型固定,CPU分支预测对这种稳定的间接跳转命中率接近100%,几乎没有额外损耗。std::visit在GCC 10中的实现是先读取std::variant的类型标签,做边界校验后再查跳转表调用对应逻辑,比虚函数多了类型标签读取、边界判断两步操作。你的比较逻辑本身只有一次内存访问+相等判断,属于极轻量操作,多出来的调度开销占比被直接放大,最终表现就是整体速度更慢。 - 内存布局没有优势
你的动态多态实现用std::vector<Comp*>存储比较器,每个子类对象的内存布局是虚表指针+两个数据指针,固定大小的内存布局对CPU缓存非常友好。而std::variant<EqualToI, EqualToF>的内存布局是两个数据指针+类型标签,对齐后和虚函数子类大小几乎一致,并没有缓存局部性的优势,抵消了variant原本可能的性能收益。 - 编译器版本优化不足
GCC 10对C++17std::visit的优化还不完善,后续GCC 12及以上版本对variant调度的优化做了大量改进,同测试场景下两者的性能差距会缩小到10%以内,部分场景甚至variant会反超。
基准测试合理性说明
你的实现和测试逻辑没有问题。通常宣传std::variant性能优于虚函数的场景,都是可以在编译期确定variant类型、让编译器直接把visit展开成直接函数调用的场景,这种场景下确实可以完全消除运行期调度开销,比虚函数更快。但你的使用场景是运行期动态生成异构比较器列表,编译器无法做编译期展开,只能走运行期跳转逻辑,这种场景下虚函数的调度效率本身就更高,出现性能差是正常现象。
如果想要进一步优化性能,可以尝试把列类型的顺序和数量在编译期固定,用std::tuple存储比较器,遍历tuple的时候可以完全消除所有运行期调度,性能会比现在两个方案都高1倍以上。
内容的提问来源于stack exchange,提问作者n1r44
相关产品推荐
相关产品推荐

