一些新的英特尔处理器同时有RDTSC
和RDTSCP
条指令,而大多数旧处理器只有RDTSC
条指令。
在C / C ++编码时,如何在编译时检测所使用的架构是否有RDTSCP
指令?
我知道我们可以通过浏览CPU信息(例如,cat / proc / cpuinfo)然后调整我们的代码来手动检查。但是在编译时获取此信息(作为宏或标志值)将真正省略手动检查和编辑代码的需要。
答案 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。