我正在读一本书,作者说这if( a < 901 )比书更快if( a <= 900 ).
与此简单示例不完全相同,但循环复杂代码略有性能变化.我想这必须对生成的机器代码做一些事情,以防它甚至是真的.
LOOP(英特尔参考手动输入)递减ecx/rcx,然后如果非零则跳转.这很慢,但是英特尔不能廉价地把它变得很快吗? dec/jnz已经将宏观融合成 Sandybridge家族的一个 uop; 唯一的区别是设置标志.
loop关于各种微体系结构,来自Agner Fog的说明表:
Bulldozer-family/Ryzen:1 m-op(与宏观融合测试和分支相同,或者jecxz)
P4:4次(相同jecxz)
loope/ loopne).吞吐量= 4c(loop)或7c(loope/ne).loope/ loopne). 吞吐量=每5个循环一个,这是将循环计数器保留在内存中的瓶颈!jecxz只有2 uops,吞吐量与普通吞吐量相同jcc难道解码器不能像lea rcx, [rcx-1]/ 那样解码jrcxz吗?这将是3 uops.至少那是没有地址大小前缀的情况,否则它必须使用ecx和截断RIP,EIP如果跳转; 也许奇怪的地址大小选择控制减量的宽度解释了许多uops?
或者更好,只需将其解码为不设置标志的融合分支和分支? dec ecx …
我使用英特尔®架构代码分析器(IACA)发现了一些意想不到的东西(对我而言).
以下指令使用[base+index]寻址
addps xmm1, xmmword ptr [rsi+rax*1]
Run Code Online (Sandbox Code Playgroud)
根据IACA没有微熔丝.但是,如果我用[base+offset]这样的
addps xmm1, xmmword ptr [rsi]
Run Code Online (Sandbox Code Playgroud)
IACA报告它确实融合了.
英特尔优化参考手册的第2-11节给出了以下"可以由所有解码器处理的微融合微操作"的示例
FADD DOUBLE PTR [RDI + RSI*8]
Run Code Online (Sandbox Code Playgroud)
和Agner Fog的优化装配手册也给出了使用[base+index]寻址的微操作融合的例子.例如,请参见第12.2节"Core2上的相同示例".那么正确的答案是什么?
此循环在英特尔Conroe/Merom上每3个周期运行一次,imul按预期方式在吞吐量方面存在瓶颈.但是在Haswell/Skylake上,它每11个循环运行一次,显然是因为setnz al它依赖于最后一个循环imul.
; synthetic micro-benchmark to test partial-register renaming
mov ecx, 1000000000
.loop: ; do{
imul eax, eax ; a dep chain with high latency but also high throughput
imul eax, eax
imul eax, eax
dec ecx ; set ZF, independent of old ZF. (Use sub ecx,1 on Silvermont/KNL or P4)
setnz al ; ****** Does this depend on RAX as well as ZF?
movzx eax, al
jnz .loop ; }while(ecx);
Run Code Online (Sandbox Code Playgroud)
如果setnz al …
来自Ira Baxter回答,为什么INC和DEC指令不会影响进位标志(CF)?
大多数情况下,我远离
INC而DEC现在,因为他们做的部分条件代码更新,这样就可以在管道中引起滑稽的摊位,和ADD/SUB没有.因此,无关紧要(大多数地方),我使用ADD/SUB避免失速.我使用INC/DEC仅在保持代码较小的情况下,例如,适合高速缓存行,其中一个或两个指令的大小产生足够的差异.这可能是毫无意义的纳米[字面意思!] - 优化,但我在编码习惯上相当老派.
我想问一下为什么它会导致管道中的停顿,而添加不会?毕竟,无论是ADD和INC更新标志寄存器.唯一的区别是INC不更新CF.但为什么重要呢?
我编写了这个简单的汇编代码,运行它并使用GDB查看内存位置:
.text
.global _main
_main:
pushq %rbp
movl $5, -4(%rbp)
addl $6, -4(%rbp)
popq %rbp
ret
Run Code Online (Sandbox Code Playgroud)
它直接在内存中添加5到6个,根据GDB它可以工作.所以这是直接在内存中执行数学运算而不是CPU寄存器.
现在在C中编写相同的东西并将其编译为汇编,结果如下:
... # clang output
xorl %eax, %eax
movl $0, -4(%rbp)
movl $5, -8(%rbp)
movl -8(%rbp), %ecx # load a
addl $6, %ecx # a += 6
movl %ecx, -8(%rbp) # store a
....
Run Code Online (Sandbox Code Playgroud)
在将它们添加到一起之前,它会将它们移动到寄存器中.
那么为什么我们不直接在内存中添加?
它慢了吗?如果是这样,为什么直接在内存中添加甚至允许,为什么汇编程序在开始时没有抱怨我的汇编代码?
编辑:这是第二个程序集块的C代码,我在编译时禁用了优化.
#include <iostream>
int main(){
int a = 5;
a+=6;
return 0;
}
Run Code Online (Sandbox Code Playgroud) 我已经阅读了各种优化指南,声称ADD 1比在x86中使用INC更快.这是真的吗?
当我遇到ADCX以前不知道的指令时,我正在浏览英特尔软件开发人员手册; 它的编码是66 0F 38 F6.它似乎几乎与ADC指令相同,所以为什么你会在以下时间使用ADCX:
ADC)是否有其他副作用或特殊情况ADCX证明有利于ADC?必须有一些很好的理由将其添加到指令库中.
在x86汇编中,当有符号整数上的add或sub操作溢出时,溢出标志置位,当无符号整数上的操作溢出时,置载标志置位.
然而,当谈到inc和dec说明,情况似乎有些不同.根据该网站,该inc指令根本不影响进位标志.
但我不能找到有关如何的任何信息inc和dec如果有的话,会影响溢出标志.
发生整数溢出时执行inc或dec设置溢出标志?对于有符号整数和无符号整数,这种行为是否相同?
============================= 编辑 ==================== =========
好的,基本上这里的共识是,就设置标志而言,INC和DEC应该与ADD和SUB的行为相同,但进位标志除外.这也是英特尔手册中的内容.
问题是,当涉及到无符号整数时,我实际上无法在实践中重现这种行为.
请考虑以下汇编代码(使用GCC内联汇编以便更轻松地打印结果.)
int8_t ovf = 0;
__asm__
(
"movb $-128, %%bh;"
"decb %%bh;"
"seto %b0;"
: "=g"(ovf)
:
: "%bh"
);
printf("Overflow flag: %d\n", ovf);
Run Code Online (Sandbox Code Playgroud)
这里我们递减一个带符号的8位值-128.由于-128是可能的最小值,溢出是不可避免的.正如所料,这打印出:Overflow flag: 1
但是当我们使用无符号值执行相同操作时,行为并不像我预期的那样:
int8_t ovf = 0;
__asm__
(
"movb $255, %%bh;"
"incb %%bh;"
"seto %b0;"
: "=g"(ovf)
:
: "%bh"
); …Run Code Online (Sandbox Code Playgroud) 据我所知,BigInts通常在大多数编程语言中实现为包含数字的数组,例如:当添加其中两个时,每个数字都是一个接一个地添加,就像我们从学校知道的那样,例如:
246
816
* *
----
1062
Run Code Online (Sandbox Code Playgroud)
其中*表示存在溢出.我在学校这样学习,所有BigInt添加函数我已经实现了类似于上面例子的工作.
所以我们都知道我们的处理器只能本地管理从0到2^32/的整数2^64.
这意味着大多数脚本语言为了高级并提供具有大整数的算术,必须实现/使用BigInt库,这些库使用整数作为上面的数组.但当然这意味着它们将比处理器慢得多.
所以我问自己的是:
它可以像任何其他BigInt库一样工作,只是(很多)更快,更低一级:处理器从缓存/ RAM中取一个数字,添加它,然后再将结果写回来.
对我来说似乎是一个好主意,为什么不是那样的?
assembly ×9
x86 ×7
performance ×4
intel ×3
c ×2
biginteger ×1
c++ ×1
clang ×1
cpu ×1
gcc ×1
iaca ×1
increment ×1
optimization ×1
processor ×1
x86-64 ×1