【发布时间】:2021-01-08 15:49:42
【问题描述】:
由于现代处理器即使对于 ALU 也使用了繁重的流水线,因此可以在一个周期内执行多次独立算术运算,例如,可以在 4 个周期内执行四次加法运算,而不是一次加法的 4 * 延迟。
即使存在流水线,并且存在执行端口上的争用,我也想通过执行一些指令来实现周期精确的延迟,以使执行一系列指令的时间是可预测的。例如,如果指令 x 需要 2 个周期,并且不能流水线化,那么通过执行四次 x,我希望我可以延迟 8 个周期。
我知道这对于用户空间来说通常是不可能的,因为内核可以在执行序列之间进行干预,并可能导致比预期更多的延迟。但是,我假设这段代码在内核端执行,没有中断或没有噪音的隔离内核。
看了https://agner.org/optimize/instruction_tables.pdf之后,我发现CDQ指令不需要内存操作,它的延迟和倒数吞吐量需要1个周期。如果我理解正确,这意味着如果 CDQ 使用的端口没有争用,它可以在每个周期执行该指令。为了测试它,我将 CDQ 放在 RDTSC 定时器之间,并将核心频率设置为标称核心频率(希望它与 TSC 周期相同)。我还将两个进程固定到超线程内核;一个落在 while(1) 循环中,另一个执行 CDQ 指令。似乎添加一条指令会增加 1-2 个 TSC 周期。
但是,我担心需要大量 CDQ 指令来放置较大延迟(例如 10000 条可能需要至少 5000 条指令)的情况。如果代码大小太大而无法放入指令缓存并导致缓存未命中和 TLB 未命中,它可能会在我的延迟中引入一些抖动。我尝试使用简单的 for 循环来执行 CDQ 指令,但无法确定是否可以使用 for 循环(使用 jnz、cmp 和 sub 实现),因为它还可能在我的延迟中引入一些意想不到的噪音。谁能确认我是否可以以这种方式使用 CDQ 指令?
添加问题
用多条 CMC 指令测试后,似乎 10 条 CMC 指令增加了 10 个 TSC 周期。我使用下面的代码来测量执行 0、10、20、30、40、50 的时间
asm volatile(
"lfence\t\n"
"rdtsc\t\n"
"lfence\t\n"
"mov %%eax, %%esi\t\n"
"cmc\n\t" // CMC * 10, 20, 30, 40, ...
"rdtscp\n\t"
"lfence\t\n"
"sub %%esi, %%eax\t\n"
:"=a"(*res)
:
: "ecx","edx","esi", "r11"
);
printf("elapsed time:%d\n", *res);
我得到了 44-46、50-52、62-64、70-72、80-82、90-92(无 CMC、10CMC、20CMC、30CMC、40CMC、50CMC)。当每次执行 RDTSC 结果变化 0~2 个 TSC 周期时,似乎 1CMC 指令映射到 1 个周期延迟。除了第一次增加 10 个 CMC(不是增加 10 而是增加 6~8 个)外,大多数时候增加 10 个 CMC 指令会增加 (10 +-2) 个 TSC 循环。 但是,当我将 CMC 更改为我最初在问题中使用的 CDQ 指令时,似乎 1 个 CDQ 指令没有映射到 i9900K 机器中的 1 个周期。但是,当我查看 agner 的优化表时,似乎 CMC 和 CDQ 指令实际上并没有什么不同。是不是因为 CMC 指令背靠背之间没有依赖关系,但是 CDQ 指令之间确实存在依赖关系?
另外,如果我们认为可变延迟是由 rdtsc 引起的,而不是因为中断或其他争用问题。那么 CMC 指令似乎可以用于延迟 1 个核心周期,对吧?因为我将我的核心固定在 3.6GHz 时钟频率下运行,这假设是 i9900k 上的 TSC 时钟频率。我确实查看了引用的问题,但无法捕捉到确切的细节。..
【问题讨论】:
-
除了像非 mips PIC 微控制器和其他类似情况的芯片外,你不能在测试环境之外执行此操作。在模拟中,通过受控的获取或完全可重复的执行,您可以手动调整它,但在现实世界中,即使只有一块主板,但肯定是在 x86 世界中,不可能始终如一地做到这一点。不是花时间尝试的东西。您希望使用外围设备进行准确的计时,该外围设备旨在做您想做的事情。
-
在软件中,你通常可以做到“至少这么长”,我可以让它不少于这个时间,但它可能会更长。这对于使用各种外围设备或其他东西很有用,但对于不超过和不低于时间的准确度来说不是。
-
然后扔进一个操作系统,一切都变得更糟了。
-
如果出于某种原因您正在寻找一个绝对依赖于自身的单字节指令(将执行限制为 1 个/时钟的延迟瓶颈),
cmc(切换 CF)可能会成功。但这对于您延迟循环的总体目标几乎没有用处。可能与lfence结合使用,但这可能会比您想要的延迟更长的时间,具体取决于任何现有的飞行指令需要多长时间。例如缓存未命中加载。 -
@JaehyukLee:对于“极小而准确”的延迟,唯一的选择是放弃,然后重新评估您错误地认为自己想要它的原因。对于内核代码;对于精度稍低的更长延迟,您可以考虑在“TSC 截止时间模式”中使用本地 APIC 计时器(可能对 IRQ 退出时间进行一些调整)和/或与性能监控计数器类似。
标签: assembly x86 delay microbenchmark timedelay