虚拟继承对性能的影响

29

我考虑在实时应用程序中使用虚继承。使用虚继承是否会像调用虚函数一样对性能产生影响?所涉及的对象仅在启动时创建,但我担心是否所有来自层次结构的函数都将通过vtable进行分派,还是只有来自虚基类的函数。

我正在考虑在实时应用程序中使用虚继承。使用虚继承是否会像调用虚函数一样对性能产生影响?所涉及的对象仅在启动时创建,但我担心是否所有来自层次结构的函数都将通过vtable进行分派,还是只有来自虚基类的函数。

1
除非您使用多重继承,否则没有必要使用虚拟继承。 - Zac Howland
@ZacHowland,除非您正在使用Google Test(gtest)进行模拟。 - Prof. Falken
3
@Amigable:没有“除非”的情况。在使用Google Test时,处理多重继承时仍然只使用虚拟继承。 - Zac Howland
@ZacHowland,那我错了。我以为能够模拟类是必要的。但实际上我还没有开始使用它...所以我知道什么呢。 - Prof. Falken
Google Test框架允许您为非纯抽象类创建模拟对象,因此当您从这些类继承时,可能会遇到钻石情况(多重继承),这将需要虚拟继承。然而,使用纯抽象(也称为“接口”)并使用新实现模拟接口仍然是更好的实践。这将使您的测试代码更易于编写和维护(除了使您的实际代码更易于编写和维护)。 - Zac Howland
4个回答

32
常见的实现会使得对虚基类的数据成员访问需要使用额外的间接引用。
正如James在评论中指出的那样,在多重继承场景下调用基类的成员函数需要调整this指针,如果该基类是虚拟的,则基类子对象在派生类对象中的偏移量取决于派生类的动态类型并且需要在运行时计算。
这是否对真实应用程序产生任何可见的性能影响取决于许多因素:
- 虚基类是否有数据成员?通常,需要从抽象基类进行虚拟派生,并且具有任何数据成员的抽象基类通常是代码异味。 - 假设您有带有数据成员的虚基类,那么这些数据成员是否在关键路径中被访问?如果用户单击GUI中的某个按钮导致几十个附加间接引用,则没有人会注意到。 - 如果避免使用虚基类,那么将是什么替代方案?不仅可能设计较差,而且替代设计很可能也会对性能产生影响。毕竟,它必须实现相同的目标,而TANSTAAFL。然后,您为一个性能损失交换了另一个性能损失和一个较差的设计。

额外说明:请查看Stan Lippmann的深入理解C++对象模型,该书对这些问题进行了很彻底的回答。


请澄清,只有对虚基类成员/函数的调用会导致额外的间接性吗? - Graeme
@Graeme:我在这个领域肯定不是专家,但我不明白调用虚基类成员函数会导致性能损失。它们要么静态分派(B::f()),要么通过派生类的虚表动态分派,就像非虚基类的成员函数一样。以我之见。 - sbi
3
如果函数不是虚函数,那么将要调用的函数可以在编译时静态地选择,但是必须在运行时计算(或查找)this指针,对吗? - James McNellis
@James:哦,没错,我忘记了this指针的调整。但这是由于_多重_继承,无论是虚拟的还是非虚拟的,还是有其他我忘记的额外成本吗? - sbi
1
@sbi:我认为这是计算和查找之间的区别。在非虚拟多重继承中,您可以添加已知偏移量以获取this指针。在虚拟多重继承中,您必须去查找(在运行时)基类子对象的位置以获取this指针。查找比加法“更昂贵”,但该差异的相关性取决于应用程序:正如您所说;除非这是具有实时性能约束的程序中的时间关键操作,否则不会有影响。 - James McNellis
显示剩余5条评论

4

请看以下在OOPSLA'96上发表的大规模实验研究。我复制粘贴了一条bibtex条目、摘要和论文链接。我认为这是迄今为止关于这个主题最全面的实验研究。

@article{driesen1996direct,
  title={{The direct cost of virtual function calls in C++}},
  author={Driesen, K. and H{\\"o}lzle, U.},
  journal={ACM Sigplan Notices},
  volume={31},
  number={10},
  pages={306--323},
  issn={0362-1340},
  year={1996},
  publisher={ACM}
}
摘要: 我们研究了C++程序中虚函数调用的直接成本,假设使用虚函数表的标准实现。我们通过可执行文件检查和处理器模拟的组合来实验测量了许多大型基准程序的开销。我们的结果显示,所测量的C++程序在分派代码中花费的时间中位数为5.2%,指令中花费的时间中位数为3.7%。对于程序的“所有虚函数”版本,中位数开销升至13.7%(指令的13%)。虚函数表实现的“thunk”变体相对于标准实现将开销中位数降低了21%。在未来的处理器上,这些开销可能会适度增加。

http://www.cs.ucsb.edu/~urs/oocsb/papers/oopsla96.pdf


5
如果编译器优化使得这项研究过时了,我并不感到惊讶。该研究发表于1996年。 - Brandon Kohn
12
嗯,但问题是关于___虚拟继承___,而不是关于___虚拟函数___。 - sbi
4
这项研究是在1995年的处理器上进行的。研究对象只有256K缓存(8K指令缓存)的32位处理器。现在的桌面处理器是64位且具有8MB缓存。研究所用的英特尔奔腾Pro处理器没有MMX技术。这就好像将Windows 95与Windows 10进行比较(这是一个修辞评论,请不要这样做)。 - David Thomas

0

你确定你要使用虚拟继承吗?如果是这样,它与普通虚函数调用的成本相同。vtable链接搜索只是按照指定路径进行。

你说这是在启动时。你的磁盘开销(仅从将代码加载到内存中)很可能需要比vtable查找的半打指令花费更多的时间数量级。如果你能够对其进行分析并检测出差异,我会感到有些惊讶。


0

不需要检查编译或运行时的详细信息,基于我的测试使用 GNU C++17,在虚拟基类中访问数据成员不会对性能产生影响。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接