编程语言理念:避免vtable查找 我一直在思考一个编程语言的概念:它本质上是C++和java,就像语法一样,是指系统编程(或者是任何需要高性能的编程),但是,我认为,C++的语法更有趣。我在考虑如何处理分层类结构中的虚拟方法(我的语言不包括多重继承),以及避免vtable查找的方法。我的问题有两个: 据我所知,vtable查找之所以会对性能造成如此大的影响(至少在游戏开发这样的时间关键场景中),是因为它需要延迟对象vtable指针,而这个vtable通常是缓存未命中。这是正确的,还是我遗漏了问题的一部分 我对部分解决方案的想法是:如果编译器可以完全确定对象的类型(即,它不能是从它认为的类型派生的类型),并且该对象作为参数传递给函数,该参数的类型是对象类型的超类,然后,函数中调用的虚拟方法的位置可以作为一种“隐藏”参数传递,该参数在编译时添加。也许举个例子会有所帮助:

编程语言理念:避免vtable查找 我一直在思考一个编程语言的概念:它本质上是C++和java,就像语法一样,是指系统编程(或者是任何需要高性能的编程),但是,我认为,C++的语法更有趣。我在考虑如何处理分层类结构中的虚拟方法(我的语言不包括多重继承),以及避免vtable查找的方法。我的问题有两个: 据我所知,vtable查找之所以会对性能造成如此大的影响(至少在游戏开发这样的时间关键场景中),是因为它需要延迟对象vtable指针,而这个vtable通常是缓存未命中。这是正确的,还是我遗漏了问题的一部分 我对部分解决方案的想法是:如果编译器可以完全确定对象的类型(即,它不能是从它认为的类型派生的类型),并且该对象作为参数传递给函数,该参数的类型是对象类型的超类,然后,函数中调用的虚拟方法的位置可以作为一种“隐藏”参数传递,该参数在编译时添加。也许举个例子会有所帮助:,c++,compiler-construction,programming-languages,compiler-optimization,vtable,C++,Compiler Construction,Programming Languages,Compiler Optimization,Vtable,考虑类层次结构的以下伪代码: 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(); doSome

考虑类层次结构的以下伪代码:

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();
    }
    // ...
}
void main() {
    Dog d = new Dog();
    doSomethingWithAnimal(d, Dog::talk);
}
然后,
main
看起来会被编译器翻译成这样:

void doSomethingWithAnimal(Animal a, methodptr talk = NULL) {
    // ...
    if ( talk != NULL ) {
        talk(a);
    } else {
        a.talk();
    }
    // ...
}
void main() {
    Dog d = new Dog();
    doSomethingWithAnimal(d, Dog::talk);
}

显然,这并不能完全消除对vtable的需求,并且在无法确定对象的确切类型的情况下,可能仍然需要提供vtable,但是作为性能优化,您对此有何想法?我计划尽可能使用寄存器来传递参数,即使参数必须溢出到堆栈上,堆栈上的methodptr参数比vtable值更有可能被缓存命中,对吗?非常感谢您的任何想法。

Re Q1:缓存利用率实际上只是虚拟呼叫“问题”的一部分。
virtual
函数和后期绑定的全部要点是,调用站点可以调用任何实现而不做任何更改。这需要一些间接的方法:

  • 间接寻址意味着解决间接寻址的空间和/或时间开销
  • 无论您如何进行间接调用,如果CPU具有良好的分支预测器并且调用站点是单态的(即,只调用过一个实现),则间接调用只能与静态调用一样快。我甚至不确定,在所有硬件开发人员关心的问题上,完美预测的分支是否与静态分支一样快
  • 在编译时不知道被调用函数也会抑制基于知道被调用函数的优化(内联,但也包括循环不变代码运动等)
您的方法并没有改变这一点,因此大部分性能问题都没有改变:它仍然会浪费一些时间和空间(仅在附加参数和分支上,而不是vtable查找),不允许内联或其他优化,也不删除间接调用

<强> Re:2:这是一种进程间的旋转去虚拟化,C++编译器已经在一定程度上做了(局部的,如评论中的@ Us2012所描述的限制)。它有一些“小”问题,但如果有选择地应用,它可能是值得的。否则,您将生成更多的代码,传递更多的附加参数,执行更多的分支,并且只获得很少甚至净损失

我认为主要的问题是它不能解决上面描述的大多数性能问题。为子类和同一主题的其他变体生成专门的函数(而不是一个泛型体)可能会有所帮助。但这会产生额外的代码,这些代码必须以性能增益来证明自己的合理性,而且普遍的共识是,即使在性能关键的程序中,对于大多数代码来说,这种激进的优化也不值得

特别是,虚拟调用开销只在相同功能的基准测试中起作用,或者如果您已经优化了所有其他功能,并且需要大量的微小间接调用(游戏开发中的一个示例:每个几何对象几个虚拟方法调用用于绘制或截锥剔除)。在大多数代码中,虚拟调用并不重要,或者至少不足以保证进一步的优化尝试。此外,这仅与AOT编译器相关,因为JIT编译器有其他方法来处理这些问题。查找多态内联缓存,注意跟踪JIT编译器可以轻松地内联所有调用,无论是否为虚拟调用


总之:vtables已经是实现虚拟功能的一种快速而通用的方法(如果可以使用,这里就是这样)。你不太可能在他们身上有多大的改进,更不用说注意到这些改进了,除非是在一些罕见的情况下。如果你想尝试一下,你可以尝试编写一个这样的LLVM PASS(尽管你必须要在较低的抽象层次上工作)。

这项技术已经在大多数生产C++编译器中实现,并以“去虚拟化”的名称进行。。您可能希望重新审视虚拟调度缓慢的前提,直到您对其进行测量。还要注意,
如果
(分支)对性能有不同的影响,这可能比虚拟分派的影响更大。当编译器在编译时知道对象类型时,它不会使用动态分派