【问题标题】:Simple getter/accessor prevents vectorization - gcc bug?简单的 getter/accessor 防止矢量化 - gcc 错误?
【发布时间】:2018-07-23 21:04:57
【问题描述】:

考虑这个固定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 错误吗? 或者,除非明确添加 always_inline,否则像 size() 这样的简单访问器会阻止自动矢量化吗?

【问题讨论】:

  • 编译器可以隐含地告诉我返回的值将会改变是我的猜测。现代编译器非常擅长找出将要发生的变化,在这种情况下,size 的返回值保证在所有情况下都会发生变化。
  • 另外将__attribute__((const)) 添加到size() 会导致应用自动矢量化(__attribute__((pure)) 不会)。
  • ++_size 放在类成员中会导致矢量化:godbolt.org/g/toBQc7 gcc 版本 5.x 和 6.x 也会生成矢量化代码:godbolt.org/g/wU6n8F
  • 这是一个错过的优化,与早期版本的编译器相比,gcc-7 中的回归 --> 请将其报告给 gcc 的 bugzilla。

标签: c++ performance gcc optimization auto-vectorization


【解决方案1】:

我可以缩小问题的范围。

双精度或单精度以及优化标志 -std=c++11 -Ofast -march=native:

版本 >= 5.0.0 的 Clang 使用 zmm 寄存器生成 AVX 移动指令

4.9

版本 >= 7.1.0 的 Gcc 使用 xmm 寄存器生成 AVX 移动指令

试试看:https://godbolt.org/g/NXgF4g

【讨论】:

    【解决方案2】:

    您示例中的循环针对 GCC 进行了矢量化,并且没有针对 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(...) 行放在末尾 - 可能是为了防止编译器丢弃整个循环,因为它在这个测试用例中没有明显的效果。如果我们只是返回v 的最后一个元素,我们可以达到相同的效果,而不会对v 的内存模型造成任何可能的副作用。

    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 和@987654340 @,注册。

    【讨论】:

    猜你喜欢
    • 2016-06-03
    • 2013-01-29
    • 2018-12-16
    • 2018-11-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-07
    相关资源
    最近更新 更多