32 位和 64 位寄存器是否会导致 CPU 微架构的差异?

Do 32-bit and 64-bit registers cause differences in CPU micro architecture?

我正在尝试将 Peter Cordes 在 中提到的方法与 'set all bits in CPU register to 1'.

的问题进行比较

因此,我编写了一个基准测试,将所有 13 个寄存器设置为所有位 1,除了 e/rspe/rbpe/rcx

代码如下。 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)代替下面table中的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

下面的table表示从第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

环境:英特尔 i7-10700、ubuntu 20.04 和 NASM 2.14.02。

我用英语解释这个有点困难。描述不清楚的请留言

所有示例中的瓶颈都是预解码器。

我用我的模拟器 uiCA (https://uica.uops.info/, https://github.com/andreas-abel/uiCA) 分析了你的例子。它预测以下吞吐量,与您的测量值非常匹配:

uiCA 生成的跟踪 table 提供了一些代码执行方式的见解。例如,对于 g1a,它生成以下跟踪:

你可以看到对于32个nop,预解码器需要8个周期,而对于剩余的指令,它需要5个周期,它们一起对应于你测量的13个周期。

您可能会注意到,在某些周期中,只有少量指令被预解码;例如,在第四个周期,只有一条指令被预解码。这是因为预解码器在对齐的 16 字节块上工作,并且它每个周期最多可以处理 5 条指令(请注意,某些来源错误地声称它每个周期可以处理 6 条指令)。您可以在 this paper.

中找到有关预解码器的更多详细信息,例如它如何处理跨越 16 字节边界的指令

如果将此轨迹与 g1b 的轨迹进行比较, 你可以看到nops之后的指令现在需要6个而不是5个周期来预解码,这是因为g1b中的几个指令比g1a中相应的指令长