Or *_*cov 2 performance x86 assembly x86-64 micro-optimization
我正在寻找一种公式/方法来衡量一条指令的速度,或者更具体地说是通过CPU周期给出每条指令的"得分".
我们以下面的汇编程序为例,
nop
mov eax,dword ptr [rbp+34h]
inc eax
mov dword ptr [rbp+34h],eax
Run Code Online (Sandbox Code Playgroud)
以及英特尔Skylake的以下信息:
mov r,m:吞吐量= 0.5延迟= 2
mov m,r:吞吐量= 1延迟= 2
nop:吞吐量= 0.25延迟=非
inc:吞吐量= 0.25延迟= 1
我知道程序中指令的顺序在这里很重要,但我希望创建一些通用的东西,不需要"对单循环准确"
任何人都知道我该怎么做?
非常感谢
没有可以申请的公式; 你必须衡量.
同一uarch系列的不同版本上的相同指令可以具有不同的性能.例如mulps:
addps也在FMA单元上运行,丢弃专用FP乘法单元,因此增加延迟更高,但吞吐量更高.如果不进行测量或了解一些微体系结构细节,就无法预测任何这些.我们期望FP数学运算不会是单周期延迟,因为它们比整数运算复杂得多.(因此,如果它们是单周期,则对于整数运算,时钟速度设置得太低.)
您可以通过在展开的循环中多次重复指令来进行测量.或者完全展开而没有循环,但是你打败了uop-cache并且可以获得前端瓶颈.(例如,用于解码10字节mov r64, imm64)
Agner Fog通过计时重复指令的大型非循环代码块来创建他的指令表(您似乎正在阅读). https://agner.org/optimize/.他的教学表的介绍部分简要介绍了他的测量方法,他的微型指南解释了x86微体系结构在内部如何工作的更多细节.
http://instlatx64.atw.hu/也有实验测量结果.我认为他们使用类似技术重复相同指令的大块,可能小到足以适应uop缓存.但是它们不使用perf计数器来测量每个指令所需的执行端口,因此它们的吞吐量数字无法帮助您确定哪些指令与哪些指令竞争.
要自己测量延迟,可以将每条指令的输出作为下一步的输入.
mov ecx, 10000000
inc_latency:
inc eax
inc eax
inc eax
inc eax
inc eax
inc eax
sub ecx,1 ; avoid partial-flag false dep for P4
jnz inc_latency ; dec or sub/jnz macro-fuses into 1 uop on Intel SnB-family
Run Code Online (Sandbox Code Playgroud)
这个7 inc条指令的依赖链将在每个7 * inc_latency循环的1次迭代中瓶颈循环.使用性能计数器进行核心时钟周期(不是RDTSC周期),您可以轻松地将所有迭代的时间测量为10k中的1个部分,并且可能更精确地比这更精确.无论您使用何种时间,重复计数10000000都会隐藏开始/停止开销.
我通常在Linux静态可执行文件中放置一个这样的循环,它只是sys_exit(0)直接(用syscall)指令进行系统调用,并为整个可执行文件perf stat ./testloop计时以获得时间和循环计数.(请参阅x86的MOV真的是"免费的"吗?为什么我不能重现这个?一个例子).
另一个例子是理解lfence对具有两个长依赖链的循环的影响,用于增加长度,增加了使用lfence以消除两个dep链的无序执行窗口的复杂性.
要测量吞吐量,可以使用单独的寄存器,和/或偶尔包含xor-zeroing来破坏dep链并让乱序exec重叠. 不要忘记也使用perf计数器来查看它可以运行的端口,这样你就可以知道它将与哪些其他指令竞争.(例如,FMA(p01)和shuffles(p5)根本不竞争Haswell/Skylake的后端资源,仅用于前端吞吐量.)不要忘记测量前端uop计数:一些指令解码以乘以uop.
我们需要多少个不同的依赖链来避免瓶颈?我们知道延迟(首先测量它),我们知道最大可能吞吐量(执行端口数或前端吞吐量).
例如,如果FP乘法具有0.25c吞吐量(每个时钟4个),我们可以在Haswell(5c延迟)上同时保持20个飞行.这比我们注册的更多,所以我们可以使用全部16并发现实际上吞吐量只有0.5c.但如果事实证明16个寄存器是一个瓶颈,我们xorps xmm0,xmm0偶尔可以添加并让无序执行重叠一些块.
更多通常更好; 只有几乎不足以隐藏延迟可能会因不完美的调度而变慢.如果我们想测量inc,我们会这样做:
mov ecx, 10000000
inc_latency:
%rep 10 ;; source-level repeat of a block, no runtime branching
inc eax
inc ebx
; not ecx, we're using it as a loop counter
inc edx
inc esi
inc edi
inc ebp
inc r8d
inc r9d
inc r10d
inc r11d
inc r12d
inc r13d
inc r14d
inc r15d
%endrep
sub ecx,1 ; break partial-flag false dep for P4
jnz inc_latency ; dec/jnz macro-fuses into 1 uop on Intel SnB-family
Run Code Online (Sandbox Code Playgroud)
如果我们担心部分标志错误依赖或标志合并效果,我们可能会尝试在某个xor eax,eax地方混合让OoO exec重叠,而不仅仅是在sub写入所有标志时.(参见INC指令与ADD 1:重要吗?)
测量shl r32, clSandybridge系列的吞吐量和延迟存在类似的问题:标志依赖链通常与计算无关,但shl背对背通过FLAGS和寄存器创建依赖关系.(或者对于吞吐量,甚至没有寄存器dep).
我在Agner Fog的博客上发布了这个帖子:https://www.agner.org/optimize/blog/read.php?i = 415#820 .我混入shl edx,cl了四条add edx,1指令,看看添加一条指令的增量减速是什么,其中FLAGS依赖是一个非问题.在SKL上,它平均只会减慢额外的1.23个周期,所以真正的延迟成本shl只有~1.23个周期,而不是2.(由于资源冲突导致标记合并,这不是一个整数或只是1的微指令shl,我猜.BMI2 shlx edx, edx, ecx会完全1C,因为它只是一个单一的UOP).
相关:对于整个代码块(包含不同指令)的静态性能分析,请参阅预测现代超标量处理器上的操作延迟的注意事项以及如何手动计算它们?.(它使用"延迟"一词来表示整个计算的端到端延迟,但实际上要求OoO exec的内容足够小以重叠不同的部分,因此指令延迟和吞吐量都很重要.)
Latency=2加载/存储的数字似乎来自Agner Fog的指令表(https://agner.org/optimize/).遗憾的是,它们并不准确mov rax, [rax].如果你把它放在一个循环中测量它,你会发现4c延迟.
Agner将加载/存储延迟分解为使总存储/重新加载延迟正确的东西,但由于某种原因,当缓存来自缓存而不是存储时,他不会使加载部分等于L1d加载使用延迟缓冲.(但是请注意,如果加载提供ALU指令而不是另一个加载,则延迟为5c.因此简单的寻址模式快速路径仅对纯指针追逐有帮助.)
| 归档时间: |
|
| 查看次数: |
490 次 |
| 最近记录: |