Assembly 为什么g++;x64/i86,无需优化,以这种方式编译模数
以下代码是由g++5.4.0编译的x64。左边是编译后的输出。右边的东西是我期望的样子。当然,右边的stuf在语法上可能不正确。该代码基本上应该执行以下操作: 如果(i%3==0) 做事 400512将把u带到if块后的空间Assembly 为什么g++;x64/i86,无需优化,以这种方式编译模数,assembly,g++,64-bit,x86-64,Assembly,G++,64 Bit,X86 64,以下代码是由g++5.4.0编译的x64。左边是编译后的输出。右边的东西是我期望的样子。当然,右边的stuf在语法上可能不正确。该代码基本上应该执行以下操作: 如果(i%3==0) 做事 400512将把u带到if块后的空间 mov -0x4(%rbp),%ecx mov -0x4(%rbp), %eax mov $0x55555556,%edx idvq $0x3 mov %ecx,%eax
mov -0x4(%rbp),%ecx mov -0x4(%rbp), %eax
mov $0x55555556,%edx idvq $0x3
mov %ecx,%eax cmp %edx, 0x0
imul %edx jnz 400512 <main+0x3c>
mov %ecx,%eax
sar $0x1f,%eax
sub %eax,%edx
mov %edx,%eax
mov %eax,%edx
add %edx,%edx
add %eax,%edx
mov %ecx,%eax
sub %edx,%eax
test %eax,%eax
jne 400512 <main+0x3c>
mov-0x4(%rbp),%ecx mov-0x4(%rbp),%eax
mov$0x555556,%edx idvq$0x3
mov%ecx,%eax cmp%edx,0x0
imul%edx jnz 400512
mov%ecx,%eax
sar$0x1f,%eax
子%eax,%edx
移动%edx,%eax
mov%eax,%edx
添加%edx,%edx
添加%eax,%edx
mov%ecx,%eax
低于%edx,%eax
测试%eax,%eax
jne 400512
对于那些比我聪明得多的人,我的问题是:为什么g++有这么多计算模数的东西,有人能给我解释一下它在做什么。出于好奇,Ross Ridge的注释修改为
unsigned int
生成了更短的代码:
bar(unsigned int):
mov eax, edi
mov edx, -1431655765
mul edx
shr edx
lea eax, [rdx+rdx*2]
cmp edi, eax
je .L4
rep ret
.L4:
jmp foo()
但是回到
int
版本。。。它很整洁。。。对于一个编译器,但它仍然感觉有些
等等,它是否对3进行了正确的除法,然后将其相乘,然后检查是否计算了相等的数字?六羟甲基三聚氰胺六甲醚。。。但是我们只需要mod 3==0
测试,对吗
那么这也应该做到:
bar(int):
mov eax, edi
mov edx, 1431655766
imul edx
; don't +1 of div result for negative edi
; mov eax, edi
; sar eax, 31
; sub edx, eax
; instead do +3 for all results
lea eax, [rdx+rdx*2+3]
sub eax, edi ; eax = 0, 1, 2 or 3 (!)
jpe .L4 ; so parity-even condition will resolve it (!)
rep ret
.L4:
jmp foo()
不幸的是,这只是“mod 3”优化的特例(我不确定这是否真的改善了任何东西)但我还是忍不住要“高尔夫”一下。出于好奇,罗斯·里奇为
unsigned int
修改的评论产生了更短的代码:
bar(unsigned int):
mov eax, edi
mov edx, -1431655765
mul edx
shr edx
lea eax, [rdx+rdx*2]
cmp edi, eax
je .L4
rep ret
.L4:
jmp foo()
但是回到
int
版本。。。它很整洁。。。对于一个编译器,但它仍然感觉有些
等等,它是否对3进行了正确的除法,然后将其相乘,然后检查是否计算了相等的数字?六羟甲基三聚氰胺六甲醚。。。但是我们只需要mod 3==0
测试,对吗
那么这也应该做到:
bar(int):
mov eax, edi
mov edx, 1431655766
imul edx
; don't +1 of div result for negative edi
; mov eax, edi
; sar eax, 31
; sub edx, eax
; instead do +3 for all results
lea eax, [rdx+rdx*2+3]
sub eax, edi ; eax = 0, 1, 2 or 3 (!)
jpe .L4 ; so parity-even condition will resolve it (!)
rep ret
.L4:
jmp foo()
不幸的是,这只是“mod 3”优化的特例(我不确定这是否真的改善了任何东西)但我还是忍不住要“高尔夫”一下。我可能已经找到了答案,risc操作大约需要1/2个周期。看起来idvq可能需要10个左右,但仍在调查它是针对速度而不是尺寸优化的;)分裂是缓慢的。参考ah-thx,我当然会看一看,当然你可以使用计数器,得到更好的代码;)gcc at
-O0
仍然使用其模块化乘法逆生成器。位于-O0
的其他一些编译器将发出朴素的idiv
(没错,有一个语法错误:idiv没有立即形式。但有相同的区别)。请参阅:gcc-O0
并不意味着没有优化。64位IDIV非常慢。请看,我可能已经找到了risc操作大约需要1/2个周期的答案。看起来idvq可能需要10个左右的周期,仍然在研究它是针对速度而不是大小进行优化的;)分裂是缓慢的。参考ah-thx,我当然会看一看,当然你可以使用计数器,得到更好的代码;)gcc at-O0
仍然使用其模块化乘法逆生成器。位于-O0
的其他一些编译器将发出朴素的idiv
(没错,有一个语法错误:idiv没有立即形式。但有相同的区别)。请参阅:gcc-O0
并不意味着没有优化。64位IDIV非常慢。请看,您的第一个代码片段仍然适用于int
。使用unsigned int
参数,它将imul
替换为mul
,并省略sub
。也许你复制并粘贴了错误的超负荷?@CodyGray是的,谢谢你,我在编辑这篇文章的过程中有点困惑,混淆了两条不同的思路。。。现在应该有意义了。现在我想知道,为什么我要麻烦sub
,cmp
也会更新PF,对吗?是的,cmp
会以与sub
完全相同的方式更新标志。但无论如何,您在这里并没有执行cmp
,因此将sub
替换为cmp
并不能提高性能。唯一的优势是如果需要保留目标寄存器。(实际上,这里不能使用cmp
,因为需要将结果放在eax
中才能返回!)您的第一个代码段仍然是int
。使用unsigned int
参数,它将imul
替换为mul
,并省略sub
。也许你复制并粘贴了错误的超负荷?@CodyGray是的,谢谢你,我在编辑这篇文章的过程中有点困惑,混淆了两条不同的思路。。。现在应该有意义了。现在我想知道,为什么我要麻烦sub
,cmp
也会更新PF,对吗?是的,cmp
会以与sub
完全相同的方式更新标志。但无论如何,您在这里并没有执行cmp
,因此将sub
替换为cmp
并不能提高性能。唯一的优势是如果需要保留目标寄存器。(实际上,您不能在这里使用cmp
,因为您需要将结果放在eax
中才能返回它!)