【发布时间】:2022-01-04 22:46:54
【问题描述】:
我试图将 Peter Cordes 在his answer 中提到的方法与“将 CPU 寄存器中的所有位设置为 1”的问题进行比较。
因此,我编写了一个基准测试,将所有 13 个寄存器设置为除 e/rsp、e/rbp 和 e/rcx 之外的所有位 1。
代码如下。 times 32 nop 用于避免 DSB 和 LSD 影响。
mov ecx, 100000000
Align 32
.test3:
times 32 nop
mov rax,-1
mov rbx,-1
;mov ecx,-1
mov rdx,-1
mov rdi,-1
mov rsi,-1
mov r8,-1
mov r9,-1
mov r10,-1
mov r11,-1
mov r12,-1
mov r13,-1
mov r14,-1
mov r15,-1
dec ecx
jge .test3
jmp .out
我测试了他提到的以下方法,Full code in here
mov e/rax, -1
xor eax, eax
dec e/rax
xor ecx, ecx
lea e/rax, [rcx-1]
or e/rax, -1
为了使这个问题更简洁,我将在下表中使用group1 a (g1a) 替换mov eax,-1。
| number | pattern | test number |
|---|---|---|
| group1 a | mov eax,-1 | test 7 |
| group1 b | mov rax,-1 | test3 |
| group2 a | xor eax, eax / dec eax | test6 |
| group2 b | xor eax, eax / dec rax | test2 |
| group3 a | xor ecx, ecx / lea eax, [rcx-1] | test0 |
| group3 b | xor ecx, ecx / lea rax, [rcx-1] | test-1(test00) |
| group4 a | or eax,-1 | test5 |
| group4 b | or rax,-1 | test1 |
下表显示从第 1 组到第 3 组,当使用 64 位寄存器时,每个循环多出 1 个周期。
IDQ_UOPS_NOT_DELIVERED 也增加了,这可以解释循环次数的增加。 但这可以解释每个循环多 1 个循环的确切原因吗?
| cycles | MITE cycles(r1002479) | MITE 4uops cycles (r4002479) | IDQ UOPS NOT DELIVERED(r19c) | |
|---|---|---|---|---|
| g1a | 1,300,903,705 | 1,300,104,496 | 800,055,137 | 601,487,115 |
| g1b | 1,400,852,931 | 1,400,092,325 | 800,049,313 | 1,001,524,712 |
| g2a | 1,600,920,156 | 1,600,113,480 | 1,300,061,359 | 501,522,554 |
| g2b | 1,700,834,769 | 1,700,108,688 | 1,300,057,576 | 901,467,008 |
| g3a | 1,701,971,425 | 1,700,093,298 | 1,300,111,482 | 902,327,493 |
| g3b | 1,800,891,861 | 1,800,110,096 | 1,300,059,338 | 1,301,497,001 |
| g4a | 1,201,164,208 | 1,200,122,275 | 1,100,049,081 | 201,592,292 |
| g4b | 1,200,553,577 | 1,200,074,422 | 1,100,031,729 | 200,772,985 |
另外,g2a和g2b的端口分布是不同的,不像g1a和g1b(g1a和g1b在端口分布上是一样的),或者g3a和g3b。
如果我评论times 32 nop,这种现象就会消失。是否与 MITE 有关?
| p0 | p1 | p2 | p3 | p4 | p5 | p6 | p7 | |
|---|---|---|---|---|---|---|---|---|
| g1a | 299,868,019 | 300,014,657 | 5,925 | 7,794 | 16,589 | 300,279,232 | 499,885,294 | 7,242 |
| g1b | 299,935,968 | 300,085,089 | 6,622 | 8,758 | 18,842 | 299,935,445 | 500,426,436 | 7,336 |
| g2a | 299,800,192 | 299,758,460 | 7,461 | 9,635 | 20,622 | 399,836,486 | 400,312,354 | 8,446 |
| g2b | 200,047,079 | 200,203,026 | 7,899 | 9,967 | 21,539 | 500,542,313 | 500,296,034 | 9,635 |
| g3a | 36,568 | 550,860,773 | 7,784 | 10,147 | 22,538 | 749,063,082 | 99,856,623 | 9,767 |
| g3b | 36,858 | 599,960,197 | 8,232 | 10,763 | 23,086 | 700,499,893 | 100,078,368 | 9,513 |
| g4a | 200,142,036 | 300,600,535 | 5,383 | 6,705 | 15,344 | 400,045,302 | 500,364,377 | 6,802 |
| g4b | 200,224,703 | 300,284,609 | 5,464 | 7,031 | 15,817 | 400,047,050 | 499,467,546 | 6,746 |
环境:intel i7-10700、ubuntu 20.04 和 NASM 2.14.02。
用英语解释这个对我来说有点困难。如果描述不清楚,请发表评论。
【问题讨论】:
-
问题是什么?您是否尝试衡量较短和较长指令之间的差异?
-
times 32 nop用于避免 DSB 和 LSD 影响。 - 意味着您正在对传统解码器 (MITE) 进行基准测试,因为这是前端的瓶颈.尤其是像 7 字节mov rdx,-1或 5 字节mov edx,-1这样的长指令。您标记了 [intel],但您使用的是什么特定的 CPU? Skylake 衍生的?我猜不是 Alder Lake 上的 E-core;它们在 L1I 缓存中具有更宽的解码和标记指令边界,而 SnB 系列 CPU 获取 16 字节块以进行传统解码。在 agner.org/optimize 上查看 Agner 的 microarch pdf -
总标题大多与The advantages of using 32bit registers/instructions in x86-64重复。 IDK 您正在寻找的答案有多具体,您使用更长或更短的指令创建了哪些解码瓶颈,但很明显,当平均长度 >= 4 左右时,使用更长的指令会消耗吞吐量,尽管 SKL 和以后有由于解码和发布/重命名之间的缓冲,5 个解码器可以弥补这一点。 (建立一些缓冲解码 5 nops / clock,然后在生产较少时吃掉它)
-
哦,我明白了。预解码仅限于查看每个周期 16 个字节,并且可能仅来自连续的获取块。 (或者可能 fetch 本身是一个瓶颈,但是它和 pre-decode 之间的队列,所以 NOP 应该给它一些时间来赶上。)分支预测可以让 CPU 将不同 fetch 块的部分粘贴到一个 16 字节的 pre -解码组。但是,如果队列中有足够的内容,我认为实际的解码器本身可以查看更多的总字节数。对于较大的平均指令长度,问题通常出在预解码。
-
@PeterCordes Skylake 有 4 个解码器(每个周期最多可以向 IDQ 提供 5 微指令),每个周期最多可以预解码 5 条指令。
标签: assembly x86-64 intel cpu-architecture micro-optimization