【发布时间】:2013-01-15 19:19:25
【问题描述】:
我一直在玩弄一种编程语言的想法:它的语法本质上类似于 C++ 和 Java,用于系统编程(或实际上任何需要高性能的编程),但是,在我看来,这是一种比 C++ 更有趣的语法。我正在考虑如何处理分层类结构中的虚拟方法(我的语言不包括多重继承),以及避免 vtable 查找的方法。我的问题是双重的:
- 据我了解,vtable 查找如此影响性能的原因(至少在游戏开发等时间紧迫的场景中)是因为它需要引用对象 vtable 指针,并且此 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();
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);
}
显然,这并不能完全消除对 vtable 的需求,并且可能仍需要为无法确定对象确切类型的情况提供 vtable,但您对此作为性能优化有何看法?我计划尽可能使用寄存器来传递参数,即使参数必须溢出到堆栈上,堆栈上的 methodptr 参数更有可能是缓存命中而不是 vtable 值,对吧?非常感谢任何和所有想法。
【问题讨论】:
-
这项技术已经在大多数生产 C++ 编译器中实现,并被称为“去虚拟化”。
-
您可能想重新审视虚拟调度在您测量之前很慢的前提。还要注意
if(分支)对性能的影响不同,这可能比虚拟调度的影响更大。当编译器在编译时知道对象类型时,它无论如何都不会使用动态分派,但在您的提议中,函数必须为每个它需要的可能功能。您最终可能会产生比您试图消除的更大的成本...... -
@KyleLutz - 它可以处理 Zach 描述的情况吗?我以为在将
doSomethingWithAnimal作为编译单元编译时,编译器无法推断出a的静态类型,因此对手头的问题无能为力。 -
@delnan:问题是我上一条评论中的问题集是否可以在不完全按照编译器现在所做的情况下回答。在问题的一个过于简单的示例中,有一个指向单个成员函数的最终覆盖器的指针,但在实际代码中会有多个这样的成员。将它们作为参数传递会比虚拟调度更昂贵,知道要传递哪些参数在单独的编译模型中会很困难,并且会将函数的实现推送到接口中(至少到编译器看到的接口中)
-
我非常同意@DavidRodríguez-dribeas:除非您有非常确凿的确凿证据证明虚拟调度是一个关键的性能瓶颈,一直到设计和实施一种新语言是我见过的最极端的反应之一。
标签: c++ compiler-construction programming-languages compiler-optimization vtable