Zac*_*ack 6 optimization performance x86 intel cpu-architecture
在Intel i7处理器,是有某种瓶颈访问的“原始”寄存器(中eax,ebx,ecx,edx)是不存在的“新”寄存器(r8d,r9d,等)?我正在计时一些代码,如果尝试并行运行三个添加指令,则只要三个添加指令中只有两个引用“原始”寄存器(例如,我使用eax,ebx和r9d)。如果我尝试使用三个“原始”寄存器,则CPI最高约为0.4。我在i7-3770和i7-4790上都观察到了。
我试图为我的计算机体系结构课程开发一个新的(希望很有趣)实验室。他们的目标是让他们在Intel i7处理器上计时一些汇编代码,并观察(a)处理器的吞吐量和(b)数据依赖性的后果。
当我尝试编写一些显示平均CPI为0.33的汇编代码(即,表明CPU可以保持每个周期3条指令的吞吐量)时,我发现只有在最多访问3条指令中的两条时才有可能“原始”通用寄存器。
这是实验的基本概述:用于rdtsc对几千条指令进行时间分段,然后将“周期数”与定时指令的数量进行对比,以估算吞吐量。例如,在循环内运行此代码
mov $0, %eax
cpuid
rdtsc
movl %eax, %r12d
addl $1, %eax
addl $1, %eax
# the line above is copied "n" times
# (I use a ruby script to generate this part of the assembly)
addl $1, %eax
rdtsc
subl %r12d, %eax
Run Code Online (Sandbox Code Playgroud)
允许我们报告执行一系列n addl指令所需的时间(以参考周期为单位)。(以上代码段是一个较长的程序的一部分,该程序重复多次测量,抛出前几千次试验,并报告最低和/或最常见的结果。)
当我对单个寄存器进行一系列加法运算时,我得到了预期的结果:
instructions elapsed reference ref cycles estimated actual actual cycles
between rdtsc cycles per instruction cycles per instruction
200 145 0.72 169 0.84
300 220 0.73 256 0.85
400 314 0.79 365 0.91
500 408 0.82 474 0.95
600 483 0.81 562 0.94
700 577 0.82 671 0.96
800 652 0.81 758 0.95
900 746 0.83 867 0.96
1000 840 0.84 977 0.98
1100 915 0.83 1064 0.97
1200 1009 0.84 1173 0.98
........................................................................
3500 3019 0.86 3510 1.00
3600 3094 0.86 3598 1.00
3700 3188 0.86 3707 1.00
3800 3282 0.86 3816 1.00
3900 3357 0.86 3903 1.00
4000 3451 0.86 4013 1.00
Run Code Online (Sandbox Code Playgroud)
将参考周期转换为(估计的)实际周期后,处理器平均每个周期大约一条指令。这是有道理的,因为每个定时指令都依赖于前一条指令,从而阻止了并行执行。注意,我们在结束之前不会发出序列化指令rdtsc。结果,当我们“停止”计时器时,最后几十个计时指令尚未完成。因此,该表的前几行的CPI人为地降低了。随着定时指令数量的增加,该“计数不足”的影响限制为零。
如果我们修改定时代码以在eax和的添加之间交替ebx,我们还将获得预期的结果:CPI趋于0.5:
mov $0, %eax
cpuid
rdtsc
movl %eax, %r12d
addl $1, %eax
addl $1, %ebx
addl $1, %eax
addl $1, %ebx
# the pair of lines above are copied until there are `n` lines total being timed
addl $1, %eax
addl $1, %ebx
rdtsc
subl %r12d, %eax
instructions elapsed reference ref cycles estimated actual actual cycles
between rdtsc cycles per instruction cycles per instruction
1000 432 0.43 502 0.50
1200 510 0.42 593 0.49
1400 601 0.43 699 0.50
1600 695 0.43 808 0.51
1800 773 0.43 899 0.50
2000 864 0.43 1005 0.50
2200 955 0.43 1110 0.50
Run Code Online (Sandbox Code Playgroud)
当我尝试运行添加到eax,ebx并ecx并行运行时,CPI高于预期的.33:
mov $0, %eax
cpuid
rdtsc
movl %eax, %r12d
addl $1, %eax
addl $1, %ebx
addl $1, %ecx
addl $1, %eax
addl $1, %ebx
addl $1, %ecx
# the group of lines above are copied until there are `n` lines total being timed
addl $1, %eax
addl $1, %ebx
addl $1, %ecx
rdtsc
subl %r12d, %eax
instructions elapsed reference ref cycles estimated actual actual cycles
between rdtsc cycles per instruction cycles per instruction
1200 408 0.34 474 0.40
1500 492 0.33 572 0.38
1800 595 0.33 692 0.38
2100 698 0.33 812 0.39
2400 782 0.33 909 0.38
2700 885 0.33 1029 0.38
3000 988 0.33 1149 0.38
3300 1091 0.33 1269 0.38
3600 1178 0.33 1370 0.38
Run Code Online (Sandbox Code Playgroud)
但是,我得到预期的结果,如果我使用r9d,r10d以及r11d:
instructions elapsed reference ref cycles estimated actual actual cycles
between rdtsc cycles per instruction cycles per instruction
1200 350 0.29 407 0.34
1500 444 0.30 516 0.34
1800 519 0.29 603 0.34
2100 613 0.29 713 0.34
2400 707 0.29 822 0.34
2700 782 0.29 909 0.34
3000 876 0.29 1019 0.34
Run Code Online (Sandbox Code Playgroud)
事实上,我得到预期的结果,只要在最多两三个寄存器的来自集eax,ebx,ecx,和edx。这是为什么?知道瓶颈是问题,解码,寄存器重命名还是退出?
我在i7-3770和i7-4790上都观察到了这种行为。物有所值:Ryzen 7和i5-6500始终具有0.38至.40的CPI,而与使用的寄存器无关。
对于那些好奇的人,这是我使用的代码的模板:
.file "timestamp_shell.c"
.text
.section .rodata
.align 8
.LC0:
.string "%8d; Start %10u; Stop %10u; Difference %5d\n"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
pushq %r13
pushq %r12
pushq %rbx
subq $8, %rsp
.cfi_offset 13, -24
.cfi_offset 12, -32
.cfi_offset 3, -40
movl $100, %r12d
movl $200, %r13d
movl $-1, %r8d
movl $0, %r8d
jmp .L2
.L3:
mov $0, %eax
cpuid
rdtsc
movl %eax, %r12d
movl $0, %eax
# I use a perl script to copy the lines marked with #@ until there
# is the desired number of instructions between the calls to rdstc
#@ addl $1, %eax
#@ addl $1, %r10d
#@ addl $1, %ecx
rdtsc
subl %r12d, %eax
movl %eax, %r8d
movl %r13d, %ecx
movl %r12d, %edx
movl %r8d, %esi
leaq .LC0(%rip), %rdi
movl $0, %eax
call printf@PLT
addl $1, %r8d
.L2:
cmpl $999999, %r8d
jle .L3
movl $199, %eax
addq $8, %rsp
popq %rbx
popq %r12
popq %r13
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (GNU) 8.2.1 20181127"
.section .note.GNU-stack,"",@progbits
Run Code Online (Sandbox Code Playgroud)