指令级的有符号数模数
Signed numbers modulus in instruction level
阅读 前一段时间关于 gcc
和 clang
中 有符号 数模计算的不同实现的有趣讨论提出了一个问题我(讨论中没有讨论)。
为什么要执行这个:
if(num % 2 == 1)
以此开头(clang
和 gcc
相似):
movl %edi, %eax
shrl , %eax
addl %edi, %eax
为什么我们从 ((num >> 31) + num)
开始?为什么取 MSB
并将其添加到号码中?这是哪里来的?
负数% 2
的结果是负余数,在C中四舍五入到0。& 1
只适用于正数。
因此编译器生成添加符号位数的代码 - 本质上是将负数增加一位,因此 -1
在最后一位有 0
,然后 -2
有 1
在最后一位,-3
再次有 0
... 然后我们屏蔽除最后一位以外的所有内容,然后 从结果中减去符号位 ,例如:
movl %edi, %edx
shrl , %edx
leal (%rdi,%rdx), %eax
andl , %eax
subl %edx, %eax
它的性能可能比 idiv
指令稍微好一点。例如,在 Core i7 上,带有 r32 的 IDIV 将具有 17-28 的延迟和 7-12 的吞吐量,而生成的代码中的所有其他代码每个都具有 ~1
阅读 gcc
和 clang
中 有符号 数模计算的不同实现的有趣讨论提出了一个问题我(讨论中没有讨论)。
为什么要执行这个:
if(num % 2 == 1)
以此开头(clang
和 gcc
相似):
movl %edi, %eax
shrl , %eax
addl %edi, %eax
为什么我们从 ((num >> 31) + num)
开始?为什么取 MSB
并将其添加到号码中?这是哪里来的?
负数% 2
的结果是负余数,在C中四舍五入到0。& 1
只适用于正数。
因此编译器生成添加符号位数的代码 - 本质上是将负数增加一位,因此 -1
在最后一位有 0
,然后 -2
有 1
在最后一位,-3
再次有 0
... 然后我们屏蔽除最后一位以外的所有内容,然后 从结果中减去符号位 ,例如:
movl %edi, %edx
shrl , %edx
leal (%rdi,%rdx), %eax
andl , %eax
subl %edx, %eax
它的性能可能比 idiv
指令稍微好一点。例如,在 Core i7 上,带有 r32 的 IDIV 将具有 17-28 的延迟和 7-12 的吞吐量,而生成的代码中的所有其他代码每个都具有 ~1