按位旋转是不是比当前 Intel CPU 上的移位慢?
Posted
技术标签:
【中文标题】按位旋转是不是比当前 Intel CPU 上的移位慢?【英文标题】:Are bitwise rotations slower than shifts on current Intel CPU?按位旋转是否比当前 Intel CPU 上的移位慢? 【发布时间】:2012-10-07 16:31:27 【问题描述】:我很好奇java.lang.Integer.rotateLeft
是否通过使用旋转指令得到优化并为其编写基准测试。结果尚无定论:它比两班倒快得多,但比单班倒慢一点。所以我用 C++ 重写了它并得到了大致相同的结果。通过g++ -S -Wall -O3
编译时,我可以看到generated assembler 中的指令。我的 CPU 是 Intel Core i5。
benchmark 很长,肯定不是最好的代码,但我认为它没有损坏。或者是吗?根据文档,轮换需要一个周期,就像轮班一样。谁能解释一下结果?
rotations: 6860
shift: 5100
前两个答案是错误的。 gcc 和 java 的 JIT 都知道旋转指令并使用它们。关于 gcc 参见上面的链接,关于 java 参见我的java benchmark 及其结果
benchmark ns linear runtime
Rotate 3.48 ====================
NonRotate 5.05 ==============================
Shift 2.16 ============
【问题讨论】:
@maartinus:我已经删除了我的答案,感谢您的更正 好的,首先,您的问题与描述不符。您询问 CPU 的移位和旋转指令,然后您尝试找出某些特定编译器如何足够聪明地使用它或不使用它。所有rol
和sar
和shl
都在相同数量的CPU 时钟中执行很长时间。我删除了我的答案,因为问题没有明确定义。
@Serge:我会看看我是否可以改进措辞。我描述了我是如何发现编译器使用rol
和ror
进行优化的,然后我问是否有人可以解释结果,因为(与您的评论相矛盾)轮换比轮换花费更长的时间 .
仅供参考。自 8086/8088 以来,在 Intel CPU 上,轮换所用的时间并不长。
如果我们谈论的是 CPU 花费在单个旋转或移位指令上的时间,那么您的基准测试是不正确的。关于你的java基准。它是 JIT 实现行为。没有其他的。 JIT 是否将我用来引用的代码折叠成单个 rol
指令?怀疑。
【参考方案1】:
我不知道 gcc 和 java jit 能够识别一系列 SHIFT 和 OR 运算符可以简化为 ROTATE 指令,非常有趣。
g++ 编译器展开您的循环并使用SHIFT immediate
和ROTATE immediate
指令(因为您按常量值移位和旋转)。
这是在 TimeShift 循环展开案例中重复的六个指令序列:
movq %rax, %rbx
salq $13, %rbx
leaq (%rbp,%rbx), %rbx
movq %rdi, %rbp
sarq $27, %rbp
xorq %rbx, %rdx
这是在 TimeRotate 循环展开案例中重复的六个指令序列:
movq %rdx, %rbx
rorq $45, %rbx
leaq (%rbp,%rbx), %rbx
movq %r8, %rbp
rorq $49, %rbp
xorq %rbx, %r9
它们的区别主要在于SHIFT
使用salq/sarq 和ROTATE
使用rorq,所以你想知道为什么时间不同是正确的。
答案深藏在 Sandy Bridge(您的 Core i5 处理器)的微架构中,可在 INTEL® 64 and IA-32 Processor Architectures Optimization Reference Manual 中找到
最新的是Order Number: 248966-026 April 2012
无论您使用by 1
操作码还是by immediate
,SHIFT
指令都有 1 个周期延迟。它可以从Port 0
或Port 1
分派,因此具有0.5 个周期的吞吐量——处理器每个周期可以分派和退出两条SHIFT immediate
指令。如果需要条件标志的结果(它们不在 gcc 生成的代码中),ROTATE
指令需要三个微操作,如果不需要,则需要两个微操作(因此在您的情况下需要两个微操作)。但是,ROTATE
指令只能从 Port 1
分派,因此具有 1 个周期的吞吐量 - 处理器每个周期只能分派和退出一个 ROTATE immediate
。
我已经复制了下面的相关图片和部分。
3.5.1.5 位旋转
按位旋转可以选择使用 CL 寄存器中指定的计数旋转,或者 立即常数和 1 位。一般来说,rotate by immediate 和 rotate by 寄存器指令比循环旋转 1 位慢。旋转 1 指令有 与班次相同的延迟。 汇编/编译器编码规则 35。(ML 影响,L 通用性)避免 ROTATE 通过寄存器或通过立即指令旋转。如果可能,请替换为 旋转 1 条指令。 在 Intel 微架构代号 Sandy Bridge 中,ROL/ROR by immediate 有 1- 周期吞吐量,SHLD/SHRD 使用相同的寄存器作为源和目标 立即常数具有 1 个周期的延迟和 0.5 个周期的吞吐量。 “ROL/ROR reg, imm8” 指令有两个微操作,旋转延迟为 1 个周期 为标志注册结果和 2 个周期(如果使用)。 在 Intel 微架构代号 Ivy Bridge 中,立即数大于 1 的“ROL/ROR reg, imm8”指令是一个具有一个周期延迟的微操作,当 使用溢出标志结果。当立即数为一时,依赖溢出 后续指令的 ROL/ROR 标志结果将看到 ROL/ROR 指令 具有两个周期的延迟。
2.4.4.2 执行单元和发布端口
在每个周期,内核可能会向四个发布端口中的一个或多个发送微操作。在 微架构层面,store操作进一步分为两部分:store 数据和存储地址操作。调度μops的四个端口 到执行单元以及加载和存储操作如图 2-6 所示。一些 端口每个时钟可以调度两个微操作。那些执行单元被标记为 Double 速度。
端口 0。 在周期的前半部分,端口 0 可以调度任一浮点 move µop(浮点堆栈移动、浮点交换或浮点 存储数据)或一个算术逻辑单元 (ALU) µop(算术、逻辑、分支或存储 数据)。在周期的后半段,它可以调度一个类似的 ALU µop。
端口 1。 在周期的前半部分,端口 1 可以调度任一浮点 执行(除移动之外的所有浮点运算,所有 SIMD 运算) µop 或 1 个正常速度整数(乘法、移位和旋转) µop 或 1 个 ALU(算术) 微操作。在周期的后半段,它可以调度一个类似的 ALU µop。
端口 2。此端口支持每个周期调度一个加载操作。
端口 3。 此端口支持每个周期调度一个存储地址操作。
每个周期的总问题带宽范围为 0 到 6 µops。每个管道 包含多个执行单元。 µops 被分派到对应于正确操作类型的管道。例如,整数算术逻辑单元 浮点执行单元(加法器、乘法器和除法器)可以共享一个 管道。
【讨论】:
完全重写了我的答案,以提供我认为对时间差异的正确解释。 完美的工作!我正在搜索您链接的文档,但找不到。 谢谢。我对该 PDF 文档进行了搜索(在我的 Mac 上使用 Command-F)并找到了 18 个对rotate
的引用,这就是我找到它的方式。
我也做了同样的事情,但不知怎的错过了它(可能我太困了)。 :D【参考方案2】:
根据benchmark,移位和轮换在您的 CPU 上都有相同的延迟,但轮换的吞吐量较低(此处列为“T”的结果是倒数吞吐量,更容易与延迟进行比较)。这可能正是您所看到的那种结果 - 较低的吞吐量有点妨碍,但您并没有完全使执行单元饱和,因此它没有显示出 2 差异的全部因素。测试自己并不容易,尤其是当你必须与编译器对抗以使其发出你想要的指令时。
【讨论】:
是的,就是这样。我计算(从这个和一些类似的基准)差异小于 1 个周期,我发现这很混乱。【参考方案3】:当您查看微基准时,您必须考虑 JIT 将优化常见模式,例如shift,它比不常见的模式更有效地识别,例如旋转(或它无法识别的)这可能意味着两个应该花费相同时间的操作可以执行完全不同的操作,因为一个比另一个更优化。例如更多循环展开或死代码删除。
即使是简单的指令也可以交互产生不同的和意想不到的结果。换句话说,你不能只看一条指令就假设它会告诉你当使用更多指令时它将如何执行。在如此低的层次上,上下文很重要。
我建议您尝试在实际程序中比较这些操作,我怀疑您很难找到可测量的差异。
【讨论】:
您在 JIT 中使用常见模式提出了一个很好的观点,也许 gcc 通过优化到旋转也“厌倦了”,这可能是解释。我会尝试一些汇编程序。 但我不同意 w.r.t.现实的程序——一个现实的程序要么根本不使用旋转,要么就像RC5,比它重要得多;就像我的基准测试一样。以上是关于按位旋转是不是比当前 Intel CPU 上的移位慢?的主要内容,如果未能解决你的问题,请参考以下文章
加速图像处理的神器: INTEL ISPC编译器 迁移图像旋转算法 - ISPC单精度 从单核 到 多核
加速图像处理的神器: INTEL ISPC编译器 迁移图像旋转算法 - ISPC单精度 从单核 到 多核
加速图像处理的神器: INTEL ISPC编译器 迁移图像旋转算法 - ISPC单精度 从单核 到 多核