NASM中的RDTSCP始终返回相同的值

时间:2019-02-10 21:43:16

标签: optimization nasm x86-64 windows64 rdtsc

我正在NASM中使用RDTSC和RDTSCP来测量机器周期,以获得各种汇编语言指令,以帮助优化。

我阅读了位于英特尔的Gabriele Paoloni(2010年9月)和其他Web资源(其中大多数是C语言中的示例)的“如何在Intel IA-32和IA-64指令集体系结构上对代码执行时间进行基准测试”。

使用下面的代码(从C转换),我测试了各种指令,但是RDTSCP在RDX中始终返回零,在RAX中始终返回7。我首先认为7是周期数,但显然并非所有指令都需要7个周期。

rdtsc
cpuid
addsd xmm14,xmm1 ; Instruction to time
rdtscp
cpuid

这将返回7,这并不奇怪,因为在某些体系结构上,添加了7个周期(包括延迟)。可以(根据某些情况)将前两个指令颠倒过来,先是cpuid,然后是rdtsc,但这在这里没有什么区别。

当我将指令更改为2周期指令时:

rdtsc
cpuid
add rcx,rdx ; Instruction to time
rdtscp
cpuid

这还会在rax中返回7,在rdx中返回零。

所以我的问题是:

  1. 如何访问和解释RDX:RAX中返回的值?

  2. 为什么RDX总是返回零,它应该返回什么?

更新:

如果我将代码更改为此:

cpuid
rdtsc
mov [start_time],rax
addsd xmm14,xmm1 ; INSTRUCTION
rdtscp
mov [end_time],rax
cpuid
mov rax,[end_time]
mov rdx,[start_time]
sub rax,rdx

我的rax达到64,但这听起来像是循环太多。

1 个答案:

答案 0 :(得分:6)

您的第一个代码(导致标题问题)有问题,因为它用EAX,EBX,ECX和EDX中的rdtsc结果和rdtscp结果覆盖了cpuidlfence结果

使用cpuid代替lfence ;自从永久性地在Intel上启用了Spectre缓解功能以来,AMD便开始使用rdtsc来序列化指令流,从而通过cpuid完成您想要的操作。


请记住,RDTSC只是计算参考周期,而不是核心时钟周期。 Get CPU cycle count?,有关RDTSC的更多信息。

您的测量间隔内没有lfencerdtscp。但是您确实在测量间隔中本身就有rdtscp。背靠背%rep并不快,如果您在不预热CPU的情况下运行, 64个参考周期听起来完全合理。空闲时钟速度通常比参考周期慢很多; 1个参考周期等于或接近“贴纸”频率,例如英特尔CPU上的最大非涡轮持续频率。例如在“ 4GHz” Skylake CPU上为4008 MHz。


这不是您计时单个指令的方式

重要的是在另一个指令可以使用结果之前的延迟,而不是直到它从无序后端完全退出之前的延迟。 RDTSC对于计时相对变化可能很有用一次加载或一条存储指令要花多长时间,但是开销意味着您不会获得良好的绝对时间。

不过,您可以尝试减少测量开销。例如clflush to invalidate cache line via C function。并参见后续内容:Using time stamp counter and clock_gettime for cache missMemory latency measurement with time stamp counter


这是我通常用来描述短块指令的延迟或吞吐量(以及uops融合和非融合域)的方法。调整使用它的方式来限制延迟(如此处所示),如果您只想测试吞吐量,则不要调整。例如在pxor xmm3, xmm3块中使用足够多的不同寄存器来隐藏等待时间,或者在较短的块之后使用global _start _start: mov ecx, 1000000000 ; linux static executables start with XMM0..15 already zeroed align 32 ; just for good measure to avoid uop-cache effects .loop: ;; LOOP BODY, put whatever you want to time in here times 4 addsd xmm4, xmm3 dec ecx jnz .loop mov eax, 231 xor edi, edi syscall ; x86-64 Linux sys_exit_group(0) 断开依赖关系链,让无序的exec发挥其魔力。 (只要您不在前端遇到瓶颈。)

您可能想要使用NASM的smartalign软件包或YASM,以避免将ALIGN指令的单字节NOP指令塞到墙上。即使在始终支持long-NOP的64位模式下,NASM默认还是真正愚蠢的NOP。

perf stat

使用类似这种单行代码的方式运行它,将其链接到一个静态可执行文件中,并使用asm-link对其进行配置,您可以在每次更改源代码时向上箭头并重新运行 strong>:

((实际上,我将nasm + ld +可选的反汇编放入了一个名为%if的shell脚本中,以节省我不进行概要分析时的键入。反汇编可确保循环中的内容是您的 meant 进行配置,尤其是在代码中包含t=testloop; nasm -felf64 -g "$t.asm" && ld "$t.o" -o "$t" && objdump -drwC -Mintel "$t" && taskset -c 3 perf stat -etask-clock,context-switches,cpu-migrations,page-faults,cycles,branches,instructions,uops_issued.any,uops_executed.thread -r4 ./"$t" 东西的情况下。如果您想在脑海中测试理论时回滚,它也位于配置文件之前的终端上。)

perf

i7-6700k在3.9GHz时的结果(当前 Performance counter stats for './testloop' (4 runs): 4,106.09 msec task-clock # 1.000 CPUs utilized ( +- 0.01% ) 17 context-switches # 4.080 M/sec ( +- 5.65% ) 0 cpu-migrations # 0.000 K/sec 2 page-faults # 0.487 M/sec 16,012,778,144 cycles # 3900323.504 GHz ( +- 0.01% ) 1,001,537,894 branches # 243950284.862 M/sec ( +- 0.00% ) 6,008,071,198 instructions # 0.38 insn per cycle ( +- 0.00% ) 5,013,366,769 uops_issued.any # 1221134275.667 M/sec ( +- 0.01% ) 5,013,217,655 uops_executed.thread # 1221097955.182 M/sec ( +- 0.01% ) 4.106283 +- 0.000536 seconds time elapsed ( +- 0.01% ) 的第二列具有单位缩放显示错误。已在上游修复,但Arch Linux尚未更新。) :

addsd

在我的i7-6700k(Skylake)上,addsd具有4个周期的延迟,吞吐率为0.5c。 (即,如果延迟不是瓶颈,则每个时钟2个)。参见https://agner.org/optimize/https://uops.info/http://instlatx64.atw.hu/

每个分支16个周期=每个链16个周期,每个周期4 addsd = :u的4个周期等待时间,即使如此,Agner Fog的4个周期的测量结果也要好于100分之一测试,包括一点启动开销和中断开销。

选择不同的计数器进行记录。向性能中添加instructions:u之类的cycles:u(甚至instructions:u)仅会计算用户空间指令,不包括在中断处理程序中运行的指令。我通常不这样做,因此我可以将其作为挂钟时间说明的一部分。但是,如果这样做,-r4可以非常匹配{em> 和{{1}}。

{{1}}将其运行4次并取平均值,这对于查看是否存在大量的运行差异很有用,而不是仅从ECX中较高的值中获得一个平均值。

调整您的初始ECX值使总时间大约为0.1到1秒,这通常是足够的,尤其是如果您的CPU非常迅速地加速到最大加速(例如,Skylake具有硬件P状态和相当激进的energy_performance_preference)。或在禁用了涡轮的情况下最大化非涡轮增压。

但这是核心时钟周期,而不是参考周期,因此无论CPU频率如何变化,其结果都相同。 (+-过渡期间停止时钟会产生一些噪音。)