KbL i7-8550U
我正在研究uops-cache的行为,并且遇到了关于它的误解。
按照《英特尔优化手册2.5.2.2
(我的地雷)中的指定:
解码的ICache由32组组成。每套包含八种方式。每个方法最多可容纳六个微操作。
-
所有微操作都以某种方式表示静态的指令在代码中是连续的,并且它们的EIP在同一行内32个字节的区域。
-
[最多可以将三种方式专用于相同的32字节对齐块,允许在每个32字节的区域中总共缓存18个微操作原始的IA程序。
-
无条件分支是某种方式中的最后一个微操作。
情况1:
请考虑以下例程:
uop.h
void inhibit_uops_cache(size_t);
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
为了确保例程的代码实际上是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
运行身份
int main(void){
inhibit_uops_cache(4096 * 4096 * 128L);
}
我有柜台
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
这正是我期望得到的。
绝大多数uops来自uops缓存。 uops数字也完全符合我的期望
mov edx, esi - 1 uop;
jmp imm - 1 uop; near
dec rdi - 1 uop;
ja - 1 uop; near
[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
disas:
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
运行身份
int main(void){
inhibit_uops_cache(4096 * 4096 * 128L);
}
我有柜台
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
计数器是完全意外的。
我希望所有的uops都像以前一样来自dsb,因为该例程符合uops缓存的要求。
[[相反,几乎70%的微指令来自旧版解码管道。
问题:
情况2有什么问题?要查看哪些计数器以了解发生了什么情况?[为了以后的读者而受益,他们可能有相同的症状,但原因有所不同。当我写完0x...30
是not 32字节边界时,我意识到了,仅0x...20
和40
,所以这个错误应该不是问题代码的问题。)
微码更新(MCU)以减轻JCC勘误
可以通过微码更新(MCU)防止这种错误。 MCU 防止跳转时将跳转指令从缓存在解码的ICache中指令越过32字节边界或在32字节边界处结束。在在这种情况下,跳转指令包括所有跳转类型:条件跳转(Jcc),宏融合的op-Jcc(其中op是cmp,test,add,sub和in,inc或dec之一),直接无条件跳转,间接跳转,直接/间接调用和返回。
Intel's whitepaper还包括触发这种不可uop缓存的效果的情况图。 (PDF屏幕截图是从Phoronix article借来的,之前/之后和之后都有基准,并在GCC / GAS中采用了一些变通办法来尝试避免这种新的性能陷阱)。
...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
JA本身跨越了一个边界。
插入NOP之后
dec rdi
应该起作用,打破宏融合,并在新的32字节块开始之后将2字节ja
完全放入。使用
sub rdi, 1
移动JA将不起作用;它仍然会进行宏保险丝操作,并且与该指令对应的x86代码的组合6字节仍将跨越边界。您可以在
jmp
之前使用单字节点号将所有内容移到更早的位置,如果可以将所有内容都移到[]的结尾之前>[ASLR可以更改虚拟页面代码从(地址的第12位及更高版本)开始执行,但不能更改页面内或相对于缓存行的对齐方式。