【问题标题】:measure time to execute single instruction测量执行单条指令的时间
【发布时间】:2010-04-17 14:37:03
【问题描述】:

有没有办法使用 C 或汇编程序甚至 C# 来准确测量执行 ADD 指令所需的时间?

【问题讨论】:

  • 我相信有一篇关于这个漂浮在ACM土地上的文章,我会检查一下
  • 您的 CPU 的编程手册将指定执行特定指令所需的周期数。
  • 这是一个高度误导的指标,尼尔。现代 CPU 比单纯的循环计数要复杂得多。考虑ADD r32, r/m32 - 例如,如果您遇到内存(m32),则需要考虑缓存问题。 L1 与 L2 与实际主内存相比,执行速度存在巨大差异(更不用说虚拟内存!)。这甚至没有引入超标量架构或流水线或大量其他深入 CPU 深处的问题。
  • @Neil:对于大多数高性能 CPU 而言,它不会...
  • 现代 CPU 性能手册(如英特尔的优化手册)将分别列出吞吐量和延迟,所以是的,他们确实考虑了乱序执行,因为是的,没有“周期”数可以加起来(What considerations go into predicting latency for operations on modern superscalar processors and how can I calculate them by hand?)。 uops.info 每条指令的 reg,reg 与 memory-source 与 memory-destination 形式的偶数倍,带有 perf 计数器来测量 uop 计数以及它们可以在哪些执行端口上运行。 (假设 L1d 命中

标签: c assembly time instructions


【解决方案1】:

是的,有点,但它并非微不足道,并且产生的结果几乎毫无意义,至少在大多数合理的现代处理器上是如此。

在相对较慢的处理器上(例如,直到英特尔系列中的原始 Pentium,在大多数小型嵌入式处理器上仍然如此),您只需查看处理器的数据表,它就会(通常)告诉您时钟节拍数期待。快速、简单、轻松。

在现代台式机(例如 Pentium Pro 或更新版本)上,生活并不是几乎那么简单。这些 CPU 一次可以执行多条指令,只要它们之间没有任何依赖关系,它们就会乱序执行。这意味着单条指令所用时间的整个概念变得几乎毫无意义。执行一条指令所花费的时间可以而且将取决于它周围的指令。

也就是说,是的,如果您真的想要,您可以(通常 - 取决于处理器)测量一些东西,尽管它究竟意味着多少是一个相当大的问题。即使得到像这样接近没有意义而不是完全没有意义的结果也不是微不足道的。例如,在 Intel 或 AMD 芯片上,您可以使用 RDTSC 自行进行时序测量。不幸的是,这可以如上所述乱序执行。要获得有意义的结果,您需要用不能乱序执行的指令(“序列化指令”)围绕它。最常见的选择是 CPUID,因为它是少数可用于“用户模式”(即 ring 3)程序的序列化指令之一。不过,这本身就增加了一些扭曲:正如 Intel 所记录的,处理器执行 CPUID 的前几次,它可能需要比后续时间更长的时间。因此,他们建议您在使用它来序列化您的计时之前执行它三次次。因此,一般序列运行如下:

.align 16
CPUID
CPUID
CPUID
RDTSC
; sequence under test
Add eax, ebx
; end of sequence under test
CPUID
RDTSC

然后,您将其与执行相同操作但删除了测试序列的结果进行比较。当然,这会遗漏很多细节——至少你需要:

  1. 在每个 CPUID 之前正确设置寄存器
  2. 在第一个 RDTSC 之后保存 EAX:EDX 中的值
  3. 从第一个 RDTSC 中减去结果

还要注意我插入的“对齐”指令——指令对齐也会影响时序,尤其是在涉及循环的情况下。

【讨论】:

  • 我试图说那部分,关于测量的价值,但你说得更好。你会得到我的 +1!
  • 您从中获得的数字意味着什么,但它不是添加指令的“成本”。正如你所说,没有这样的事情;它具有与延迟分开的吞吐量。另见How many CPU cycles are needed for each assembly instruction?。您实际上应该通过创建一个循环来衡量该指令的吞吐量或延迟(无论您想衡量哪个),并为多次迭代计时。这就是 uops.info 所做的(使用 perf 计数器用于 uops 和核心周期,而不是 RDTSC ref 周期)。
【解决方案2】:

构造一个执行 1000 万次的循环,循环体中没有任何内容,然后计算时间。将这段时间作为循环所需的开销。

然后再次执行相同的循环,这次是在主体中测试代码。这个循环的时间,减去开销(来自空循环的情况)是由于您的被测代码重复 1000 万次的时间。所以,除以迭代次数。

显然,此方法需要针对迭代次数进行调整。如果你测量的东西很小,比如一条指令,你甚至可能想要运行超过 10 亿次迭代。如果它是一个重要的代码块,几十万可能就足够了。

在单个汇编指令的情况下,汇编程序可能是完成这项工作的正确工具,或者如果您熟悉内联汇编,则可能是 C。其他人已经发布了更优雅的解决方案,说明如何在没有重复的情况下进行测量,但重复技术始终可用,例如,嵌入式处理器没有其他人提到的良好时序指令。

但是请注意,在现代流水线处理器上,指令级并行性可能会混淆您的结果。由于一次不止一条指令在执行流水线中运行,因此一条给定指令的 N 次重复所花费的时间不再是单条的 N 倍。

【讨论】:

    【解决方案3】:

    好的,如果您使用 Windows、Linux、Unix、MacOS、AmigaOS 等操作系统以及其他所有其他操作系统,您的计算机上已经在后台运行大量进程,您将遇到的问题会影响表现。计算指令实际时间的唯一真正方法是拆卸主板并使用外部硬件测试每个组件。这取决于您是绝对想自己执行此操作,还是只是想知道您的处理器的典型版本实际运行的速度有多快。英特尔和摩托罗拉等公司在发布之前对他们的芯片进行了广泛的测试,这些结果可供公众使用。您需要做的就是询问他们,他们会寄给您一张免费的 CD-ROM(可能是 DVD - 废话),其中包含结果。您可以自己做,但请注意,尤其是英特尔处理器包含许多不再需要的冗余指令,更不用说必要了。这会占用你很多时间,但我绝对可以看到这样做的乐趣。 PS。如果纯粹是为了帮助您在个人项目中将自己机器的硬件推向理论上的最大值,那么您正在做的 Just Jeff 的回答非常适合在现实条件下生成整齐的指令速度平均值。

    【讨论】:

      【解决方案4】:

      不,但您可以根据加法指令所需的时钟周期数乘以 CPU 的时钟频率来计算它。 ADD 的不同类型的参数可能会导致更多或更少的周期,但对于给定的参数列表,指令总是需要相同数量的周期才能完成。

      也就是说,你为什么在乎?

      【讨论】:

      • 实际上计算机领域的 ADD 始终是 1 个寄存器加另一个。它永远不会同时有 7 个寄存器,所以这是一个谎言。
      • @WootMoo:这取决于所涉及的处理器和指令集。例如,VAX 有一个“AddP6”指令(“Add packed, 6 operand”)。 x86 也不符合您的描述。
      • @Woot4Moo:在谈论ADD“始终是”之前,您可能需要查阅您使用的处理器手册。 Intel 64 位(非安腾)芯片有 13 种不同的ADD 形式,可以将内存作为操作的目标,还有 5 种形式可以将内存作为源。实际上,唯一不支持的操作数组合是立即/立即和内存/内存。
      • 我每天都学到一些新东西,例如一个线程可以同时做多个加法。
      猜你喜欢
      • 1970-01-01
      • 2019-03-11
      • 2015-06-28
      • 1970-01-01
      • 1970-01-01
      • 2021-07-03
      相关资源
      最近更新 更多