运行时C++去虚拟化?

C++ devirtualization at runtime?

本文关键字:虚拟化 C++ 运行时      更新时间:2023-10-16

是否有技术/库允许灵活地拥有类层次结构(具有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/clangVC++。

我相当确定没有魔法这样的东西,"在这里编译器,我没有更多的子类我要定义,这个列表不会改变,所以消除虚函数调用开销">之类的事情。

在性能极其关键的情况下,您可以做的一件事可以帮助虚拟调用,那就是按形状的子类型对形状列表进行排序。例如,您希望重新排列这些类型以形成:圆形、圆形、圆形、圆形、圆形、圆形、...、正方形、正方形、正方形等子类型的零星模式,而不是圆形、矩形、三角形、矩形、三角形、正方形、正方形等子类型的零星模式。这有效地优化了分支预测。我不知道这种方法是否仍然适用,或者是否在最新的架构和优化器中产生了很大的影响,但至少在不久前,当我活着的时候,它非常有用。

关于JIT,我一直在探索这个领域。我不一定建议尝试找到一个 JIT 解决方案来神奇地使您的C++代码更快。

相反,我一直在探索它,因为我的软件已经有一种特定于领域的语言,一种可视化的节点GUI编程语言,你可以在节点(函数)之间绘制连接,而不是编写代码来制作着色器和图像滤镜等新东西(类似于虚幻引擎4的蓝图)。它目前远不及手写本机代码,这就是为什么我对探索代码生成/JIT 路由感兴趣的原因。它目前更像是口译员。

我已经尝试过 Tiny C 和 LCC,但我发现它们相当令人失望的一件事是它们的优化器并不像您的商业生产编译器那么复杂。我经常得到的结果平均比MSVC或GCC慢3到4倍。它们在其他方面很棒,因为它们非常轻盈且易于嵌入。

LLVM似乎是一个很棒的匹配,除了它是巨大的。我们在核心级领域有这种老派的美学,其中旨在最大限度地重用的代码应该尽可能少地重用(以避免对外部包的零星依赖)。我很难将其简化为足以通过这些标准的轻量级,但我仍在研究它。