此循环在英特尔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 …
在下面的C++源代码的汇编中.为什么RAX被推入堆栈?
正如我从ABI理解的那样,RAX可以包含来自调用函数的任何内容.但是我们将它保存在这里,然后将堆栈移回8个字节.所以堆栈上的RAX,我认为只与std::__throw_bad_function_call()操作相关......?
代码:-
#include <functional>
void f(std::function<void()> a)
{
a();
}
Run Code Online (Sandbox Code Playgroud)
gcc.godbolt.org使用Clang 3.7.1 -O3 输出:
f(std::function<void ()>): # @f(std::function<void ()>)
push rax
cmp qword ptr [rdi + 16], 0
je .LBB0_1
add rsp, 8
jmp qword ptr [rdi + 24] # TAILCALL
.LBB0_1:
call std::__throw_bad_function_call()
Run Code Online (Sandbox Code Playgroud)
我确定原因很明显,但我很难弄清楚.
这是一个没有std::function<void()>包装器的尾部调用,用于比较:
void g(void(*a)())
{
a();
}
Run Code Online (Sandbox Code Playgroud)
琐碎的:
g(void (*)()): # @g(void (*)())
jmp rdi # TAILCALL
Run Code Online (Sandbox Code Playgroud) 使用以下代码是否存在任何执行速度差异:
cmp al, 0
je done
Run Code Online (Sandbox Code Playgroud)
以下内容:
or al, al
jz done
Run Code Online (Sandbox Code Playgroud)
我知道JE和JZ指令是相同的,并且使用OR可以提供一个字节的大小改进.但是,我也关心代码速度.逻辑运算符似乎比SUB或CMP更快,但我只是想确定.这可能是规模和速度之间的权衡,或双赢(当然代码将更加不透明).
write(1,"hi",3)在linux上反汇编,gcc -s -nostdlib -nostartfiles -O3结果如下:
ba03000000 mov edx, 3 ; thanks for the correction jester!
bf01000000 mov edi, 1
31c0 xor eax, eax
e9d8ffffff jmp loc.imp.write
Run Code Online (Sandbox Code Playgroud)
我不是到编译器的开发,但由于移动到这些寄存器的每一个值是恒定的和已知的编译时间,我很好奇,为什么不GCC使用dl,dil和al来代替.也许有人会说,此功能不会让任何性能上的差异,但有一个在之间的可执行文件的大小有很大的区别mov $1, %rax => b801000000,并mov $1, %al => b001当我们谈论数千寄存器的程序访问.如果软件的优雅部分不仅体积小,它确实会对性能产生影响.
有人可以解释为什么"海湾合作委员会决定"它无所谓?
我正在阅读http://www.realworldtech.com/sandy-bridge/,我在理解一些问题时面临一些问题:
专用堆栈指针跟踪器也存在于Sandy Bridge中并重命名堆栈指针,消除了串行依赖性并删除了多个uop.
什么是dedicated stack pointer tracker实际?
对于Sandy Bridge(和P4),英特尔仍然使用术语ROB.但重要的是要理解,在这种情况下,它只引用了飞行中uops的状态数组
事实上它意味着什么?请说清楚.
Haswell及更早版本的ADC通常为2 uops,有2个周期延迟,因为Intel uops传统上只能有2个输入(https://agner.org/optimize/).在Haswell为FMA引入3输入微指令和某些情况下的索引寻址模式的微融合之后,Broadwell/Skylake及其后来都有单uop ADC/SBB/CMOV .
(但不适用于adc al, imm8短格式编码,或其他al/ax/eax/rax,imm8/16/32/32短格式,没有ModRM.我的答案中有更详细的说明.)
但是adc,即时0是特殊的Haswell解码为只有一个uop. @BeeOnRope测试了这个,并在他的uarch-bench中包含了对这个性能怪癖的检查:https://github.com/travisdowns/uarch-bench.从输出样本CI一个的Haswell服务器上示出之间的差adc reg,0和adc reg,1或adc reg,zeroed-reg.
(对于SBB也是如此.就我所见,在任何CPU上具有相同立即数的等效编码,ADC和SBB性能之间从来没有任何差别.)
这个优化何时adc bl,0推出?
我测试了Core 2 1,发现imm=0延迟是2个周期,相同adc eax,0.同时,也是循环计数是与吞吐量测试一些变化相同的adc eax,3对比0,所以第一代的Core 2(Conroe处理器/ Merom处理器)并没有这样做优化.
回答这个问题的最简单方法可能是在Sandybridge系统上使用我的测试程序,看看是否3比它快adc eax,0.但基于可靠文档的答案也可以.
(顺便说一句,如果有人可以访问Sandybridge上的perf计数器,你还可以通过运行@ BeeOnRope的测试代码来清除在执行uop计数不是处理器宽度倍数的循环时性能降低的谜团.或者是性能我在不再工作的SnB上观察到的只是因为未分层与正常的uops有什么不同?)
脚注1:我在运行Linux的Core 2 E6600(Conroe/Merom)上使用了这个测试程序.
;; NASM / YASM
;; assemble / link this …Run Code Online (Sandbox Code Playgroud) 如果您正在编写程序集,那么将值分配给哪个寄存器重要吗?假设您将累积/中间值存储在 %ebx 而不是 %eax 中,后者传统上用于此目的。这是不好的做法吗?会影响性能吗?
换句话说,您是否可以将它们等同于存储空间,还是应该坚持将它们用于特定目的?
我对x86-64二进制编码很新.我正在尝试修复一些旧的"汇编程序"代码.
无论如何,我正在尝试做这样的事情(英特尔语法):
mov [rbp+rcx], al
Run Code Online (Sandbox Code Playgroud)
汇编程序目前正在生成:
88 04 0D
Run Code Online (Sandbox Code Playgroud)
但这似乎不是一个有效的指示.如果我将SIB字节中的基数更改rbp为其他寄存器,则可以正常工作.另一种使其工作的方法是添加一个零字节的位移(88 44 0D 00).这似乎与其他类似的操作码一起发生.
为什么我不能rbp在那里使用mod=00?
assembly ×8
x86 ×8
intel ×3
x86-64 ×3
abi ×1
accumulator ×1
c++ ×1
gcc ×1
machine-code ×1
optimization ×1
performance ×1