.NET JIT如何优化生成的代码布局?

时间:2016-06-24 20:52:41

标签: c# .net optimization code-generation jit

早在2009年,我就this answer发布了有关嵌套try / catch / finally块优化的问题。

几年后再次考虑这个问题,似乎问题可以扩展到其他控制流程,不仅是try / catch / finally,还有{{1} } / if

在每个交叉点,执行将遵循一条路径。显然,必须为两者生成代码,但它们在内存中的放置顺序以及浏览它们所需的跳转次数会有所不同。

订单生成代码在内存中布局会影响CPU指令缓存的未命中率。让指令管道停止,等待内存读取,可以真正杀死循环性能。

我不认为循环(else / for / foreach)是一个非常合适的选择,除非你期望循环的迭代次数多于它有的次数,因为自然生成顺序看起来非常理想。

有些问题:

  • 可用的.NET JIT以什么方式优化生成的指令顺序?
  • 这在实践中对公共代码有多大差异?那些非常合适的案例呢?
  • 开发人员可以做些什么来影响这种布局?如果使用禁止while
  • 进行修改呢?
  • 使用的特定JIT是否会对布局产生很大影响?
  • 内联启发式方法也可以在这里发挥作用吗?
  • 基本上与JIT的这方面有关的任何有趣内容!

一些初步想法:

移动goto块不合适是一件容易的事,因为根据定义它们应该是例外情况。不确定会发生这种情况。

对于某些循环,我怀疑你可以非常轻松地提高性能。但总的来说,我认为它不会产生那么大的差异。

我不知道JIT如何决定生成代码的顺序。在C on Linux上,您可以使用likely(cond) and unlikely(cond)来告诉编译器哪个分支是优化的常用路径。我不确定所有编译器都尊重这些宏。

指令排序不同于分支预测的问题,其中CPU猜测(自己,afaik)将采取哪个分支以启动流水线(过度执行的步骤:解码,获取操作数,执行,写回)在执行步骤确定条件变量的值之前,在指令上。

我想不出用C#语言影响这个顺序的任何方法。也许你可以通过catch显式地标记它来稍微操纵它,但这是否可移植,还有其它问题吗?

也许这就是配置文件引导优化的用途。我们在.NET生态系统中,现在还是计划中都有这个?也许我会去阅读LLILC

1 个答案:

答案 0 :(得分:1)

您所指的优化称为代码布局优化,其定义如下:

  • 在同一个线程中及时执行的那些代码段应该在虚拟地址空间中关闭,以便它们适合单个或几个连续的高速缓存行。这减少了缓存未命中。
  • 在不同线程中及时执行的那些代码段应该在虚拟地址空间中接近,这样只要没有自修改代码,它们就适合单个或几个连续的高速缓存行。这比前一个优先级低。这减少了缓存未命中。
  • 频繁执行的那些代码(热代码)应该在虚拟地址空间中关闭,以便它们适合尽可能少的虚拟页面。这样可以减少页面错误和工作集大小。
  • 很少执行的代码片段(冷代码)应该在虚拟地址空间中关闭,以便它们适合尽可能少的虚拟页面。这样可以减少页面错误和工作集大小。

现在回答你的问题。

  

可用.NET JIT以何种方式优化生成   指令顺序?

"指令顺序"这是一个非常笼统的术语。许多优化会影响指令顺序。我假设您指的是代码布局。

JITters的设计应该花费最少的时间来编译代码,同时生成高质量的代码。为实现这一目标,他们只执行最重要的优化,以便真正值得花时间去做。代码布局优化不是其中之一,因为没有分析,它可能没有益处。虽然JITter当然可以执行分析和动态优化,但通常有一种首选方式。

  

这在实践中对普通代码有多大差异?什么   关于完美适合的案例?

代码布局优化本身通常可以将整体性能提高-1%(负一)到4%,这足以使编译器编写者满意。我想补充一点,它通过减少缓存未命中来间接降低能耗。指令缓存的未命中率降低通常可高达35%。

  

开发人员可以做些什么来影响这种布局?什么   关于用禁止的goto进行修改?

是的,有很多方法。我想提一下通常推荐的mpgo.exe。请不要为此目的使用goto。这是被禁止的。

  

使用的特定JIT是否会对布局产生很大影响?

没有

  

内联启发式方法是否也在这里发挥作用?

内联确实可以改进函数调用的代码布局。它是最重要的优化之一,所有.NET JIT都可以执行它。

  

移动捕获区域是一项很容易的工作,因为他们应该这样做   根据定义是例外情况。不确定会发生这种情况。

是的可能轻松",但潜在的收益是什么? catch块通常很小(包含对处理异常的函数的调用)。处理这种特殊的代码布局情况似乎并不乐观。如果您真的在意,请使用mpgo.exe

  

我不知道JIT如何决定生成代码的顺序。在C上   Linux你可能(cond)和不太可能(cond)你可以使用   告诉编译器哪个分支是优化的公共路径。

使用PGO比使用likely(cond)unlikely(cond)要好得多,原因有两个:

  1. 程序员可能会在代码中放置可能(cond)和不太可能(cond)时无意中犯错误。它实际上发生了很多。在尝试手动优化代码时犯大错是非常典型的。
  2. 在代码中添加可能(cond)和不太可能(cond)使得将来可维护性降低。每次更改源代码时,您都必须确保这些提示保持不变。在大型代码库中,这可能是(或者更确切地说)是一场噩梦。
  3.   

    指令排序不同于分支问题   预测...

    假设您正在谈论代码布局,是的,它们是截然不同的。但代码布局优化通常由真正包含分支统计信息的配置文件引导。硬件分支预测当然完全不同。

      

    也许我会去阅读有关LLILC的内容。

    虽然使用mpgo.exe是执行此优化的主流方式,但您也可以使用LLILC,因为LLVM也支持配置文件引导优化。但我认为你不需要走这么远。