我在JIT编译器中动态生成一些操作码,我正在寻找操作码对齐的准则。
1)我读过一些评论,通过在电话
之后添加nops来简单地“推荐”对齐2)我还读到了使用nop来优化并行化序列。
3)我已经读过ops的对齐有利于“缓存”性能
通常这些评论不提供任何支持性参考。阅读博客或评论的一件事是“做一个这样的好主意”,但实际编写一个实现特定操作序列并实现大多数在线材料的编译器,尤其是博客,是另一回事用于实际应用。因此,我相信自己能够找到自己的东西(反汇编等等,看看现实世界的应用程序是做什么的)。这是我需要一些外部信息的一种情况。
我注意到编译器通常会在之前的任何指令序列之后立即启动奇数字节指令。因此编译器在大多数情况下没有特别小心。我在这里或那里看到“nop”,但通常看起来很少使用nop,如果有的话。操作码对齐有多重要?您能为我实际可以用于实施的案例提供参考吗?感谢。
答案 0 :(得分:4)
所有这些微观优化的最佳来源是Agner Fog's x86 optimization manuals。那些文件应该包含你需要的一切,然后是一些。 :)
我能想到的一件事是对齐循环以使循环代码不跨越任何高速缓存行边界,即循环是< 64字节,从一个可被64整除的地址开始。整个循环将适合单个缓存行,并为其他事物留下更多缓存行。我怀疑这在现实世界的程序中是否重要,无论这个特定的循环是多么“热”。
答案 1 :(得分:4)
除了分支目标的对齐外,我建议不要插入nops。在某些特定的CPU上,分支预测算法可能会惩罚控制传输以控制传输,因此nop可能能够充当标志并反转预测,但否则它不太可能有所帮助。
现代CPU将把你的ISA操作转换为 micro-ops 。这可能会使经典对齐技术变得不那么重要,因为可能微操作代码转换器会省略nops并改变秘密真机操作的大小和对齐。
然而,出于同样的原因,基于第一原则的优化应该很少或没有伤害。
理论是通过在缓存行边界处启动循环来更好地利用缓存。如果一个循环是在缓存行的中间开始的,那么缓存行的前半部分将不可避免地加载并在循环期间保持加载,如果循环长于1 /则这将浪费在缓存中的空间2缓存行。
此外,对于分支目标,当目标对齐时,缓存行的初始加载会加载指令流的最大前向窗口。
关于使用nops分离不是分支目标的内联指令,在现代CPU上执行此操作的原因很少。 (曾经有一段时间RISC机器有 delay slots ,这通常导致在控制传输后插入nops。)解码指令流很容易管道化,如果是架构具有奇数字节长度的运算,可以确保它们被合理地解码。