现代CPU到底有多“快”?

时间:2009-01-11 15:53:27

标签: assembly cpu-speed

当我以前编程嵌入式系统和早期的8/16位PC(6502,68K,8086)时,我非常好地处理了每条指令执行的时间(以纳秒或微秒为单位)。根据系列的不同,一个(或四个)周期等同于一个“内存提取”,并且无需担心缓存,您可以根据所涉及的内存访问次数猜测时序。

但是对于现代CPU来说,我很困惑。我知道它们的速度要快得多,但我也知道,如果不知道每条指令需要多少个时钟周期,标题千兆赫的速度就无济于事。

因此,任何人都可以为两个示例指令提供一些时序,比如说(2)Core 2 Duo。最好和最坏的情况(假设缓存中没有任何内容/缓存中的所有内容)都很有用。

指令#1:将一个32位寄存器添加到秒。

指令#2:将32位值从寄存器移到内存中。

编辑:我之所以这样做是为了尝试制定一个“经验法则”,这样我就可以查看简单的代码并粗略估算到最近的顺序大小。

编辑#2:很多答案都有趣点,但没有人(还)已经记下了及时测量的数字。我很欣赏这个问题存在“并发症”,但是来吧:如果我们可以估计number of piano-tuners in NYC,我们应该能够估算代码运行时间......

采取以下(哑)代码:

int32 sum = frigged_value();

// start timing
 for (int i = 0 ; i < 10000; i++)
 {
   for (int j = 0 ; j < 10000; j++)
   {
     sum += (i * j)
   }
   sum = sum / 1000;
 }

// end timing

我们如何估算运行需要多长时间... 1飞秒? 1 gigayear?

15 个答案:

答案 0 :(得分:40)

你提到的现代处理器如Core 2 Duo都是超标量流水线。它们每个核心有多个执行单元,实际上每个核心一次处理多个指令;这是超标量部分。流水线部分意味着从读取和“发出”指令到完成执行时存在延迟,并且该时间根据该指令与同时移动通过其他执行单元的其他指令之间的依赖性而变化。因此,实际上,任何给定指令的时序取决于它周围的内容和依赖的内容。这意味着给定指令具有基于许多因素的最佳情况和最差情况执行时间。由于多个执行单元实际上可以有多个指令完成每个核心时钟的执行,但是如果管道必须停止等待管道中的内存或依赖性,有时在完成之间会有几个时钟。

以上所有内容仅仅来自CPU核心本身。然后,您与缓存进行交互,并与其他核心争用带宽。 CPU的总线接口单元处理将指令和数据输入到核心,并通过高速缓存将结果从核心返回到内存。

用一粒盐做出粗略的数量级经验法则:

  • 注册到注册操作需要执行1 核心时钟。这应该是保守的,特别是因为更多这些按顺序出现。
  • 与内存相关的加载和存储操作需要执行1 内存总线时钟。这应该是非常保守的。具有较高的缓存命中率,它将更像2 CPU总线时钟,这是CPU核心和缓存之间总线的时钟速率,但不一定是核心的时钟。

答案 1 :(得分:14)

几乎不可能以对您有用的方式提供准确的时序信息。

以下概念影响指令时序;有些可能随时变化:

  • 微操作分解
  • 操作流水线
  • 超标量执行
  • 乱序执行
  • SMT / SMP执行
  • 浮点模式
  • 分支预测/预取
  • 缓存延迟
  • 内存延迟
  • 时钟速度限制

如果您需要对上述概念有任何进一步的解释,请查阅有关现代计算机体系结构的书籍。

衡量代码速度的最佳方法是(惊喜!)衡量运行相同工作负载的代码的速度,以及“在现实世界中”时所处的相同条件。

答案 2 :(得分:8)

使用主要基于英特尔奔腾架构的描述来缩短一个非常长的故事:

  • 处理器有许多“执行单元”,可以执行不同类型的“微操作”;说明可以分为几个微操作
  • 不同的执行单元基本上并行运行
  • 每个微操作器将相应的执行单元绑定一定数量的时钟周期,因此同时没有其他指令可以使用该执行单元: “浮点加”可以将“FP执行”单元占用2个时钟周期
  • 执行单元按“端口”分组,每个时钟周期,一个新的微操作可以发送到每个端口(假设相关执行单元在那一刻是空闲的);一些单位也可以在周期中途发送“额外操作”;所以每个时钟周期,一定数量的操作可以启动执行;
  • 处理器可以重新排序微操作,其中不会破坏依赖关系(或者仍然可以重建结果)以利用哪些执行单元在给定时刻是空闲的
  • 所以说明可以并行执行,但是任何一次执行哪些部分指令都是相当复杂的情况
  • 因此,给定指令的总时间取决于必须“等待”必要的执行单元变为可用的时间,这些操作在给定单元上运行的实际时间,以及“所需的额外时间”结果“

由于指令的时间取决于周围的指令,实际上,通常最好计算代表性的代码而不是尝试并担心个别指令。但是:

  • 英特尔(可能是其他制造商)发布了指示吞吐量延迟时间的列表
  • 吞吐量是相关执行单元实际需要的时钟周期数
  • 延迟是一个指令开始执行时所需的“最坏情况”时钟周期数,在执行结果可用作另一条指令的输入之前

因此,例如,如果,例如,浮点加法和乘法指令的吞吐量均为2,延迟为5(实际上,为了将其加倍,我认为),这意味着向自身添加寄存器或将它自身乘以可能需要两个时钟周期(因为没有其他相关值),而添加它之前的乘法结果将需要或稍微少于2 + 5个时钟周期,具体取决于您开始/结束的位置时机,以及各种其他事情。 (在某些时钟周期中,可能会发生另一个加/复运算,因此可以说你实际上将多少个周期归因于单个add / mutliply指令......)

哦,就像一个具体的例子。用于遵循Java代码

public void runTest(double[] data, double randomVal) {
  for (int i = data.length-1; i >= 0; i--) {
    data[i] = data[i] + randomVal;
  }
}

Hotspot 1.6.12 JIT-将内循环序列编译为以下英特尔代码,包括数组中每个位置的加载 - 存储(在这种情况下,'randomVal'保存在XMM0a中):

  0b3     MOVSD  XMM1a,[EBP + #16]
  0b8     ADDSD  XMM1a,XMM0a
  0bc     MOVSD  [EBP + #16],XMM1a
  0c1     MOVSD  XMM1a,[EBP + #8]
  0c6     ADDSD  XMM1a,XMM0a
  0ca     MOVSD  [EBP + #8],XMM1a
  ...

每组加载 - 添加商店似乎需要5个时钟周期

答案 3 :(得分:7)

这不是那么简单。两条指令的时间安排不会帮助您更好地衡量更多指令的性能。这是因为现代处理器可以并行执行许多操作,并且具有大型缓存,因此“将值移动到内存”的时间与指令的执行完全相同。

因此,最佳情况为零(与其他指令并行执行时)。但是这对你有什么帮助?

This网页显示了一些基准测试,包括一些%MIPS / MHz结果。如您所见,在许多基准测试中,每个时钟周期执行多条指令。图表还显示了缓存大小和内存速度的影响。

答案 4 :(得分:7)

现代处理器做得更加棘手。

无序执行。如果可以在不影响正确行为的情况下执行此操作,则处理器可以按照与程序中列出的顺序不同的顺序执行指令。这可以隐藏长时间运行指令的延迟。

注册重命名。处理器通常在其指令集中具有比可寻址寄存器更多的物理寄存器(所谓的“架构”寄存器)。这可以是为了向后兼容,或者只是为了实现有效的指令编码。当程序运行时,处理器将“重命名”它使用的架构寄存器,以便任何物理寄存器都是空闲的。这使处理器能够实现比原始程序中更多的并行性。

例如,如果您在EAX和ECX上有很长的操作序列,然后是将EAX和ECX重新初始化为新值并执行另一个长序列操作的指令,则处理器可以为这两个任务使用不同的物理寄存器,并行执行它们。

英特尔P6微体系结构同时执行无序执行和寄存器重命名。 Core 2架构是P6的最新衍生产品。

要真正回答您的问题 - 面对所有这些架构优化,您基本上无法手动确定性能。

答案 5 :(得分:7)

你要求的那种预测是没有希望的。

如果你想要一个经验法则,这里有一些经验法则:

  • 在从二级缓存中获取单词所需的时间内,处理器可以执行至少10条指令。所以担心内存访问,而不是指令计数---寄存器中的计算几乎是免费的。

  • 在从RAM中获取字数所需的时间内,处理器可以执行数千条指令(此数字会根据硬件的详细信息而变化几个数量级)。确保只在冷缓存上发生这种情况;否则没有其他问题。

  • 如果您在x86 CPU上运行,则没有足够的寄存器。尽量不要在代码中包含超过5个实时变量。或者更好的是,转移到AMD64(x86_64)并将寄存器数量增加一倍。有16个寄存器和寄存器中传递的参数,您可以放弃担心寄存器。

曾经有一段时间我会问建筑师我应该使用哪些经验法则来预测编译器生成的代码的成本。我已经停止了,因为上次我收到一个有用的答案是在1999年。(答案是“确保你的循环适合重新排序缓冲区”。所有那些知道重新排序缓冲区的人现在可以举手了。如果您可以在当前使用的任何计算机上发现重新排序缓冲区的大小,请指出。)

答案 6 :(得分:5)

这只回答了你的部分问题,但我发现这张来自维基百科的locality of reference表很有帮助。它使用大约2006次来描述内存层次结构的不同级别的访问速度和内存量:

  • CPU寄存器(8-32个寄存器) - 立即访问(0-1个时钟周期)
  • L1 CPU缓存(32 KiB至128 KiB) - 快速访问(3个时钟周期)
  • L2 CPU缓存(128 KiB至12 MiB) - 访问速度稍慢(10个时钟周期)
  • 主要物理内存(RAM)(256 MiB至4 GiB) - 访问速度慢(100个时钟周期)
  • 磁盘(文件系统)(1 GiB到1 TiB) - 非常慢(10,000,000个时钟周期)
  • 远程内存(如其他计算机或Internet)(实际上无限制) - 速度不同

答案 7 :(得分:4)

您可以下载英特尔64和IA-32手册here

但你真正需要的是来自Agner Fog的东西。

他有很多额外的信息,例如他的手册"Instruction tables: Lists of instruction latencies, throughputs and micro-operation breakdowns for Intel and AMD CPUs"

或用于计算时钟周期的测试程序(他使用时间戳计数器)。

答案 8 :(得分:4)

这个帖子已经有很多好的答案了,但到目前为止还没有提到一个主题: branch misprediction

因为所有现代处理器都是流水线的,当指令解码器遇到像“如果相等”的指令时,它不知道指令将以哪种方式跳转,因此它只是猜测。然后它继续根据该猜测将指令输入管道。如果它做出了正确的预测,则跳转指令的吞吐量和延迟基本上为零。如果猜错了,同一跳转指令的吞吐量和延迟可能是50或100个周期。

请注意,相同的指令在第一次循环执行时可以具有“零成本”,并且下次执行相同指令时会产生巨大的成本!

答案 9 :(得分:3)

您需要的只是相应的CPU手册。 AMD和英特尔都在其网站上提供了PDF文件,描述了每条指令的延迟。

请记住现代CPU的复杂性。它们不会一次执行一条指令,它们每个周期可以加载3-4条指令,并且几乎所有指令都是流水线的,因此当下一条指令加载时,当前的指令无法完成。它还重新排序指令以允许更有效的调度。 现代CPU一次只能轻松执行50条指令。

所以你问的是错误的问题。单个指令所需的时间根据您测量的方式和时间而有很大差异。它取决于指令解码器的繁忙程度,分支预测器,调度以及调度其他指令的位置,以及缓存等简单问题。

答案 10 :(得分:3)

一个有趣的quote from Alan Kay in 2004

  

另外,为了给你一个有趣的基准 - 大致相同的系统,大致以同样的方式优化,从1979年施乐PARC的基准测试今天运行速度只快了50倍。摩尔定律给了我们当时的改善时间在40,000到60,000之间。因此,糟糕的CPU架构已经损失了大约1,000倍的效率。

暗示似乎是CPU性能增强似乎集中在对我们真正编写的软件影响相对较小的领域。

答案 11 :(得分:2)

Doug已经指出,最好的情况是零(超标量处理器,多个执行单元,已经在L1缓存中的数据)。

最坏的情况是几毫秒(当操作系统处理页面故障并且必须从磁盘获取数据/指令时)。排除磁盘/交换仍取决于您是否拥有NUMA计算机,它具有哪种拓扑,数据所在的内存节点,是否存在来自另一个CPU的并发访问(总线锁定和缓存同步协议)等。 / p>

答案 12 :(得分:2)

我建议下载AMD software optimization guide

答案 13 :(得分:0)

我认为最糟糕的情况并不局限于某些平台。当您有多个核心和处理器竞争相同的位置或相邻的内存位置时,您可以看到各种性能下降。高速缓存行必须从处理器移动到处理器。我还没有看到现代平台上内存操作的最坏情况编号。

答案 14 :(得分:0)

花了将近11年,但我有一个估计。您的循环约为10 ops * 1亿次迭代,因此约为10亿ops。在2.3 GHz的计算机上,我估计约为0.4秒。当我测试它时,我实际上得到了1.2秒。所以它在一个数量级内。

只需采用您的核心频率,估算ops并除以即可。这给出了一个非常粗略的估计,并且每当我进行经验测试时,我的水平就不会比它高出一个数量级。只要确保您的op估算值是合理的即可。