简单 getter/accessor 阻止矢量化 - gcc 错误?
Simple getter/accessor prevents vectorization - gcc bug?
考虑固定 vector<int>
:
的最小实现
constexpr std::size_t capacity = 1000;
struct vec
{
int values[capacity];
std::size_t _size = 0;
std::size_t size() const noexcept
{
return _size;
}
void push(int x)
{
values[size()] = x;
++_size;
}
};
给定以下测试用例:
vec v;
for(std::size_t i{0}; i != capacity; ++i)
{
v.push(i);
}
asm volatile("" : : "g"(&v) : "memory");
编译器生成非矢量化程序集:live example on godbolt.org
如果我进行任何以下更改...
values[size()]
-> values[_size]
将__attribute__((always_inline))
添加到size()
...然后编译器生成矢量化汇编:live example on godbolt.org
这是一个 gcc 错误吗? 或者有一个简单的访问器如 size()
会阻止自动矢量化的原因,除非 always_inline
是明确的已添加?
我可以缩小问题范围。
双精度或单精度和优化标志 -std=c++11 -Ofast -march=native:
版本 >= 5.0.0 的 Clang 使用 zmm 寄存器生成 AVX 移动指令
4.9 <= 版本 <= 6.3 的 Gcc 使用 zmm 寄存器生成 AVX 移动指令
版本 >= 7.1.0 的 Gcc 使用 xmm 寄存器生成 AVX 移动指令
您的示例中的循环 已针对 GCC < 7.1 进行矢量化,未针对 GCC >= 7.1 进行矢量化。所以这里的行为似乎有些变化。
我们可以通过在命令行中添加-fopt-info-vec-all
来查看编译器优化报告:
对于 GCC 7.3:
<source>:24:29: note: === vect_pattern_recog ===
<source>:24:29: note: === vect_analyze_data_ref_accesses ===
<source>:24:29: note: not vectorized: complicated access pattern.
<source>:24:29: note: bad data access.
<source>:21:5: note: vectorized 0 loops in function.
对于 GCC 6.3:
<source>:24:29: note: === vect_pattern_recog ===
<source>:24:29: note: === vect_analyze_data_ref_accesses ===
<source>:24:29: note: === vect_mark_stmts_to_be_vectorized ===
[...]
<source>:24:29: note: LOOP VECTORIZED
<source>:21:5: note: vectorized 1 loops in function.
因此 GCC 7.x 决定不对循环进行矢量化,因为访问模式很复杂,这可能是(在那时)非内联 size()
函数。强制内联或手动进行内联可以解决这个问题。 GCC 6.x 似乎是自己做的。然而,在这两种情况下,程序集确实看起来 size()
最终被内联,但可能只是在 GCC 7.x 的矢量化步骤之后(这是我的猜测)。
我想知道你为什么把 asm volatile(...)
行放在最后 - 可能是为了防止编译器丢弃整个循环,因为它在这个测试用例。如果我们只是 return v
的最后一个元素而不是 ,我们可以达到相同的效果而不会对 [=18= 的内存模型造成任何可能的副作用].
return v.values[capacity - 1];
代码现在使用 GCC 7.x 向量化,就像它已经使用 GCC 6.x:
<source>:24:29: note: === vect_pattern_recog ===
<source>:24:29: note: === vect_analyze_data_ref_accesses ===
<source>:24:29: note: === vect_mark_stmts_to_be_vectorized ===
[...]
<source>:24:29: note: LOOP VECTORIZED
<source>:21:5: note: vectorized 1 loops in function.
那么这里的结论是什么?
- GCC 7.1 发生了一些变化
- 最佳猜测:
asm volatile
的副作用与 size()
的内联混淆,阻止了矢量化
这是否是错误 - 可能在 6.x 或 7.x 中,具体取决于 asm volatile()
构造所需的行为 - 将是 GCC 的问题开发人员。
此外:尝试将 -mavx2
或 -mavx512f -mavx512cd
(或 -march=native
等)添加到命令行,具体取决于您的硬件,以获得超过 128 位的矢量化 xmm
,即 ymm
和 zmm
,寄存器。
考虑固定 vector<int>
:
constexpr std::size_t capacity = 1000;
struct vec
{
int values[capacity];
std::size_t _size = 0;
std::size_t size() const noexcept
{
return _size;
}
void push(int x)
{
values[size()] = x;
++_size;
}
};
给定以下测试用例:
vec v;
for(std::size_t i{0}; i != capacity; ++i)
{
v.push(i);
}
asm volatile("" : : "g"(&v) : "memory");
编译器生成非矢量化程序集:live example on godbolt.org
如果我进行任何以下更改...
values[size()]
->values[_size]
将
__attribute__((always_inline))
添加到size()
...然后编译器生成矢量化汇编:live example on godbolt.org
这是一个 gcc 错误吗? 或者有一个简单的访问器如 size()
会阻止自动矢量化的原因,除非 always_inline
是明确的已添加?
我可以缩小问题范围。
双精度或单精度和优化标志 -std=c++11 -Ofast -march=native:
版本 >= 5.0.0 的 Clang 使用 zmm 寄存器生成 AVX 移动指令
4.9 <= 版本 <= 6.3 的 Gcc 使用 zmm 寄存器生成 AVX 移动指令
版本 >= 7.1.0 的 Gcc 使用 xmm 寄存器生成 AVX 移动指令
您的示例中的循环 已针对 GCC < 7.1 进行矢量化,未针对 GCC >= 7.1 进行矢量化。所以这里的行为似乎有些变化。
我们可以通过在命令行中添加-fopt-info-vec-all
来查看编译器优化报告:
对于 GCC 7.3:
<source>:24:29: note: === vect_pattern_recog ===
<source>:24:29: note: === vect_analyze_data_ref_accesses ===
<source>:24:29: note: not vectorized: complicated access pattern.
<source>:24:29: note: bad data access.
<source>:21:5: note: vectorized 0 loops in function.
对于 GCC 6.3:
<source>:24:29: note: === vect_pattern_recog ===
<source>:24:29: note: === vect_analyze_data_ref_accesses ===
<source>:24:29: note: === vect_mark_stmts_to_be_vectorized ===
[...]
<source>:24:29: note: LOOP VECTORIZED
<source>:21:5: note: vectorized 1 loops in function.
因此 GCC 7.x 决定不对循环进行矢量化,因为访问模式很复杂,这可能是(在那时)非内联 size()
函数。强制内联或手动进行内联可以解决这个问题。 GCC 6.x 似乎是自己做的。然而,在这两种情况下,程序集确实看起来 size()
最终被内联,但可能只是在 GCC 7.x 的矢量化步骤之后(这是我的猜测)。
我想知道你为什么把 asm volatile(...)
行放在最后 - 可能是为了防止编译器丢弃整个循环,因为它在这个测试用例。如果我们只是 return v
的最后一个元素而不是 ,我们可以达到相同的效果而不会对 [=18= 的内存模型造成任何可能的副作用].
return v.values[capacity - 1];
代码现在使用 GCC 7.x 向量化,就像它已经使用 GCC 6.x:
<source>:24:29: note: === vect_pattern_recog ===
<source>:24:29: note: === vect_analyze_data_ref_accesses ===
<source>:24:29: note: === vect_mark_stmts_to_be_vectorized ===
[...]
<source>:24:29: note: LOOP VECTORIZED
<source>:21:5: note: vectorized 1 loops in function.
那么这里的结论是什么?
- GCC 7.1 发生了一些变化
- 最佳猜测:
asm volatile
的副作用与size()
的内联混淆,阻止了矢量化
这是否是错误 - 可能在 6.x 或 7.x 中,具体取决于 asm volatile()
构造所需的行为 - 将是 GCC 的问题开发人员。
此外:尝试将 -mavx2
或 -mavx512f -mavx512cd
(或 -march=native
等)添加到命令行,具体取决于您的硬件,以获得超过 128 位的矢量化 xmm
,即 ymm
和 zmm
,寄存器。