在编译时检测处理器是否有 RDTSCP
Detect if processor has RDTSCP at compile time
一些新的英特尔处理器同时具有 RDTSC
和 RDTSCP
指令,而大多数旧处理器只有 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");
}
一些新的英特尔处理器同时具有 RDTSC
和 RDTSCP
指令,而大多数旧处理器只有 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");
}