是否有人使用 JIT 技巧来提高静态编译语言(如 C++)的运行时性能?似乎基于运行时观察的热点分析和分支预测可以提高任何代码的性能,但也许有一些基本的战略原因使得在运行时进行这种观察和实施更改只能在虚拟机中进行。我清楚地记得在听动态语言爱好者谈论收集统计数据和重新排列代码时无意中听到 C++ 编译器作者喃喃地说“你也可以为用 C++ 编写的程序做这件事”,但我的网络搜索支持这种记忆的证据已经枯竭。
7 回答
配置文件引导优化不同于运行时优化。优化仍然是离线完成的,基于分析信息,但是一旦二进制文件交付,就没有持续的优化,所以如果配置文件引导优化阶段的使用模式不能准确反映实际使用情况,那么结果将是不完善,程序也不会适应不同的使用模式。
您可能会对查找有关HP Dynamo的信息感兴趣,尽管该系统专注于本机二进制 -> 本机二进制翻译,尽管由于 C++ 几乎完全编译为本机代码,我想这正是您正在寻找的。
您可能还想看看LLVM,它是一个编译器框架和支持 JIT 编译和运行时优化的中间表示,尽管我不确定是否实际上有任何基于 LLVM 的运行时可以编译 C++ 并执行 + 运行时优化它。
在过去的几年里,我做了很多这样的优化。这是我实现的图形渲染 API。由于 API 定义了数千种不同的绘图模式作为通用功能,因此速度很慢。
我最终为特定领域的语言编写了自己的小型 Jit 编译器(非常接近 asm,但包含一些高级控制结构和局部变量)。
我得到的性能提升在 10 到 60 倍之间(取决于编译代码的复杂性),所以额外的工作得到了很大的回报。
在 PC 上,我不会开始编写自己的 jit 编译器,而是使用 LIBJIT 或 LLVM 进行 jit 编译。在我的情况下这是不可能的,因为我正在开发不受 LIBJIT/LLVM 支持的非主流嵌入式处理器,所以我必须自己发明。
答案更有可能:没有人比 PGO 为 C++ 做的更多,因为好处可能不明显。
让我详细说明一下:从开发人员的角度来看, JIT引擎/运行时既有优点也有缺点:它们在运行时有更多信息,但分析时间却很少。一些优化真的很昂贵,而且你不可能看到对开始时间没有巨大影响的那些优化是:循环展开、自动矢量化(在大多数情况下也是基于循环展开)、指令选择(使用 SSE4.1 进行使用 SSE4.1 的 CPU)结合指令调度和重新排序(使用更好的超标量 CPU)。这种优化与类似 C 的代码(可从 C++ 访问)很好地结合在一起。
进行高级编译的单一成熟编译器架构(据我所知)是 Java Hotspot编译和使用分层编译具有类似原理的架构(Java Azul的系统,当时流行的JaegerMonkey JS 引擎)。
但运行时最大的优化之一如下:
多态内联缓存(这意味着如果您使用某些类型运行第一个循环,第二次循环的代码将是来自前一个循环的特殊类型,JIT将放置一个保护并将内联的分支作为默认分支类型,并基于它,从这种使用SSA形式引擎的特殊形式中,将应用常量折叠/传播、内联、死代码消除优化,并取决于JIT的“高级”程度,将进行改进或CPU 寄存器分配改进较少。)您可能会注意到,JIT(热点)将主要改进分支代码,并且运行时信息将比 C++ 代码更好,但是静态编译器,在它旁边有时间进行分析,指令重新排序,对于简单循环,可能会变得更好一点表现。而且,通常情况下,C++ 代码,需要快速的区域往往不是 OOP,因此JIT优化的信息不会带来如此惊人的改进。
JIT的另一个优点是JIT可以跨程序集工作,因此如果要进行内联,它会获得更多信息。
让我详细说明一下:假设您有一个基类 A,而您在另一个包/程序集/gem/etc 中只有一个实现,即 B。并且是动态加载的。
JIT看到 B 是 A的唯一实现,它可以在其内部表示中的任何地方用 B 代码替换 A 调用,并且方法调用不会进行调度(查看 vtable),而是直接调用。这些直接调用也可能是内联的。例如这个 B 有一个方法:getLength()
它返回 2,所有对的调用都getLength()
可以减少到常量 2。最后,C++ 代码将无法跳过另一个 dll 对 B 的虚拟调用。
C++ 的一些实现不支持优化更多的 .cpp 文件(即使在今天,在最新版本的GCC中也有 -lto 标志使这成为可能)。但是如果你是 C++ 开发者,关心速度,你可能会将所有敏感类放在同一个静态库甚至同一个文件中,这样编译器就可以很好地内联它,让 JIT 设计的额外信息,由开发者自己提供,所以没有性能损失。
Visual Studio 有一个用于执行运行时分析的选项,然后可用于优化代码。
“配置文件引导优化”
我相信LLVM试图做到这一点。它试图在程序的整个生命周期(编译时、链接时和运行时)中进行优化。
合理的问题-但前提令人怀疑。
正如 Nils 的回答一样,有时“优化”意味着“低级优化”,这本身就是一个很好的主题。
但是,它基于“热点”的概念,与通常给出的相关性相去甚远。
定义:热点是一小块代码区域,进程的程序计数器在其中花费了大部分时间。
如果存在热点,例如占用大量时间的紧密内部循环,则值得尝试在低级别进行优化,如果它在您控制的代码中(即不在第三方库中)。
现在假设内部循环包含对函数的调用,任何函数。现在程序计数器不太可能在那里找到,因为它更有可能在函数中。因此,虽然代码可能很浪费,但它不再是热点。
使软件变慢的常见方法有很多,热点就是其中之一。但是,根据我的经验,这是大多数程序员都知道的唯一一种,也是唯一适用低级优化的一种。