奇怪的 gcc6.1 -O2 编译行为
Strange gcc6.1 -O2 compiling behaviour
我正在使用 gcc -O2 -march=native
标志编译相同的基准测试。然而,有趣的是,当我查看 objdump
时,它实际上会产生一些指令,例如 vxorpd
等,我认为它们应该只在 -ftree-vectorize
启用时出现(并且 -O2
默认情况下不应启用此功能?)如果我添加 -m32
标志以在 32 位指令中编译,这些打包指令就会消失。哪位遇到过类似情况的可以给解释一下?谢谢。
XORPD
是经典的 SSE2 指令,它对两个压缩双精度浮点值执行按位逻辑异或。
VXORPD
是同一指令的矢量版本。本质上,它是带有 VEX prefix 的经典 SSE2 XORPD
指令。这就是 "V" 前缀在操作码中的含义。它是随 AVX(高级矢量扩展)引入的,并且在任何支持 AVX 的架构上都受支持。 (实际上有两个版本,适用于 128 位 AVX 寄存器的 VEX.128 编码版本,以及适用于 256 位 AVX2 寄存器的 VEX.256 编码版本。)
所有旧版 SSE 和 SSE2 指令都可以添加一个 VEX 前缀,为它们提供三操作数形式,并允许它们与其他新的 AVX 指令进行交互和更有效地调度。它还避免了the high cost of transitions between VEX and non-VEX modes。否则,这些新编码将保留相同的行为。因此,只要目标体系结构支持它们,编译器通常会生成这些指令的 VEX 前缀版本。显然,在您的情况下,march=native
指定的架构至少支持 AVX。
在 GCC 和 Clang 上,即使关闭优化 (-O0
),您实际上也会发出这些指令,因此在启用优化时您肯定会得到它们。 -ftree-vectorize
开关和任何其他特定于矢量化的优化开关都不需要打开,因为这实际上与代码矢量化没有任何关系。更准确地说,代码流没有改变,只是指令的编码。
你可以用能想到的最简单的代码看到这个:
double Foo()
{
return 0.0;
}
Foo():
vxorpd xmm0, xmm0, xmm0
ret
这就解释了为什么在使用 -march=native
开关编译 64 位版本时会看到 VXORPD
及其朋友。
这留下了一个问题,即为什么当您使用 -m32
开关(这意味着为 32 位平台生成代码)时,您 没有 看到它。 SSE 和 AVX 指令在针对这些平台时仍然可用,我相信它们会在某些情况下使用,但由于 32 位 ABI 的显着差异,它们不能经常使用。具体来说,32 位 ABI 要求在 x87 浮点堆栈上返回浮点值。由于这需要使用 x87 浮点指令,因此优化器倾向于坚持使用这些指令,除非它对一段代码进行了大量矢量化。这是唯一一次真正有意义的将值从 x87 堆栈洗牌到 SIMD 寄存器,然后再洗回来。否则,这是一种性能消耗,几乎没有实际好处。
您也可以在实际操作中看到这一点。通过抛出 -m32
开关查看输出的变化:
Foo():
fldz
ret
FLDZ
是 x87 FPU 指令,用于将常量零加载到浮点堆栈的顶部,准备返回给调用者。
显然,当您使代码更复杂时,您更有可能更改优化器的试探法并说服它发出 SIMD 指令。如果您启用基于矢量化的优化,您的可能性就更大了。
只是添加到 ,您可以通过输出到汇编器并打开 -fverbose-asm
.
来检查 gcc 的内部启用选项
例如:
gcc -O2 -fverbose-asm -S -o test.S test.c
将在 test.S
中列出在所选优化级别(此处 -O2
)启用的所有优化选项。
我正在使用 gcc -O2 -march=native
标志编译相同的基准测试。然而,有趣的是,当我查看 objdump
时,它实际上会产生一些指令,例如 vxorpd
等,我认为它们应该只在 -ftree-vectorize
启用时出现(并且 -O2
默认情况下不应启用此功能?)如果我添加 -m32
标志以在 32 位指令中编译,这些打包指令就会消失。哪位遇到过类似情况的可以给解释一下?谢谢。
XORPD
是经典的 SSE2 指令,它对两个压缩双精度浮点值执行按位逻辑异或。
VXORPD
是同一指令的矢量版本。本质上,它是带有 VEX prefix 的经典 SSE2 XORPD
指令。这就是 "V" 前缀在操作码中的含义。它是随 AVX(高级矢量扩展)引入的,并且在任何支持 AVX 的架构上都受支持。 (实际上有两个版本,适用于 128 位 AVX 寄存器的 VEX.128 编码版本,以及适用于 256 位 AVX2 寄存器的 VEX.256 编码版本。)
所有旧版 SSE 和 SSE2 指令都可以添加一个 VEX 前缀,为它们提供三操作数形式,并允许它们与其他新的 AVX 指令进行交互和更有效地调度。它还避免了the high cost of transitions between VEX and non-VEX modes。否则,这些新编码将保留相同的行为。因此,只要目标体系结构支持它们,编译器通常会生成这些指令的 VEX 前缀版本。显然,在您的情况下,march=native
指定的架构至少支持 AVX。
在 GCC 和 Clang 上,即使关闭优化 (-O0
),您实际上也会发出这些指令,因此在启用优化时您肯定会得到它们。 -ftree-vectorize
开关和任何其他特定于矢量化的优化开关都不需要打开,因为这实际上与代码矢量化没有任何关系。更准确地说,代码流没有改变,只是指令的编码。
你可以用能想到的最简单的代码看到这个:
double Foo()
{
return 0.0;
}
Foo():
vxorpd xmm0, xmm0, xmm0
ret
这就解释了为什么在使用 -march=native
开关编译 64 位版本时会看到 VXORPD
及其朋友。
这留下了一个问题,即为什么当您使用 -m32
开关(这意味着为 32 位平台生成代码)时,您 没有 看到它。 SSE 和 AVX 指令在针对这些平台时仍然可用,我相信它们会在某些情况下使用,但由于 32 位 ABI 的显着差异,它们不能经常使用。具体来说,32 位 ABI 要求在 x87 浮点堆栈上返回浮点值。由于这需要使用 x87 浮点指令,因此优化器倾向于坚持使用这些指令,除非它对一段代码进行了大量矢量化。这是唯一一次真正有意义的将值从 x87 堆栈洗牌到 SIMD 寄存器,然后再洗回来。否则,这是一种性能消耗,几乎没有实际好处。
您也可以在实际操作中看到这一点。通过抛出 -m32
开关查看输出的变化:
Foo():
fldz
ret
FLDZ
是 x87 FPU 指令,用于将常量零加载到浮点堆栈的顶部,准备返回给调用者。
显然,当您使代码更复杂时,您更有可能更改优化器的试探法并说服它发出 SIMD 指令。如果您启用基于矢量化的优化,您的可能性就更大了。
只是添加到 -fverbose-asm
.
例如:
gcc -O2 -fverbose-asm -S -o test.S test.c
将在 test.S
中列出在所选优化级别(此处 -O2
)启用的所有优化选项。