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

时间:2016-10-09 21:54:22

标签: performance timer x86 cpu-architecture

是否存在需要固定时间的x64指令,无论缓存,分支预测器等微架构状态如何?

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

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

1 个答案:

答案 0 :(得分:1)

x86 ISA文档不保证什么需要一定的周期。 ISA允许Transmeta's Crusoe等JIT编译的x86指令到内部VLIW指令集。可以想象,可以在相邻指令之间进行优化。

你能做的最好的事情是写一些可以在尽可能多的已知微体系结构上运行的东西。我不知道任何像Transmeta那样“奇怪”的x86-64微体系结构,只有英特尔和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说VIA Nano3000的每分钟采用分支吞吐量为1,所以这个循环只能在那里每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 insn和jcc。

使用add / sub而不是dec / inc可以避免对Pentium 4产生部分标志错误依赖。(尽管我认为这不会是一个问题。)

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

是的,Prescott P4是一个x86-64 CPU,所以如果我们需要一个通用的答案,我们就不能完全忽略P4。