【发布时间】:2016-10-27 13:53:29
【问题描述】:
早在 2009 年,我在 this answer 上发布了一个关于嵌套 try/catch/finally 块优化的问题。
几年后再次考虑这个问题,似乎这个问题可以扩展到其他控制流,不仅是try/catch/finally,还有if/else。
在这些交叉点中的每一个,执行都将遵循一条路径。显然,两者都必须生成代码,但是它们在内存中的放置顺序以及导航它们所需的跳转次数会有所不同。
生成的代码在内存中的布局顺序会影响 CPU 指令缓存的未命中率。让指令流水线停止,等待内存读取,真的会扼杀循环性能。
我不认为循环 (for/foreach/while) 非常适合,除非您期望循环的零迭代次数比它的迭代次数多,因为自然生成顺序看起来很漂亮最佳。
一些问题:
- 可用的 .NET JIT 以哪些方式优化生成的指令顺序?
- 这在实践中对通用代码有多大影响?非常适合的案例呢?
- 开发人员可以做些什么来影响此布局?与被禁止的
goto搞混呢? - 使用的特定 JIT 是否会对布局产生很大影响?
- 内联启发式方法也在这里发挥作用吗?
- 基本上任何与 JIT 的这方面相关的有趣内容!
一些初步的想法:
将catch 块移出线是一件容易的事,因为根据定义,它们应该是例外情况。不确定会发生这种情况。
对于某些循环,我怀疑您可以不平凡地提高性能。但总的来说,我认为这不会有太大的不同。
我不知道 JIT 如何决定生成代码的顺序。在 Linux 上的 C 中,您有 likely(cond) and unlikely(cond) 可以用来告诉编译器哪个分支是要优化的公共路径。我不确定所有编译器都尊重这些宏。
指令排序与分支预测的问题不同,其中 CPU 猜测(自行,afaik)将采用哪个分支以启动管道(过于简单的步骤:解码、获取操作数、执行、写回) 在指令上,在执行步骤确定条件变量的值之前。
我想不出任何方法来影响 C# 语言中的这个顺序。也许你可以通过goto显式地对标签进行一些操作,但这是否可移植,还有其他问题吗?
也许这就是配置文件引导优化的目的。我们现在或计划中是否在 .NET 生态系统中拥有它?也许我会去阅读一下LLILC。
【问题讨论】:
-
好在 JIT 可以使用运行时指标进行优化。循环几次后,它可以找出更好的路径。
-
@Caramiriel 是的,JVM 这样做效果很好。不幸的是,.NET JIT 很差。没有运行时分析。
-
这不是抖动的工作。移动代码需要使用 mpgo.exe(托管配置文件引导优化)工具和 Ngen.exe 来使用配置文件数据。精确到 +/- 2%,实际上没有人使用它,从 SO 中没有人问过有关它的问题可以看出这一点。我知道它使 I-cache 保持热状态,这很容易做到。它是否改变分支是一个盲目的猜测,微软并没有吹嘘它,也没有在 channel9 视频中详细介绍。回报并不存在,PGO 只会为您带来众所周知的 15% 的改进。
-
快速跟进。我修改了我的序列化/反序列化库以发出异常(不太可能)的代码在正常的直线代码之后。基准测试显示反序列化提高了 14.3%(在此期间进行了大量验证)。不需要任何 PGO 来提升,只是预感。
标签: c# .net optimization code-generation jit