St.*_*rio 7 performance x86 assembly intel cpu-architecture
KbL i7-8550U
我正在研究 uops-cache 的行为并遇到了关于它的误解。
如英特尔优化手册2.5.2.2
(我的)中所述:
解码的 ICache 由 32 组组成。每组包含八种方式。 每路最多可容纳六个微操作。
——
Way 中的所有微操作表示在代码中静态连续的指令,并且它们的 EIP 位于相同的对齐 32 字节区域内。
——
最多三种方式可以专用于相同的 32 字节对齐块,从而允许在原始 IA 程序的每个 32 字节区域中缓存总共 18 个微操作。
——
无条件分支是 Way 中的最后一个微操作。
情况1:
考虑以下例程:
uop.h
void inhibit_uops_cache(size_t);
Run Code Online (Sandbox Code Playgroud)
uop.S
align 32
inhibit_uops_cache:
mov edx, esi
mov edx, esi
mov edx, esi
mov edx, esi
mov edx, esi
mov edx, esi
jmp decrement_jmp_tgt
decrement_jmp_tgt:
dec rdi
ja inhibit_uops_cache ;ja is intentional to avoid Macro-fusion
ret
Run Code Online (Sandbox Code Playgroud)
为了确保例程的代码实际上是 32 字节对齐的,这里是 asm
0x555555554820 <inhibit_uops_cache> mov edx,esi
0x555555554822 <inhibit_uops_cache+2> mov edx,esi
0x555555554824 <inhibit_uops_cache+4> mov edx,esi
0x555555554826 <inhibit_uops_cache+6> mov edx,esi
0x555555554828 <inhibit_uops_cache+8> mov edx,esi
0x55555555482a <inhibit_uops_cache+10> mov edx,esi
0x55555555482c <inhibit_uops_cache+12> jmp 0x55555555482e <decrement_jmp_tgt>
0x55555555482e <decrement_jmp_tgt> dec rdi
0x555555554831 <decrement_jmp_tgt+3> ja 0x555555554820 <inhibit_uops_cache>
0x555555554833 <decrement_jmp_tgt+5> ret
0x555555554834 <decrement_jmp_tgt+6> nop
0x555555554835 <decrement_jmp_tgt+7> nop
0x555555554836 <decrement_jmp_tgt+8> nop
0x555555554837 <decrement_jmp_tgt+9> nop
0x555555554838 <decrement_jmp_tgt+10> nop
0x555555554839 <decrement_jmp_tgt+11> nop
0x55555555483a <decrement_jmp_tgt+12> nop
0x55555555483b <decrement_jmp_tgt+13> nop
0x55555555483c <decrement_jmp_tgt+14> nop
0x55555555483d <decrement_jmp_tgt+15> nop
0x55555555483e <decrement_jmp_tgt+16> nop
0x55555555483f <decrement_jmp_tgt+17> nop
Run Code Online (Sandbox Code Playgroud)
运行为
int main(void){
inhibit_uops_cache(4096 * 4096 * 128L);
}
Run Code Online (Sandbox Code Playgroud)
我拿到了柜台
Performance counter stats for './bin':
6?431?201?748 idq.dsb_cycles (56,91%)
19?175?741?518 idq.dsb_uops (57,13%)
7?866?687 idq.mite_uops (57,36%)
3?954?421 idq.ms_uops (57,46%)
560?459 dsb2mite_switches.penalty_cycles (57,28%)
884?486 frontend_retired.dsb_miss (57,05%)
6?782?598?787 cycles (56,82%)
1,749000366 seconds time elapsed
1,748985000 seconds user
0,000000000 seconds sys
Run Code Online (Sandbox Code Playgroud)
这正是我期望得到的。
绝大多数 uops 来自 uops 缓存。uops 数字也完全符合我的期望
mov edx, esi - 1 uop;
jmp imm - 1 uop; near
dec rdi - 1 uop;
ja - 1 uop; near
Run Code Online (Sandbox Code Playgroud)
4096 * 4096 * 128 * 9 = 19?327?352?832
大约等于计数器 19?326?755?442 + 3?836?395 + 1?642?975
案例2:
考虑inhibit_uops_cache
通过注释掉的一条指令不同的实现:
align 32
inhibit_uops_cache:
mov edx, esi
mov edx, esi
mov edx, esi
mov edx, esi
mov edx, esi
; mov edx, esi
jmp decrement_jmp_tgt
decrement_jmp_tgt:
dec rdi
ja inhibit_uops_cache ;ja is intentional to avoid Macro-fusion
ret
Run Code Online (Sandbox Code Playgroud)
疾病:
0x555555554820 <inhibit_uops_cache> mov edx,esi
0x555555554822 <inhibit_uops_cache+2> mov edx,esi
0x555555554824 <inhibit_uops_cache+4> mov edx,esi
0x555555554826 <inhibit_uops_cache+6> mov edx,esi
0x555555554828 <inhibit_uops_cache+8> mov edx,esi
0x55555555482a <inhibit_uops_cache+10> jmp 0x55555555482c <decrement_jmp_tgt>
0x55555555482c <decrement_jmp_tgt> dec rdi
0x55555555482f <decrement_jmp_tgt+3> ja 0x555555554820 <inhibit_uops_cache>
0x555555554831 <decrement_jmp_tgt+5> ret
0x555555554832 <decrement_jmp_tgt+6> nop
0x555555554833 <decrement_jmp_tgt+7> nop
0x555555554834 <decrement_jmp_tgt+8> nop
0x555555554835 <decrement_jmp_tgt+9> nop
0x555555554836 <decrement_jmp_tgt+10> nop
0x555555554837 <decrement_jmp_tgt+11> nop
0x555555554838 <decrement_jmp_tgt+12> nop
0x555555554839 <decrement_jmp_tgt+13> nop
0x55555555483a <decrement_jmp_tgt+14> nop
0x55555555483b <decrement_jmp_tgt+15> nop
0x55555555483c <decrement_jmp_tgt+16> nop
0x55555555483d <decrement_jmp_tgt+17> nop
0x55555555483e <decrement_jmp_tgt+18> nop
0x55555555483f <decrement_jmp_tgt+19> nop
Run Code Online (Sandbox Code Playgroud)
运行为
int main(void){
inhibit_uops_cache(4096 * 4096 * 128L);
}
Run Code Online (Sandbox Code Playgroud)
我拿到了柜台
Performance counter stats for './bin':
2?464?970?970 idq.dsb_cycles (56,93%)
6?197?024?207 idq.dsb_uops (57,01%)
10?845?763?859 idq.mite_uops (57,19%)
3?022?089 idq.ms_uops (57,38%)
321?614 dsb2mite_switches.penalty_cycles (57,35%)
1?733?465?236 frontend_retired.dsb_miss (57,16%)
8?405?643?642 cycles (56,97%)
2,117538141 seconds time elapsed
2,117511000 seconds user
0,000000000 seconds sys
Run Code Online (Sandbox Code Playgroud)
计数器完全出乎意料。
我希望所有的 uops 都像以前一样来自 dsb,因为例程符合 uops 缓存的要求。
相比之下,几乎 70% 的 uops 来自 Legacy Decode Pipeline。
问题:案例 2 有什么问题?看什么计数器来了解发生了什么?
UPD:按照@PeterCordes 的想法,我检查了无条件分支目标的 32 字节对齐方式decrement_jmp_tgt
。结果如下:
案例3:
将 onconditionaljump
目标对齐到 32 字节如下
align 32
inhibit_uops_cache:
mov edx, esi
mov edx, esi
mov edx, esi
mov edx, esi
mov edx, esi
; mov edx, esi
jmp decrement_jmp_tgt
align 32 ; align 16 does not change anything
decrement_jmp_tgt:
dec rdi
ja inhibit_uops_cache
ret
Run Code Online (Sandbox Code Playgroud)
疾病:
0x555555554820 <inhibit_uops_cache> mov edx,esi
0x555555554822 <inhibit_uops_cache+2> mov edx,esi
0x555555554824 <inhibit_uops_cache+4> mov edx,esi
0x555555554826 <inhibit_uops_cache+6> mov edx,esi
0x555555554828 <inhibit_uops_cache+8> mov edx,esi
0x55555555482a <inhibit_uops_cache+10> jmp 0x555555554840 <decrement_jmp_tgt>
#nops to meet the alignment
0x555555554840 <decrement_jmp_tgt> dec rdi
0x555555554843 <decrement_jmp_tgt+3> ja 0x555555554820 <inhibit_uops_cache>
0x555555554845 <decrement_jmp_tgt+5> ret
Run Code Online (Sandbox Code Playgroud)
并运行为
int main(void){
inhibit_uops_cache(4096 * 4096 * 128L);
}
Run Code Online (Sandbox Code Playgroud)
我得到以下计数器
Performance counter stats for './bin':
4?296?298?295 idq.dsb_cycles (57,19%)
17?145?751?147 idq.dsb_uops (57,32%)
45?834?799 idq.mite_uops (57,32%)
1?896?769 idq.ms_uops (57,32%)
136?865 dsb2mite_switches.penalty_cycles (57,04%)
161?314 frontend_retired.dsb_miss (56,90%)
4?319?137?397 cycles (56,91%)
1,096792233 seconds time elapsed
1,096759000 seconds user
0,000000000 seconds sys
Run Code Online (Sandbox Code Playgroud)
结果完全符合预期。超过 99% 的 uops 来自 dsb。
平均 dsb uops 交付率 = 17?145?751?147 / 4?296?298?295
=3.99
接近峰值带宽。
请参阅代码对齐显着影响编译器选项的性能,以解决英特尔引入 Skylake 派生 CPU 的性能坑,作为此解决方法的一部分。
其他观察结果:6mov
条指令块应该填充一个 uop 缓存行,并单独一行jmp
。在第 2 种情况下, 5 mov
+jmp
应该适合一个缓存行(或更恰当的“方式”)。
(张贴这对谁可能具有相同的症状,但不同的事业未来的读者受益。 我意识到权利,我写完这是0x...30
是不是一个32字节的边界,只有0x...20
和40
,所以此错误不应该成为问题问题中的代码。)
最近(2019 年末)微代码更新引入了一个新的性能坑。 它围绕 Intel 在 Skylake 衍生的微架构上的 JCC 勘误表工作。(特别是您的 Kaby-Lake 上的 KBL142)。
微码更新 (MCU) 以缓解 JCC 错误
这种错误可以通过微码更新 (MCU) 来防止。当跳转指令跨越 32 字节边界或在 32 字节边界处结束时,MCU 会阻止跳转指令缓存在解码的 ICache 中。在这种情况下,跳转指令包括所有跳转类型:条件跳转 (Jcc)、宏融合 op-Jcc(其中 op 是 cmp、test、add、sub、and、inc 或 dec 之一)、直接无条件跳转、间接跳转、直接/间接调用,然后返回。
英特尔的白皮书还包括触发这种非 uop 可缓存效应的案例图。(PDF 截图从Phoronix 文章中借用了之前/之后的基准测试,之后使用 GCC/GAS 中的一些变通方法重建,试图避免这种新的性能陷阱)。
代码中 ja 的最后一个字节是...30
,所以它是罪魁祸首。
如果这是一个 32 字节的边界,而不仅仅是 16,那么我们就会遇到问题:
0x55555555482a <inhibit_uops_cache+10> jmp # fine
0x55555555482c <decrement_jmp_tgt> dec rdi
0x55555555482f <decrement_jmp_tgt+3> ja # spans 16B boundary (not 32)
0x555555554831 <decrement_jmp_tgt+5> ret # fine
Run Code Online (Sandbox Code Playgroud)
本节未完全更新,还在讨论跨越32B边界
JA 本身跨越了一个边界。
在 dec rdi
应该工作之后插入 NOP ,将 2 字节ja
完全放在边界之后,并带有一个新的 32 字节块。无论如何,dec/ja 的宏融合是不可能的,因为 JA 读取 CF(和 ZF)但 DEC 不写入 CF。
使用sub rdi, 1
移动JA将不工作; 它会进行宏融合,并且与该指令对应的 x86 代码的组合 6 字节仍将跨越边界。
如果在块的最后一个字节之前将所有内容全部放入,则可以使用单字节 nops 代替mov
之前的jmp
所有内容。
ASLR 可以更改从哪个虚拟页面代码执行(地址的第 12 位和更高位),但不能更改页面内的对齐方式或相对于缓存线的对齐方式。因此,我们在一种情况下在反汇编中看到的情况每次都会发生。
归档时间: |
|
查看次数: |
483 次 |
最近记录: |