x86-64 上的确定性执行时间

Deterministic execution time on x86-64

是否有 x64 指令需要固定的时间量,而不管微架构状态,如缓存、分支预测器等?

例如,如果一个假设的加法或递增指令总是需要 n 个周期,那么我可以通过多次执行该加法指令在我的程序中实现一个定时器。也许带有寄存器操作数的增量指令可能会起作用,但我不清楚英特尔的规范是否保证它需要确定的周期数。请注意,我对当前时间不感兴趣,而只对需要固定周期数的原始/指令序列感兴趣。

假设我有办法强制原子执行,即在计时器执行期间没有上下文切换,即只有我的程序到达 运行。 在相关说明中,我也无法使用系统服务来跟踪时间,因为我在一个设置中工作,其中我的程序是一个用户级程序 运行ning 在不受信任的 OS.

x86 ISA 文档不保证任何需要一定数量周期的东西。 ISA 允许像 Transmeta's Crusoe 这样的东西,即 JIT 将 x86 指令编译为内部 VLIW 指令集。可以想象它可以在相邻指令之间进行优化。

你能做的最好的事情就是写一些可以在尽可能多的已知微体系结构上工作的东西。我不知道有任何像 Transmeta 这样的 "weird" x86-64 微体系结构,只有像 Intel 和 AMD 使用的通常的超标量解码到 uops 设计。

像 ADD 这样的简单整数 ALU 指令几乎都是 1c 延迟,不接触内存的微小循环几乎完全不受影响,而且非常可预测。如果他们 运行 多次迭代,他们也几乎完全不受周围代码对无序核心的影响的任何影响,并且可以从定时器中断等中断中快速恢复。


在几乎每个英特尔微体系结构上,此循环将 运行 每个时钟迭代一次:

mov   ecx, 1234567   ; or use a 64-bit register for higher counts.

ALIGN 16
.loop:
 sub  ecx, 1      ; not dec because of Pentium 4.
 jnz  .loop

Agner Fog's microarch guide and instruction tables 表示威盛 Nano3000 的分支吞吐量为每 3 个周期一个,因此该循环只会 运行 每 3 个时钟迭代一次。 AMD Bulldozer 系列和 Jaguar 同样具有每 2 个时钟一个 JCC 的最大吞吐量。

另请参阅 标签 wiki 中的其他性能链接。

如果您想要一个更节能的循环,您可以在循环中使用 PAUSE,但它在 Skylake 上等待约 100 个周期,高于之前微架构的约 5 个周期。 (您可以对不涉及内存的更复杂循环进行周期精确预测,但这取决于微架构细节。)


您可以通过在每次迭代中创建更长的依赖链来创建一个更可靠的循环,该循环不太可能在不同的 CPU 上出现不同的瓶颈。由于每条指令都依赖于前一条指令,因此每个周期仍然只能 运行 一条指令(不计算分支),每个周期的分支数量很大。

# one add/sub per clock, limited by latency
# should run one iteration per 6 cycles on every CPU listed in Agner Fog's tables
# And should be the same on all future CPUs unless they do magic inter-instruction optimizations.
# Or it could be slower on CPUs that always have a bubble on taken branches, but it seems unlikely anyone would design one.
ALIGN 16
.loop:
 add  ecx, 1
 sub  ecx, 1   ; net result ecx+0
 add  ecx, 1
 sub  ecx, 1   ; net result ecx+0
 add  ecx, 1
 sub  ecx, 2   ; net result ecx-1
 jnz  .loop

这样展开可以保证前端效果不是瓶颈。它给前端解码器足够的时间在下一个分支之前排队 6 add/sub insns 和 jcc。

使用 add/sub 而不是 dec/inc 避免了对 Pentium 4 的部分标志错误依赖。(尽管我认为这无论如何都不是问题。)

Pentium4 的双时钟 ALU 每个时钟可以 运行 两个 ADD,但延迟仍然是一个周期。也就是说,显然它不能在内部转发结果来咀嚼这个依赖链的速度是任何其他 CPU.

的两倍

是的,Prescott P4 是 x86-64 CPU,因此如果我们需要通用答案,我们不能完全忽略 P4。