【发布时间】:2015-05-18 23:58:42
【问题描述】:
是否有技术/库允许灵活地拥有类层次结构(具有 virtual 函数),一旦对象类型在运行时确定,就允许函数调用的去虚拟化?
举个简单的例子,假设我有一个程序从某个配置文件中读取形状类型(圆形、矩形、三角形等),以构建所述形状的某种数据结构(例如,vector<shape*>):
class shape {
public:
virtual void draw() const = 0;
// ...
};
class circle : public shape {
public:
void draw() const;
// ...
};
// ...
vector<shape*> shapes;
显然,如果我想画出所有的形状,我可以这样做:
for ( auto&& s : shapes )
s->draw();
每次对shapes 进行这样的迭代时,都会调用virtual 函数来为每个形状调用draw()。
但是假设一旦创建了shapes,它永远在程序的整个生命周期内都不会再次改变;并进一步假设draw() 将被调用很多 次。如果知道 实际 形状后,如果有一种方法可以“去虚拟化”对draw() 的调用在运行时,那就太好了。
我知道在编译时去虚拟化virtual 函数调用的优化技术,但我没有问这个问题。
如果有一个巧妙的技巧可以直接在 C++ 中执行此操作,我会非常感到惊讶,因为这样做的一种方法是在运行时修改内存中的机器代码。但是是否有一些 C++ 库可以实现这样的事情?
我想像这样的事情可能可以通过 LLVM 实现,因为它允许在运行时生成机器代码。有人为此使用过 LLVM 吗?也许是在 LLVM 之上分层的框架?
注意:解决方案必须是跨平台,即至少使用 gcc/clang 和 VC++。
【问题讨论】:
-
如果列表中有不同的形状,这种去虚拟化将如何工作?它会(从概念上)将 for 循环内的 s->draw() 调用替换为 (if type == Circle) s->circle::draw(); else if (type == Square) s->square::draw();指令类型?如果是这样,我不确定这一定会更有效。
-
如果没有进一步的 shape 子类,您可以自己“去虚拟化”这个,通过 typedef-ing 一个到另一个。但是,只要有两个或多个子类,除非编译器能够以某种方式向自己证明特定的代码块只会使用特定的子类,否则它必须使用虚函数调用。这不太可能,但可能。在“auto x=new circle;”之后编译器知道,只要手头有“x”,“x->draw()”就可以安全地去虚拟化,而且许多编译器可能已经这样做了。不幸的是,此类优化的范围往往很小。
-
您是否执行了表明 vtable 跳转具有可衡量的性能影响的分析? vtable 调用仍然非常便宜。根据 Donald Knuth 的说法,过早的优化是万恶之源。
-
我从来没有听说过一种自动化的方式来做到这一点,但您总是可以通过根据对象类型将对象放入不同的列表来手动完成。然后,您可以遍历每个类型的列表,为每个列表调用适当的非虚拟方法。
-
虚拟通话本身基本上是免费的。几次调用后,分支预测器将其拾取,并推测性地执行正确的功能,而无需等待 vtable 查找 - IOW,您的去虚拟化器已经存在,在硬件中实现。真正的代价是错过了内联的潜力,但这需要在运行时对函数进行完全重新编译和优化,所以它绝对是 JIT 材料(不仅仅是修补函数体中的一些位)。
标签: c++ optimization llvm virtual-functions llvm-c++-api