32字节对齐的例程不适合uops缓存

问题描述 投票:6回答:1

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有什么问题?要查看哪些计数器以了解发生了什么情况?
performance assembly x86 intel cpu-architecture
1个回答
0
投票
这不是OP的问题的答案,但是需要提防

[为了以后的读者而受益,他们可能有相同的症状,但原因有所不同。当我写完0x...30not 32字节边界时,我意识到了,仅0x...2040,所以这个错误应该不是问题代码的问题。


[[最近(2019年末)微代码更新引入了一个新的性能隐患。
它适用于Skylake派生的微体系结构上英特尔的JCC勘误表。 (特别是您的Kaby-Lake上的KBL142)。

微码更新(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中采用了一些变通办法来尝试避免这种新的性能陷阱)。

JCC


您的代码中由宏融合的dec / 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

JA本身跨越了一个边界。

插入NOP 

之后 dec rdi应该起作用,打破宏融合,并在新的32字节块开始之后将2字节ja完全放入。

使用sub rdi, 1移动JA将起作用;它仍然会进行宏保险丝操作,并且与该指令对应的x86代码的组合6字节仍将跨越边界。

您可以在jmp之前使用单字节点号将所有内容移到更早的位置,如果可以将所有内容都移到[]的结尾之前>

[ASLR可以更改虚拟页面代码从(地址的第12位及更高版本)开始执行,但不能更改页面内或相对于缓存行的对齐方式。

© www.soinside.com 2019 - 2024. All rights reserved.