【问题标题】:JIT optimizer for C/C++C/C++ 的 JIT 优化器
【发布时间】:2011-11-14 15:50:38
【问题描述】:

我正在阅读 JIT 相对于预编译的优势,其中提到的其中一个是 JIT 可以根据实际运行时数据调整分支预测。现在我已经很久没有在大学写过编译器了,但在我看来,在大多数情况下(没有明确的 goto),预编译代码也可以实现类似的效果。

考虑以下代码:

   test x
   jne L2:
L1: ...
   jmp L3:
L2: ...
L3:

如果我们有一些运行时检测来查看“jne L2”为真的次数,它可以物理交换 L1: 块和 L2: 块中的所有指令。当然,它必须知道在交换期间任何一个块中都没有线程,但这些都是细节......

   test x
   jeq L1:
L2: ...
   jmp L3:
L1: ...
L3:

我了解将程序代码加载到只读内存等时也会出现问题,但这是一个想法。

所以我的问题是,这样的 JIT 优化对于 C/C++ 是否可行,还是我错过了一些无法做到的根本原因?有没有针对 C/C++ 的 JIT 优化器?

【问题讨论】:

  • 问题需要在句子的某处加上问号。您面临的具体问题是什么?
  • 编辑了原帖。没问题,只是想知道。
  • 我没有得到“没有明确的 goto”部分。你在谈论跳转指令。有什么区别?
  • 我的意思是源代码中没有明确的 goto 从代码中的其他地方直接跳转到“then”或“else”块,即从条件语句之外。
  • @Jay - 啊。在这种情况下,我希望 JIT 也可以修补这些引用,否则它会做一个相当半途而废的工作。 :P

标签: c++ c optimization branch jit


【解决方案1】:

据我所知,没有适用于 C++ 的 JIT 编译器;但是,GCC 确实支持反馈导向优化 (FDO),它可以使用运行时分析来优化分支预测等。

查看以“-fprofile”开头的GCC options(提示:“-fprofile-use”使用生成的运行时配置文件执行优化,而“-fprofile-generate”用于生成运行时配置文件)。

【讨论】:

  • 当然可以,但是基于配置文件的优化仅与配置文件数据具有代表性一样好。 JIT 的支持者声称(正确地)他们的优化是针对特定的运行,而不是一些可能与实际运行相关或不相关的分析数据。
  • @Jay:JIT也有同样的问题,只是粒度更小:它只能针对当前运行中已经处理过的数据进行优化,可能代表也可能不代表数据还没来。
【解决方案2】:

大多数现代 CPU 都支持分支预测。它们有一个小的缓存,允许 CPU 在理论上为您提供在运行时重新排序的好处。此缓存的大小相当有限,但可能意味着您不会获得想象中的好处。有些 CPU 甚至可以同时开始执行两个分支,并丢弃在未执行的分支上完成的工作。


编辑:使用 JIT 编译器的最大优势来自这样的代码。

if (debug) {
   // do something
}

JIT 非常擅长检测和优化不执行任何操作的代码。 (如果你有一个表明 Java 比 C 快得多的微基准,那么 JIT 很可能已经检测到你的测试没有做 C 编译器没有做的任何事情)

你可能会问,为什么 C 没有这样的东西?因为它有“更好”的东西

#if DEBUG
    // do something
#endif

这是最佳选择,前提是 DEBUG 很少更改,并且这些标志很少,因此您可以编译每个有用的组合。

这种方法的问题是可扩展性。您添加的每个标志都可以使要生成的预编译二进制文件的数量翻倍。

如果你有很多这样的标志并且编译每个组合是不切实际的,你需要依靠分支预测来动态优化你的代码。

【讨论】:

  • 所以这就像在未选择的道路中走两条路?这是作弊!
  • @muntoo,非常复杂。未采用的分支可能会触发中断,这很常见!例如double d = p == NULL ? NAN : *p 它是有限的,原因很明显,这就是为什么它在 JIT 中进行优化仍然是一个小优势。
  • 好的,我原以为并行管道会开始获取操作数等工作,但是,当任一管道到达执行阶段时,您就会知道采用了哪个分支,并且只有 'true ' 管道将继续执行。当然,“假”分支中的操作数之一可能是间接地址并触发错误。
【解决方案3】:

您指的是跟踪或重新优化 JIT,而不仅仅是任何旧的 JIT,这样的东西还没有为 C 或 C++ 制作(至少不是公开的)。但是,您可能想检查 LLVM 是否没有使用 Clang 或 GCC 前端的分支(考虑到它的编译器和 JIT),因为我似乎有一些主题表明它可能会被实现。

【讨论】:

  • 是的,它必须是编译器和运行时 JIT 之间的合作努力来标记哪些 then/else 块是自包含的(即没有外部引用)并且可以安全地重新定位在内存中.我理解 Michael 关于多流水线架构的评论,它消除了对这种优化的需求——模缓存限制——所以也许这就是没有人追求这一点的原因。
  • 对不起,我在上面的评论中指的是彼得。
  • @jay:分支预测只能解决这么多,这就是为什么要跟踪 JIT 的最初创建位置
  • 我不确定前端参与开发的必要性。我认为 LLVM 可以在运行时重新优化 IR(毕竟,它是在考虑 JIT 的情况下创建的)。
  • @Matthieu M. 我只提到涉及 C 和 C++ 语言的前端(因为 LLVM 不是特定于语言的)
【解决方案4】:

HP Dynamo 二进制重新编译器证明,由 C++ 编译器生成的优化代码可以实现高达 20% 的加速。 Dynamo 并不完全是一个 JIT 编译器,因为它以任意机器代码而不是一些更高级别的表示形式(如 JVM 字节码或 .NET CIL)开始,但原则上 C++ 的 JIT 只能比 Dynamo 更有效。见:http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.12.7138&rank=1

Dynamo 是为 HP PA-RISC 架构而创建的,从未作为商业产品提供,因此在当前以 x86 变体为主的世界中,它的用处不大。我想知道 VMware、Connectix 或 Parallels 是否曾经在他们的重新编译器中添加优化通道,或者他们是否已经摆脱了二进制转换以支持最新 x86 CPU 中的虚拟化功能。

【讨论】:

  • 20% 是巨大的!在我看来,C++ 社区已经对速度感到自满,而 C# 和 Java 却在无情地努力缩小差距。或许智能手机上原生应用的增长将提供必要的推动力,以提高性能。
  • @Jay:我不会说自满。 gcc 或 clang 的每个新版本都会显示速度改进。然而,确实这样的改进通常仅限于编译时......问题是任何时间的代码修改(在运行时)必然会增加一些复杂性,您需要测量、嵌入代码生成器等......而C 和 C++ 旨在生成小而高效的代码。
  • 像 Dynamo 这样的基于跟踪的动态优化器可以轻松地在库边界上进行优化,这赋予了它固有的优势。 GCC 最近引入了对链接时优化的支持,这有助于静态链接的二进制文件,但我认为对动态链接的库进行 LTO 甚至没有意义。
猜你喜欢
  • 1970-01-01
  • 2017-04-21
  • 2010-10-30
  • 2015-12-25
  • 1970-01-01
  • 2015-09-03
  • 1970-01-01
  • 2016-05-25
  • 2011-12-12
相关资源
最近更新 更多