47

我在 SO 上看到了这篇文章,其中包含用于获取最新 CPU 周期计数的 C 代码:

C/C++ Linux x86_64 中基于 CPU 周期计数的分析

有没有办法可以在 C++ 中使用此代码(欢迎使用 Windows 和 linux 解决方案)?虽然用 C 编写(并且 C 是 C++ 的子集),但我不太确定这段代码是否可以在 C++ 项目中工作,如果不能,如何翻译它?

我正在使用 x86-64

编辑2:

找到了这个函数,但是VS2010无法识别汇编程序。我需要包括任何东西吗?uint64_t(我相信我必须换成long longWindows ......?)

static inline uint64_t get_cycles()
{
  uint64_t t;
  __asm volatile ("rdtsc" : "=A"(t));
  return t;
}

编辑3:

从上面的代码我得到错误:

“错误 C2400:‘操作码’中的内联汇编语法错误;找到‘数据类型’”

有人可以帮忙吗?

4

5 回答 5

77

从 GCC 4.5 及更高版本开始,MSVC__rdtsc()和 GCC 都支持内部函数。

但是需要的包含是不同的:

#ifdef _WIN32
#include <intrin.h>
#else
#include <x86intrin.h>
#endif

这是 GCC 4.5 之前的原始答案。

直接从我的一个项目中拉出来:

#include <stdint.h>

//  Windows
#ifdef _WIN32

#include <intrin.h>
uint64_t rdtsc(){
    return __rdtsc();
}

//  Linux/GCC
#else

uint64_t rdtsc(){
    unsigned int lo,hi;
    __asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi));
    return ((uint64_t)hi << 32) | lo;
}

#endif

这个GNU C Extended asm告诉编译器:

  • volatile:输出不是输入的纯函数(因此每次都必须重新运行,而不是重用旧结果)。
  • "=a"(lo)"=d"(hi):输出操作数是固定寄存器:EAX 和 EDX。(x86 机器限制)。x86rdtsc指令将其 64 位结果放入 EDX:EAX,因此让编译器选择一个输出"=r"是行不通的:没有办法要求 CPU 将结果发送到其他任何地方。
  • ((uint64_t)hi << 32) | lo- 将两个 32 位一半零扩展为 64 位(因为 lo 和 hi 是unsigned),并将它们逻辑移位 + OR 到一个 64 位 C 变量中。在 32 位代码中,这只是一种重新解释;这些值仍然只保留在一对 32 位寄存器中。在 64 位代码中,您通常会得到一个实际的 shift + OR asm 指令,除非高半部分被优化掉。

unsigned long(编者注:如果你使用而不是 ,这可能会更有效unsigned int。然后编译器会知道它lo已经被零扩展为 RAX。它不会知道上半部分是零,所以如果它想要,它是等价|+以不同的方式合并。就让优化器做好工作而言,内在函数理论上应该为您提供两全其美的效果。)

https://gcc.gnu.org/wiki/DontUseInlineAsm如果可以避免的话。但是,如果您需要了解使用内联 asm 的旧代码以便您可以用内在函数重写它,希望本节对您有所帮助。另请参阅https://stackoverflow.com/tags/inline-assembly/info

于 2012-12-07T23:42:59.393 回答
50

对于 x86-64,您的内联汇编已损坏。 "=A"在 64 位模式下,编译器可以选择RAX或 RDX,而不是 EDX:EAX。有关更多信息,请参阅此问答


你不需要内联汇编。没有任何好处;编译器内置了rdtscand rdtscp,并且(至少现在)__rdtsc如果您包含正确的标头,则它们都定义了一个内在函数。但与几乎所有其他情况(https://gcc.gnu.org/wiki/DontUseInlineAsm)不同,asm 没有严重的缺点,只要您使用像 @Mysticial's 这样的良好且安全的实现

(asm 的一个小优势是,如果您想计时一个肯定会小于 2^32 计数的小间隔,您可以忽略结果的高半部分。编译器可以uint32_t time_low = __rdtsc()使用内在函数为您进行优化,但在练习他们有时仍然会浪费指令做换档/或。)


不幸的是,MSVC 对于非 SIMD 内部函数使用哪个标头不同意其他所有人。

英特尔的 intriniscs 指南_rdtsc(带有一个下划线) in <immintrin.h>,但这不适用于 gcc 和 clang。他们只在 中定义 SIMD 内在函数<immintrin.h>,所以我们坚持使用<intrin.h>(MSVC) 与<x86intrin.h>(其他一切,包括最近的 ICC)。为了与 MSVC 和 Intel 的文档兼容,gcc 和 clang 定义了函数的单下划线和双下划线版本。

有趣的事实:双下划线版本返回一个无符号的 64 位整数,而 Intel 文档_rdtsc()返回 (signed) __int64

// valid C99 and C++

#include <stdint.h>  // <cstdint> is preferred in C++, but stdint.h works.

#ifdef _MSC_VER
# include <intrin.h>
#else
# include <x86intrin.h>
#endif

// optional wrapper if you don't want to just use __rdtsc() everywhere
inline
uint64_t readTSC() {
    // _mm_lfence();  // optionally wait for earlier insns to retire before reading the clock
    uint64_t tsc = __rdtsc();
    // _mm_lfence();  // optionally block later instructions until rdtsc retires
    return tsc;
}

// requires a Nehalem or newer CPU.  Not Core2 or earlier.  IDK when AMD added it.
inline
uint64_t readTSCp() {
    unsigned dummy;
    return __rdtscp(&dummy);  // waits for earlier insns to retire, but allows later to start
}

使用所有 4 个主要编译器进行编译:gcc/clang/ICC/MSVC,用于 32 位或 64 位。在 Godbolt 编译器资源管理器上 查看结果,包括几个测试调用者。

这些内在函数在 gcc4.5(从 2010 年开始)和 clang3.5(从 2014 年开始)中是新的。Godbolt 上的 gcc4.4 和 clang 3.4 不会编译这个,但 gcc4.5.3(2011 年 4 月)可以。您可能会在旧代码中看到内联 asm,但您可以并且应该将其替换为__rdtsc(). 十多年前的编译器通常生成的代码比 gcc6、gcc7 或 gcc8 慢,并且有用的错误消息较少。

MSVC 内在(我认为)存在的时间要长得多,因为 MSVC 从不支持 x86-64 的内联 asm。ICC13 有__rdtscin immintrin.h,但根本没有 a x86intrin.h。最近的 ICC 有x86intrin.h,至少是 Godbolt 为 Linux 安装它们的方式。

您可能希望将它们定义为有符号long long,特别是如果您想减去它们并转换为浮点数。 int64_t-> float/double 比uint64_t没有 AVX512 的 x86 更有效。此外,如果 TSC 没有完全同步,由于 CPU 迁移,可能会出现小的负面结果,这可能比巨大的无符号数字更有意义。


顺便说一句,clang 还有一个__builtin_readcyclecounter()适用于任何架构的便携式设备。(在没有循环计数器的架构上总是返回零。)请参阅clang/LLVM 语言扩展文档


有关使用lfence(or cpuid) 通过阻止乱序执行来提高可重复性rdtsc并准确控制哪些指令在定时间隔内/不在定时间隔内的更多信息,请参阅@HadiBrais 对 clflush 的回答,以通过 C 函数和评论它所产生的差异的一个例子。

另请参阅LFENCE 是否在 AMD 处理器上进行序列化?(TL:DR 是的,启用 Spectre 缓解,否则内核会保留相关的 MSR 未设置,因此您应该使用cpuid它来进行序列化。)它一直被定义为 Intel 上的部分序列化。

如何在英特尔® IA-32 和 IA-64 指令集架构上对代码执行时间进行基准测试,这是 2010 年的英特尔白皮书。


rdtsc计算参考周期,而不是 CPU 核心时钟周期

无论涡轮/省电如何,它都以固定频率计数,因此如果您想要按时钟进行 uops-per-clock 分析,请使用性能计数器。 rdtsc与挂钟时间完全相关(不计算系统时钟调整,因此它是 的完美时间源steady_clock)。

TSC 频率过去总是等于 CPU 的额定频率,即标榜的标签频率。在某些 CPU 中,它只是接近,例如 i7-6700HQ 2.6 GHz Skylake 上的 2592 MHz,或 4000 MHz i7-6700k 上的 4008 MHz。在 i5-1035 Ice Lake 等更新的 CPU 上,TSC = 1.5 GHz,base = 1.1 GHz,因此禁用 turbo 甚至对于这些 CPU 上的 TSC = 核心周期几乎都不起作用。

如果您将其用于微基准测试,请先包含一个预热期,以确保您的 CPU 在开始计时之前已经处于最大时钟速度。(并且可以选择禁用 turbo 并告诉您的操作系统更喜欢最大时钟速度,以避免在您的微基准测试期间 CPU 频率偏移)。
微基准测试很难:见惯用的性能评估方式?对于其他陷阱。

除了 TSC 之外,您还可以使用一个库来访问硬件性能计数器。复杂但开销低的方法是编写 perf 计数器并rdmsr在用户空间中使用,或者更简单的方法包括perf stat 之类的技巧,如果您的定时区域足够长,您可以附加一个perf stat -p PID.

不过,您通常仍希望为微基准测试保持 CPU 时钟固定,除非您想了解不同的负载如何让 Skylake 在内存受限或其他情况下时钟下降。(请注意,内存带宽/延迟大部分是固定的,使用与内核不同的时钟。在空闲时钟速度下,L2 或 L3 缓存未命中需要的内核时钟周期要少得多。)

如果您出于调整目的使用 RDTSC 进行微基准测试,那么最好的选择是只使用滴答声并跳过甚至尝试转换为纳秒。 否则,请使用高分辨率库时间函数,如std::chronoor clock_gettime。有关时间戳函数的一些讨论/比较,请参阅gettimeofday 的更快等效项rdtsc,或者如果您的精度要求足够低以使计时器中断或线程更新它,则从内存中读取共享时间戳以完全避免。

另请参阅使用 rdtsc 计算系统时间,了解查找晶体频率和乘数。

CPU TSC 获取操作,尤其是在多核-多处理器环境中表示Nehalem 和更新的 TSC 为一个包中的所有内核同步并锁定在一起(以及不变 = 恒定和不间断的 TSC 功能)。有关多套接字同步的一些有用信息,请参阅@amdn 的答案。

(显然,即使对于现代多插槽系统,只要它们具有该功能,它们通常也是可靠的,请参阅@amdn 对链接问题的回答,以及下面的更多详细信息。)


与 TSC 相关的 CPUID 功能

使用Linux/proc/cpuinfo用于 CPU features的名称,以及您还将找到的相同功能的其他别名。

  • tsc- TSC 存在并rdtsc受支持。x86-64 的基线。
  • rdtscp-rdtscp支持。
  • tsc_deadline_timer CPUID.01H:ECX.TSC_Deadline[bit 24] = 1- 本地 APIC 可以编程为在 TSC 达到您输入的值时触发中断IA32_TSC_DEADLINE。我认为,启用“tickless”内核,直到下一件应该发生的事情发生。
  • constant_tsc:对恒定 TSC 功能的支持是通过检查 CPU 系列和型号来确定的。无论核心时钟速度如何变化,TSC 都以恒定频率滴答作响。没有这个,RDTSC计算核心时钟周期。
  • nonstop_tsc:此功能在英特尔 SDM 手册中称为不变 TSC,并且在带有CPUID.80000007H:EDX[8]. 即使在深度睡眠 C 状态下,TSC 也会保持滴答作响。在所有 x86 处理器上,nonstop_tsc暗示constant_tscconstant_tsc不一定暗示nonstop_tsc. 没有单独的 CPUID 功能位;在 Intel 和 AMD 上,相同的不变 TSC CPUID 位意味着两者constant_tscnonstop_tsc特性。请参阅Linux 的 x86/kernel/cpu/intel.c 检测代码,并且amd.c类似。

一些基于 Saltwell/Silvermont/Airmont 的处理器(但不是全部)甚至在 ACPI S3 全系统睡眠中保持 TSC 滴答作响:nonstop_tsc_s3. 这称为永远在线 TSC。(尽管似乎基于 Airmont 的那些从未发布过。)

有关常数和不变 TSC 的更多详细信息,请参阅:常数非不变 tsc 可以在 cpu 状态之间改变频率吗?.

  • tsc_adjust:MSR 可用,允许操作系统设置偏移量,在读取或读取它时添加CPUID.(EAX=07H, ECX=0H):EBX.TSC_ADJUST (bit 1)到TSC 。这允许有效地更改某些/所有内核上的 TSC,而无需在逻辑内核之间取消同步。(如果软件在每个内核上将 TSC 设置为新的绝对值,就会发生这种情况;很难在每个内核上以相同的周期执行相关的 WRMSR 指令。)IA32_TSC_ADJUSTrdtscrdtscp

constant_tscnonstop_tsc共同使 TSC 可用作clock_gettime用户空间等事物的时间源。(但像 Linux 这样的操作系统只使用 RDTSC 在由 NTP 维护的较慢时钟的滴答之间进行插值,更新定时器中断中的比例/偏移因子。请参阅On a cpu with constant_tsc 和 nonstop_tsc,为什么我的时间会漂移?)在更旧的 CPU 上不支持深度睡眠状态或频率缩放,TSC 作为时间源可能仍然可用

Linux 源代码中的注释还表明constant_tsc/ nonstop_tscfeatures(在 Intel 上)意味着“它在内核和套接字之间也是可靠的。(但不是跨机柜 - 在这种情况下我们明确将其关闭。)

“跨套接字”部分不准确。通常,不变的 TSC 仅保证 TSC 在同一插槽内的内核之间同步。在英特尔论坛主题中,Martin Dixon(英特尔)指出TSC 不变性并不意味着跨插槽同步。这需要平台供应商将 RESET 同步分发到所有套接字。鉴于上述 Linux 内核评论, 显然平台供应商在实践中会这样做。关于 CPU TSC 获取操作的答案,特别是在多核多处理器环境中,也同意单个主板上的所有插槽应该同步启动。

在多插槽共享内存系统上,没有直接的方法可以检查所有内核中的 TSC 是否同步。Linux 内核默认执行启动时和运行时检查以确保 TSC 可以用作时钟源。这些检查涉及确定 TSC 是否已同步。该命令的输出dmesg | grep 'clocksource'将告诉您内核是否使用 TSC 作为时钟源,只有在检查通过时才会发生这种情况。但即便如此,这也不能明确证明 TSC 在系统的所有套接字之间是同步的。内核参数tsc=reliable可以用来告诉内核它可以盲目地使用TSC作为时钟源而不做任何检查。

在某些情况下,跨插槽 TSC 可能不同步:(1) 热插拔 CPU,(2) 当插槽分布在由扩展节点控制器连接的不同板上时,(3) TSC 在唤醒后可能不会重新同步从某些处理器中 TSC 断电的 C 状态开始,并且 (4) 不同的插槽安装了不同的 CPU 型号。

直接更改 TSC 而不是使用 TSC_ADJUST 偏移量的操作系统或管理程序可以取消同步它们,因此在用户空间中,假设 CPU 迁移不会让您读取不同的时钟可能并不总是安全的。(这就是为什么rdtscp生成一个核心 ID 作为额外输出的原因,因此您可以检测开始/结束时间何时来自不同的时钟。它可能是在不变 TSC 功能之前引入的,或者他们只是想考虑所有可能性。 )

如果您rdtsc直接使用,您可能希望将您的程序或线程固定到核心,例如taskset -c 0 ./myprogram在 Linux 上。无论您是否需要 TSC,CPU 迁移通常会导致大量缓存未命中,并且无论如何都会弄乱您的测试,并且需要额外的时间。(尽管中断也会如此)。


使用内在函数的 asm 效率如何?

它与您从@Mysticial 的 GNU C 内联汇编中获得的一样好,或者更好,因为它知道 RAX 的高位被归零。您想要保留内联 asm 的主要原因是为了与顽固的旧编译器兼容。

函数本身的非内联版本readTSC使用 MSVC for x86-64 编译,如下所示:

unsigned __int64 readTSC(void) PROC                             ; readTSC
    rdtsc
    shl     rdx, 32                             ; 00000020H
    or      rax, rdx
    ret     0
  ; return in RAX

对于在 中返回 64 位整数的 32 位调用约定edx:eax,它只是rdtsc/ ret。没关系,你总是希望它内联。

在使用它两次并减去时间间隔的测试调用者中:

uint64_t time_something() {
    uint64_t start = readTSC();
    // even when empty, back-to-back __rdtsc() don't optimize away
    return readTSC() - start;
}

所有 4 个编译器都编写了非常相似的代码。这是 GCC 的 32 位输出:

# gcc8.2 -O3 -m32
time_something():
    push    ebx               # save a call-preserved reg: 32-bit only has 3 scratch regs
    rdtsc
    mov     ecx, eax
    mov     ebx, edx          # start in ebx:ecx
      # timed region (empty)

    rdtsc
    sub     eax, ecx
    sbb     edx, ebx          # edx:eax -= ebx:ecx

    pop     ebx
    ret                       # return value in edx:eax

这是 MSVC 的 x86-64 输出(应用了名称分解)。gcc/clang/ICC 都发出相同的代码。

# MSVC 19  2017  -Ox
unsigned __int64 time_something(void) PROC                            ; time_something
    rdtsc
    shl     rdx, 32                  ; high <<= 32
    or      rax, rdx
    mov     rcx, rax                 ; missed optimization: lea rcx, [rdx+rax]
                                     ; rcx = start
     ;; timed region (empty)

    rdtsc
    shl     rdx, 32
    or      rax, rdx                 ; rax = end

    sub     rax, rcx                 ; end -= start
    ret     0
unsigned __int64 time_something(void) ENDP                            ; time_something

所有 4 个编译器都使用or+mov而不是lea将低半部分和高半部分组合到不同的寄存器中。我猜这是他们未能优化的固定序列。

但是自己在 inline asm 中编写 shift/lea 也好不到哪里去。如果您的时间间隔如此之短以至于您只保留 32 位结果,那么您将剥夺编译器忽略 EDX 中结果的高 32 位的机会。或者如果编译器决定将开始时间存储到内存中,它可以只使用两个 32 位存储而不是 shift/或/mov。如果 1 个额外的 uop 作为计时的一部分让您感到困扰,您最好用纯 asm 编写整个微基准测试。

然而,我们也许可以通过 @Mysticial 代码的修改版本获得两全其美:

// More efficient than __rdtsc() in some case, but maybe worse in others
uint64_t rdtsc(){
    // long and uintptr_t are 32-bit on the x32 ABI (32-bit pointers in 64-bit mode), so #ifdef would be better if we care about this trick there.

    unsigned long lo,hi;  // let the compiler know that zero-extension to 64 bits isn't required
    __asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi));
    return ((uint64_t)hi << 32) + lo;
    // + allows LEA or ADD instead of OR
}

在 Godbolt 上,这有时会提供比__rdtsc()gcc/clang/ICC 更好的 asm,但有时它会欺骗编译器使用额外的寄存器来分别保存 lo 和 hi,因此 clang 可以优化为((end_hi-start_hi)<<32) + (end_lo-start_lo). 希望如果有真正的寄存器压力,编译器会更早地结合起来。(gcc 和 ICC 仍然分别保存 lo/hi,但也不要优化。)

但是 32 位 gcc8 把它弄得一团糟,甚至只用带有零rdtsc()的实际值编译函数本身,add/adc而不是像 clang 那样只在 edx:eax 中返回结果。(gcc6 和更早的版本可以使用|而不是,但如果您关心来自 gcc 的 32 位代码生成,则+绝对更喜欢内在的)。__rdtsc()

于 2018-08-18T10:29:05.203 回答
9

VC++ 对内联汇编使用完全不同的语法——但仅限于 32 位版本。64 位编译器根本不支持内联汇编。

在这种情况下,这可能也一样——rdtsc在时序代码序列方面(至少)有两个主要问题。首先(像大多数指令一样)它可以乱序执行,所以如果你试图对一小段代码进行计时,那么rdtsc之前和之后的代码可能都在它之前执行,或者都在它之后执行,或者你有什么(我相当确定这两者将始终按顺序执行,因此至少差异永远不会是负面的)。

其次,在多核(或多处理器)系统上,一个 rdtsc 可能在一个内核/处理器上执行,而另一个在不同的内核/处理器上执行。在这种情况下,完全有可能得出否定的结果

一般来说,如果你想在 Windows 下有一个精确的计时器,你最好使用QueryPerformanceCounter.

如果您真的坚持使用rdtsc,我相信您必须在完全用汇编语言(或使用编译器内部)编写的单独模块中执行此操作,然后与您的 C 或 C++ 链接。我从来没有为 64 位模式编写过该代码,但在 32 位模式下它看起来像这样:

   xor eax, eax
   cpuid
   xor eax, eax
   cpuid
   xor eax, eax
   cpuid
   rdtsc
   ; save eax, edx

   ; code you're going to time goes here

   xor eax, eax
   cpuid
   rdtsc

我知道这看起来很奇怪,但实际上是对的。您执行 CPUID 因为它是一个序列化指令(不能乱序执行)并且在用户模式下可用。您在开始计时之前执行了三次,因为英特尔记录了第一次执行可以/将以与第二次不同的速度运行的事实(他们推荐的是三个,所以它是三个)。

然后你执行你的被测代码,另一个 cpuid 强制序列化,最后一个 rdtsc 来获取代码完成后的时间。

除此之外,您还想使用您的操作系统提供的任何方式来强制这一切在一个进程/核心上运行。在大多数情况下,您还想强制代码对齐——对齐的变化会导致执行速度的相当大的差异。

最后你想多次执行它——而且它总是有可能在中间被打断(例如,任务切换),所以你需要准备好执行可能需要相当长的时间比其他的更长——例如,5 次运行需要大约 40-43 个时钟周期,而第六次运行需要 10000 多个时钟周期。显然,在后一种情况下,您只需丢弃异常值——它不是来自您的代码。

摘要:管理执行 rdtsc 指令本身(几乎)是您最不担心的事情。在获得结果之前,您还需要做很多事情rdtsc,这实际上意味着任何事情。

于 2012-12-07T23:45:36.457 回答
5

对于 Windows,Visual Studio 提供了一个方便的“编译器内在函数”(即编译器理解的特殊函数),它为您执行 RDTSC 指令并返回结果:

unsigned __int64 __rdtsc(void);
于 2012-12-07T23:41:59.943 回答
5

Linuxperf_event_open系统调用与config = PERF_COUNT_HW_CPU_CYCLES

这个 Linux 系统调用似乎是性能事件的跨体系结构包装器。

这个答案类似:快速计算在 C 程序中执行的指令数量但使用PERF_COUNT_HW_CPU_CYCLES而不是PERF_COUNT_HW_INSTRUCTIONS. 该答案将侧重于PERF_COUNT_HW_CPU_CYCLES细节,请参阅其他答案以获取更多通用信息。

这是一个基于手册页末尾提供的示例。

perf_event_open.c

#define _GNU_SOURCE
#include <asm/unistd.h>
#include <linux/perf_event.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>

#include <inttypes.h>
#include <sys/types.h>

static long
perf_event_open(struct perf_event_attr *hw_event, pid_t pid,
                int cpu, int group_fd, unsigned long flags)
{
    int ret;

    ret = syscall(__NR_perf_event_open, hw_event, pid, cpu,
                    group_fd, flags);
    return ret;
}

int
main(int argc, char **argv)
{
    struct perf_event_attr pe;
    long long count;
    int fd;

    uint64_t n;
    if (argc > 1) {
        n = strtoll(argv[1], NULL, 0);
    } else {
        n = 10000;
    }

    memset(&pe, 0, sizeof(struct perf_event_attr));
    pe.type = PERF_TYPE_HARDWARE;
    pe.size = sizeof(struct perf_event_attr);
    pe.config = PERF_COUNT_HW_CPU_CYCLES;
    pe.disabled = 1;
    pe.exclude_kernel = 1;
    // Don't count hypervisor events.
    pe.exclude_hv = 1;

    fd = perf_event_open(&pe, 0, -1, -1, 0);
    if (fd == -1) {
        fprintf(stderr, "Error opening leader %llx\n", pe.config);
        exit(EXIT_FAILURE);
    }

    ioctl(fd, PERF_EVENT_IOC_RESET, 0);
    ioctl(fd, PERF_EVENT_IOC_ENABLE, 0);

    /* Loop n times, should be good enough for -O0. */
    __asm__ (
        "1:;\n"
        "sub $1, %[n];\n"
        "jne 1b;\n"
        : [n] "+r" (n)
        :
        :
    );

    ioctl(fd, PERF_EVENT_IOC_DISABLE, 0);
    read(fd, &count, sizeof(long long));

    printf("%lld\n", count);

    close(fd);
}

结果似乎是合理的,例如,如果我打印周期然后重新编译指令计数,我们每次迭代得到大约 1 个周期(在一个周期中完成 2 条指令)可能是由于诸如超标量执行之类的影响,每次运行的结果略有不同可能是由于随机内存访问延迟。

您可能还对PERF_COUNT_HW_REF_CPU_CYCLES手册页中的文档感兴趣:

总周期;不受 CPU 频率缩放的影响。

因此,如果您的频率缩放打开,这将提供更接近真实墙上时间的东西。这些比我的快速实验大 2/3 倍PERF_COUNT_HW_INSTRUCTIONS,大概是因为我的非压力机器现在是频率缩放的。

于 2020-11-18T17:19:52.547 回答