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

时间:2015-09-02 15:42:37

标签: c++ c linux x86 intel

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

在C / C ++编码时,如何在编译时检测所使用的架构是否有RDTSCP指令?

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

4 个答案:

答案 0 :(得分:2)

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

根据@Jason的建议调查了一下之后,我现在有一个运行时解决方案(仍然不是编译时的解决方案),通过检查第28位确定RDTSCP是否存在(参见cpuid 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; } 作为-fno-pie -no-pie的输入。

{{1}}

如果由于EBX崩溃而无法在32位PIC代码中工作,那么1.停止使用32位PIC,因为它与64位PIC或{{{{{{{{ 1}}可执行文件。 2.获得更新的GCC,即使在32位PIC代码中也允许使用EBX clobbers,发出额外的指令来保存/恢复EBX或其他任何需要。 3.使用内在函数版本(这应该适合你)。

现在我对GNU编译器很好,但是如果有人需要在MSVC下执行此操作,那么就像在output bitmap中所解释的那样,这是一种内在的检查方式。

答案 1 :(得分:2)

GCC定义了许多宏,以在编译时确定使用-march指定的微体系结构是否支持特定功能。您可以在源代码here中找到完整列表。显然,GCC并未为RDTSCP(甚至就RDTSC而言)也没有定义这样的宏。而且据我所知,还没有支持RDTSCP的微体系结构的 complete 列表。但是众所周知,许多微体系结构都支持RDTSCP。这些包括英特尔Nehalem和所有后来的英特尔微体系结构。我不了解英特尔凌动。基本上,大多数(如果不是全部)现代(2008+)高性能Intel和AMD处理器都支持RDTSCP。我怀疑其中一些低功耗设备可能不支持它。

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

不幸的是,尽管Intel数据表讨论了AVX2之类的其他功能,但似乎并未指定特定处理器是否支持RDTSCP

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

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


要在运行时确定对RDTSCP的支持,可以在任何x86 OS上的支持GNU扩展(GCC,clang,ICC)的编译器上使用以下代码。 cpuid.h随编译器一起提供,而不与操作系统一起提供。

#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()运行两次CPUID:一次检查最大级别,一次使用指定的叶子值。如果请求的级别甚至不可用,它将返回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");
}

答案 2 :(得分:-1)

我一直试图让某些事情发挥作用,但到目前为止还没有成功,但你可能想尝试俯视SFINAE路线:https://en.wikipedia.org/wiki/Substitution_failure_is_not_an_error

我认为可能有一个很小的机会我可以将程序集注入lambda并导致如果平台上不存在该指令会失败,或者如果确实成功则会失败,但是lambdas不能与decltype一起使用。如果你能以某种方式将汇编代码反馈到模板参数中,那么就可以完成,但我不知道这是否可行。 SFINAE非常酷,但可以很快地让你的头脑旋转。

如果您使用* nix,另一种(可能天真且相当不优雅)的方法是编写运行该汇编指令的程序,然后捕获SIGILL并执行程序版本而无需特殊说明。

但是必须有一个比这更好的方法,我应该认为查看特定于编译器的宏将是这样做的方法。

祝你好运!

答案 3 :(得分:-2)

您好,您可以使用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。