gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-03 09:27:34 30 4
gpt4 key购买 nike

KbL i7-8550U
我正在研究 uops-cache 的行为并遇到了关于它的误解。

如英特尔优化手册 2.5.2.2 中所述(我的):

The Decoded ICache consists of 32 sets. Each set contains eight Ways. Each Way can hold up to six micro-ops.



——

All micro-ops in a Way represent instructions which are statically contiguous in the code and have their EIPs within the same aligned 32-byte region.



——

Up to three Ways may be dedicated to the same 32-byte aligned chunk, allowing a total of 18 micro-ops to be cached per 32-byte region of the original IA program.



——

A non-conditional branch is the last micro-op in a Way.



案例 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

疾病:
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% 的 uops 来自 Legacy Decode Pipeline。

问题: CASE 2 有什么问题?看什么计数器来了解发生了什么?

更新:按照@PeterCordes 的想法,我检查了无条件分支目标的 32 字节对齐 decrement_jmp_tgt .结果如下:

案例 3:

有条件地对齐 jump目标为 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

疾病:
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

并运行为
int main(void){
inhibit_uops_cache(4096 * 4096 * 128L);
}

我得到以下计数器
 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

结果完全符合预期。超过 99% 的 uops 来自 dsb。

平均 dsb uops 交付率 = 17 145 751 147 / 4 296 298 295 = 3.99

接近峰值带宽。

最佳答案

这不是 OP 问题的答案,而是需要注意的问题
Code alignment dramatically affects performance作为此解决方法的一部分,编译器选项可以解决 Intel 引入 Skylake 派生 CPU 中的这个性能坑。

其他观察:6 mov的块指令应该填充一个 uop 缓存行,用 jmp在一行中。在情况 2 中, 5 mov + jmp应该适合一个缓存行(或更恰当的“方式”)。
( 发布此内容是为了将来可能有相同症状但原因不同的读者。 在我写完之后我意识到0x...30 不是32 字节的边界,只有0x...2040 ,所以 这个勘误不应该是问题代码的问题。 )

最近(2019 年末)微代码更新引入了一个新的性能坑。 它围绕 Intel 在 Skylake 衍生的微架构上的 JCC 勘误表工作。 (特别是您的 Kaby-Lake 上的 KBL142)。

Microcode Update (MCU) to Mitigate JCC Erratum


This erratum can be prevented by a microcode update (MCU). The MCU preventsjump instructions from being cached in the Decoded ICache when the jumpinstructions cross a 32-byte boundary or when they end on a 32-byte boundary. Inthis context, Jump Instructions include all jump types: conditional jump (Jcc), macrofused op-Jcc (where op is one of cmp, test, add, sub, and, inc, or dec), directunconditional jump, indirect jump, direct/indirect call, and return.


Intel's whitepaper还包括触发这种非 uop-cacheable 效果的案例图。 (从 Phoronix article 借用的 PDF 截图,在之前/之后和之后的基准测试中使用 GCC/GAS 中的一些变通方法重建,试图避免这种新的性能陷阱)。
JCC

代码中 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
本节未全面更新,还在说跨越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 位和更高位),但不能更改页面内的对齐方式或相对于缓存线的对齐方式。因此,我们在一种情况下在反汇编中看到的情况每次都会发生。

关于performance - 32 字节对齐例程不适合 uops 缓存,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61016077/

30 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com