编程语言思想:避免虚参表查找
Programming Language Idea: Avoiding vtable lookups
我有一种编程语言的想法已经有一段时间了:它本质上是c++和类似java的语法,用于系统编程(或任何需要高性能的编程),但在我看来,它的语法比c++更令人愉快。我正在考虑如何处理分层类结构中的虚拟方法(我的语言不包括多重继承),以及避免虚函数表查找的方法。我的问题是双重的:
- 在我看来,虚表查找之所以会影响性能(至少在像游戏开发这样的时间关键场景中),是因为它需要延迟对象的虚表指针,而这个虚表通常是缓存缺失。这是对的吗,还是我漏掉了问题的一部分?
- 我对部分解决方案的想法是这样的:如果编译器可以完全确定对象的类型(即。它不能是从它认为的类型派生的类型),并且该对象作为参数传递给函数,该参数的类型是对象类型的超类,然后函数中调用的虚方法的位置可以作为一种"隐藏"参数传递,该参数在编译时添加。也许一个例子会有所帮助:
考虑以下类层次结构的伪代码:
class Animal {
public void talk() { /* Generic animal noise... */ }
// ...
}
class Dog extends Animal {
public void talk() { /* Override of Animal::talk(). */ }
// ...
}
void main() {
Dog d = new Dog();
doSomethingWithAnimal(d);
}
void doSomethingWithAnimal(Animal a) {
// ...
a.talk();
// ....
}
请记住这是伪代码,而不是c++或Java或类似的代码。同样,假设Animal参数是通过引用而不是值隐式传递的。因为编译器可以看到d
肯定是Dog
类型的,所以它可以将doSomethingWithAnimal
的定义翻译成这样:
void doSomethingWithAnimal(Animal a, methodptr talk = NULL) {
// ...
if ( talk != NULL ) {
talk(a);
} else {
a.talk();
}
// ...
}
那么main
就会被编译器翻译成这样:
void main() {
Dog d = new Dog();
doSomethingWithAnimal(d, Dog::talk);
}
显然,这不会完全消除对虚值表的需求,并且可能仍然需要为无法确定对象的确切类型的情况提供虚值表,但是您认为这是一种性能优化吗?我计划尽可能使用寄存器来传递参数,即使参数必须溢出到堆栈中,堆栈上的methodptr参数更有可能是缓存命中,而不是虚参表的值,对吗?
Re Q1:缓存利用率实际上只是虚拟调用"问题"的一部分。virtual
函数和后期绑定的全部意义在于,调用站点可以调用任何实现而无需更改。这需要一些间接:
- 间接意味着解决间接的空间和/或时间开销。
- 无论您如何进行间接调用,如果CPU具有良好的分支预测器和,则间接调用只能与静态调用一样快,调用站点是单态的(即,只调用一个实现)。我甚至不确定在所有硬件开发人员关心的情况下,一个完美预测的分支是否和静态分支一样快。
- 在编译时不知道被调用的函数也会抑制基于知道被调用函数的优化(内联,但也有循环不变代码运动,可能更多)。
你的方法并没有改变这一点,因此留下了大部分的性能问题:它仍然浪费了一些时间和空间(只是在额外的参数和分支上,而不是在虚值表查找上),它不允许内联或其他优化,并且它没有删除间接调用。
Re 2:这是一种过程间的反虚拟化,c++编译器已经在某种程度上做到了(在本地,有@us2012在评论中描述的限制)。它有一些"小"问题,但如果有选择地使用,那么可能是值得的。否则,您将生成更多的代码,传递许多额外的参数,执行许多额外的分支,并且只获得很少甚至是净损失。
我认为主要问题是它不能解决上面描述的大多数性能问题。为子类和同一主题的其他变体生成专门化函数(而不是一个泛型体),可以对此有所帮助。但是这会产生额外的代码,这些代码必须用性能提升来证明自己,并且普遍的共识是,即使在性能关键的程序中,对于大多数代码来说,这种激进的优化也是不值得的。
特别地,虚拟调用开销只会影响相同功能的基准测试,或者如果你已经优化了所有其他东西,并且需要大量微小的间接调用(游戏开发中的一个例子:用于绘制或截体剔除的每个几何对象的多个虚拟方法调用)。在大多数代码中,虚拟调用并不重要,或者至少不足以保证进一步的优化尝试。此外,这只与AOT编译器相关,因为JIT编译器有其他处理这些问题的方法。查找多态内联缓存,并注意跟踪JIT编译器可以简单地内联所有调用,无论是否为虚拟的。
总而言之:虚表已经是实现虚函数的一种快速和通用的方式(如果它们可以被使用,这里就是这种情况)。你不太可能在这些问题上取得很大的进步,更不用说注意到进步了,除非是在一些罕见的情况下。如果你想尝试一下,你可以试着写一个LLVM通道,做一些像这样的事情(尽管你必须在一个较低的抽象层次上工作)。- 了解算法的性能差异(如果以不同的编程语言实现)
- 为什么编程语言被编译为汇编程序而不是二进制?
- 如何在同时包含C++和Python的项目(多编程语言项目)中使用doxygen
- 什么是编程语言支持定义您自己的自定义运算符?
- 如何通过不同的编程语言发送,接收和解析XML消息
- 今天的主流编程语言主要使用动态还是静态(词汇)作用域?
- 谁以编程语言(例如C )制定标准
- 如何使用任何编程语言组合序列中的多个图像
- 我可以使用功能指针在编程语言边界上调用函数
- 有没有办法将cin.fail和cin.clear翻译成C编程语言
- 编程语言中的 char-int 等价性
- C 编程语言帮助我
- 从其他编程语言调用 c++ dll 类函数
- 值和对象不同的编程语言
- 返回 2 语句的含义 c++ 编程语言
- 互联网连接速度与HTTP请求的编程语言速度
- 在什么编程语言游戏引擎上编写"Frostbit 3"?
- 在 "Code Blocks" IDE 中混合编程语言?
- 一些应用程序是如何用几种编程语言制作的
- 编程语言思想:避免虚参表查找