gcc (6.1.0) 在 SSE 内在函数中使用 'wrong' 指令
gcc (6.1.0) using 'wrong' instructions in SSE intrinsics
背景:我开发了一个计算密集型工具,用C/C++编写,必须能够运行在各种不同的 x86_64 处理器。为了加快浮点数和整数的计算速度,代码包含相当多的 SSE* 内在函数,这些函数具有针对不同 CPU SSE 功能量身定制的不同路径。 (由于 CPU 标志在程序开始时被检测到并用于设置布尔值,我假设定制代码块的分支预测将非常有效地工作)。
为简单起见,我假设只需要考虑 SSE2 到 SSE4.2。
为了访问 SSE4.2 intrinsics fpr 4.2 路径,我需要使用 gcc 的 -msse4.2 选项。
问题
我遇到的问题是,至少在 6.1.0 中,gcc 会使用 sse4.2 实现 sse2 内在的 mm_cvtsi32_si128 指令,pinrd.
如果我使用-msse2限制编译,它会使用sse2指令,movd,即。英特尔 "intrinsics guide" 说它应该使用的那个。
这在两个方面很烦人。
1) 关键问题是程序现在在 pre4.2 CPU 上获得 运行 时因非法指令而崩溃。我无法控制使用的硬件,因此可执行文件需要与旧机器兼容,但需要在可用的情况下利用较新硬件的功能。
2) 根据 Intel intrinsics 指南,pinrd 指令比它替换的 mov 指令慢很多。 (pinsrd 更通用,但这不是必需的)。
有谁知道如何让 gcc 只是 使用内在函数指南说应该使用的指令,但仍然允许在同一编译单元中访问所有 SSE2 到 SSE4*?
更新:我还应该注意,相同的代码是在 Linux、Windows 和 OSX 下使用各种不同的编译器编译的,因此我宁愿避免或至少有尽可能少的特定于编译器的扩展。
Update2:(感谢@PeterCordes)似乎如果启用了优化,gcc 将在适当的情况下恢复使用 pinsrd 中的 movd。
如果在编译步骤中向 gcc 的命令行提供 -msse4.2
标志,它将假定它可以免费使用整个翻译单元的 SSE 4.2 指令集。这可能会导致您描述的行为。如果您需要 仅 使用 SSE2 和以下代码的代码,则需要使用 -msse2
(如果您正在为 x86_64 构建,则根本不使用标志)。
我能想到的一些选项是:
如果您可以轻松地在函数级别分解代码,那么 gcc 的 multiversioning 功能可以提供帮助。它需要一个相对较新版本的编译器,但它允许你做这样的事情(取自上面的 link):
__attribute__ ((target ("default")))
int foo ()
{
// The default version of foo.
return 0;
}
__attribute__ ((target ("sse4.2")))
int foo ()
{
// foo version for SSE4.2
return 1;
}
__attribute__ ((target ("arch=atom")))
int foo ()
{
// foo version for the Intel ATOM processor
return 2;
}
__attribute__ ((target ("arch=amdfam10")))
int foo ()
{
// foo version for the AMD Family 0x10 processors.
return 3;
}
int main ()
{
int (*p)() = &foo;
assert ((*p) () == foo ());
return 0;
}
在此示例中,gcc 将自动编译 foo()
的不同版本,并在运行时根据 CPU 的功能分派到适当的版本。
您可以将不同的实现(SSE2、SSE4.2 等)分解为不同的翻译单元,然后在运行时适当地分派到正确的实现。
您可以将所有 SIMD 代码放入共享库中,并使用不同的编译器标志多次构建共享库。然后在运行时,您可以检测 CPU 的功能并加载适当版本的共享库。这是 Intel's Math Kernel Library.
等图书馆采用的方法
背景:我开发了一个计算密集型工具,用C/C++编写,必须能够运行在各种不同的 x86_64 处理器。为了加快浮点数和整数的计算速度,代码包含相当多的 SSE* 内在函数,这些函数具有针对不同 CPU SSE 功能量身定制的不同路径。 (由于 CPU 标志在程序开始时被检测到并用于设置布尔值,我假设定制代码块的分支预测将非常有效地工作)。
为简单起见,我假设只需要考虑 SSE2 到 SSE4.2。
为了访问 SSE4.2 intrinsics fpr 4.2 路径,我需要使用 gcc 的 -msse4.2 选项。
问题 我遇到的问题是,至少在 6.1.0 中,gcc 会使用 sse4.2 实现 sse2 内在的 mm_cvtsi32_si128 指令,pinrd.
如果我使用-msse2限制编译,它会使用sse2指令,movd,即。英特尔 "intrinsics guide" 说它应该使用的那个。
这在两个方面很烦人。
1) 关键问题是程序现在在 pre4.2 CPU 上获得 运行 时因非法指令而崩溃。我无法控制使用的硬件,因此可执行文件需要与旧机器兼容,但需要在可用的情况下利用较新硬件的功能。
2) 根据 Intel intrinsics 指南,pinrd 指令比它替换的 mov 指令慢很多。 (pinsrd 更通用,但这不是必需的)。
有谁知道如何让 gcc 只是 使用内在函数指南说应该使用的指令,但仍然允许在同一编译单元中访问所有 SSE2 到 SSE4*?
更新:我还应该注意,相同的代码是在 Linux、Windows 和 OSX 下使用各种不同的编译器编译的,因此我宁愿避免或至少有尽可能少的特定于编译器的扩展。
Update2:(感谢@PeterCordes)似乎如果启用了优化,gcc 将在适当的情况下恢复使用 pinsrd 中的 movd。
如果在编译步骤中向 gcc 的命令行提供 -msse4.2
标志,它将假定它可以免费使用整个翻译单元的 SSE 4.2 指令集。这可能会导致您描述的行为。如果您需要 仅 使用 SSE2 和以下代码的代码,则需要使用 -msse2
(如果您正在为 x86_64 构建,则根本不使用标志)。
我能想到的一些选项是:
如果您可以轻松地在函数级别分解代码,那么 gcc 的 multiversioning 功能可以提供帮助。它需要一个相对较新版本的编译器,但它允许你做这样的事情(取自上面的 link):
__attribute__ ((target ("default"))) int foo () { // The default version of foo. return 0; } __attribute__ ((target ("sse4.2"))) int foo () { // foo version for SSE4.2 return 1; } __attribute__ ((target ("arch=atom"))) int foo () { // foo version for the Intel ATOM processor return 2; } __attribute__ ((target ("arch=amdfam10"))) int foo () { // foo version for the AMD Family 0x10 processors. return 3; } int main () { int (*p)() = &foo; assert ((*p) () == foo ()); return 0; }
在此示例中,gcc 将自动编译
foo()
的不同版本,并在运行时根据 CPU 的功能分派到适当的版本。您可以将不同的实现(SSE2、SSE4.2 等)分解为不同的翻译单元,然后在运行时适当地分派到正确的实现。
您可以将所有 SIMD 代码放入共享库中,并使用不同的编译器标志多次构建共享库。然后在运行时,您可以检测 CPU 的功能并加载适当版本的共享库。这是 Intel's Math Kernel Library.
等图书馆采用的方法