在编译时检测处理器是否有 RDTSCP

Detect if processor has RDTSCP at compile time

一些新的英特尔处理器同时具有 RDTSCRDTSCP 指令,而大多数旧处理器只有 RDTSC 指令。

在 C/C++ 中编码时,如何在编译时检测正在使用的体系结构是否具有 RDTSCP 指令?

我知道我们可以通过浏览 CPU 信息(例如,cat /proc/cpuinfo)然后调整我们的代码来手动检查。但是在编译时获取此信息(作为宏或标志值)将真正省去手动检查和编辑代码的需要。

我一直在尝试让一些东西发挥作用,但到目前为止还没有成功,但您可能想尝试查看 SFINAE 路线:https://en.wikipedia.org/wiki/Substitution_failure_is_not_an_error

我认为我可以将程序集注入 lambda 并在平台上不存在该指令时导致此操作失败,或者在存在时成功,但无法使用 lambda 的可能性很小与decltype。如果您能以某种方式将汇编代码提供给模板参数,那么就可以完成,但我不知道这是否可行。 SFINAE 真的很酷,但可以让你的头脑很快转起来。

如果您使用的是 *nix,另一种(可能是幼稚且相当不优雅的)方法是编写一个运行该汇编指令的程序,然后捕获一个 SIGILL 并执行没有特别说明。

但是必须有比这更好的方法,我认为查看特定于编译器的宏将是实现它的方法。

祝你好运!

编者注:https://gcc.gnu.org/wiki/DontUseInlineAsm。很长一段时间以来,这个答案都是不安全的,后来被编辑为在仍然不安全的情况下甚至无法编译(破坏 RAX 使 "a" 约束无法满足,同时仍然缺少 CPUID 写入的寄存器上的破坏)。在另一个答案中使用内在函数。 (但我已经修复了这里的内联 asm 以确保安全和正确,以防有人 copy/paste 它,或者想学习如何正确使用约束和破坏。)


根据@Jason 提出的建议进行更多调查后,我现在有了一个 运行 时解决方案(仍然不是编译时解决方案)来确定 RDTSCP 是否存在检查 cpuid 指令的第 28 位(参见 output bitmap),其中 0x80000001 作为 EAX.

中的输入
int if_rdtscp() {
    unsigned int edx;
    unsigned int eax = 0x80000001;
#ifdef __GNUC__              // GNU extended asm supported
    __asm__ (     // doesn't need to be volatile: same EAX input -> same outputs
     "CPUID\n\t"
    : "+a" (eax),         // CPUID writes EAX, but we can't declare a clobber on an input-only operand.
      "=d" (edx)
    : // no read-only inputs
    : "ecx", "ebx");      // CPUID writes E[ABCD]X, declare clobbers

    // a clobber on ECX covers the whole RCX, so this code is safe in 64-bit mode but is portable to either.

#else // Non-gcc/g++ compilers.
    // To-do when needed
#endif
    return (edx >> 27) & 0x1;
}

如果由于 EBX 破坏,这在 32 位 PIC 代码中不起作用,则 1. 停止使用 32 位 PIC,因为它与 64 位 PIC 或 -fno-pie -no-pie 可执行文件相比效率低下. 2. 获得一个更新的 GCC,即使在 32 位 PIC 代码中也允许 EBX 破坏,向 save/restore EBX 或任何需要的东西发出额外的指令。 3. 使用 intrinsics 版本(应该可以解决这个问题)。


现在我可以使用 GNU 编译器,但如果有人需要在 MSVC 下执行此操作,那么这是一种内部检查方法,如 here 所述。

您好,您可以使用 CPUID 标志在编译时检查它是否存在,为此您必须使用 2 个东西,首先是守卫,例如:

#ifdef __RDTSCP__
    // do things because it has the 
       function 
#else
    // do things if it doesn't have 
#endif 

最后你必须使用 gcc 中的标志编译代码,例如:

gcc x.c -o x.o -march=native

此 gcc 指令将使用您的本机函数编​​译您的代码 cpu 因此它将定义您的 CPUID。

GCC 定义了许多宏来在编译时确定使用 -march 指定的微体系结构是否支持特定功能。您可以在源代码中找到完整列表 here. It's clear that GCC does not define such a macro for RDTSCP (or even RDTSC for that matter). The processors that support RDTSCP are listed in: What is the gcc cpu-type that includes support for RDTSCP?.

因此您可以制作自己的(可能不完整的)支持 RDTSCP 的微体系结构列表。然后编写一个构建脚本来检查传递给 -march 的参数并查看它是否在列表中。如果是,则定义一个宏,例如 __RDTSCP__ 并在您的代码中使用它。我认为即使您的列表不完整,也不应影响代码的正确性。

不幸的是,Intel 数据表似乎没有具体说明特定处理器是否支持 RDTSCP,即使它们讨论了 AVX2 等其他功能。

这里的一个潜在问题是无法保证 每个 实现特定微体系结构(例如 Skylake)的单个处理器都支持 RDTSCP。不过我不知道有这样的例外。

相关:What is the gcc cpu-type that includes support for RDTSCP?.


要在 运行-time 确定 RDTSCP 支持,以下代码可用于支持 GNU 扩展(GCC、clang、ICC)的编译器,在任何 x86 上OS。 cpuid.h是编译器自带的,不是OS。

#include <cpuid.h>

int rdtscp_supported(void) {
    unsigned a, b, c, d;
    if (__get_cpuid(0x80000001, &a, &b, &c, &d) && (d & (1<<27)))
    {
        // RDTSCP is supported.
        return 1;
    }
    else
    {
        // RDTSCP is not supported.
        return 0;
    }
}

__get_cpuid() 运行s CPUID 两次:一次检查最大等级,一次检查指定的叶子值。如果请求的级别甚至不可用,它 returns false,这就是为什么它是 && 表达式的一部分。您可能不想每次在 rdtscp 之前都使用它,只是作为变量的初始化器,除非它只是一个简单的一次性程序。看到了吧on the Godbolt compiler explorer.

对于 MSVC,请参阅 How to detect rdtscp support in Visual C++? 以获取使用其内在代码的代码。


对于 GCC 确实知道的某些 CPU 功能,您可以使用 __builtin_cpu_supports 检查在启动早期初始化的功能位图。

// unfortunately no equivalent for RDTSCP
int sse42_supported() {
    return __builtin_cpu_supports("sse4.2");
}