问题 为什么GCC优化不适用于valarrays?


这是一个使用valarrays的简单c ++程序:

#include <iostream>
#include <valarray>

int main() {
    using ratios_t = std::valarray<float>;

    ratios_t a{0.5, 1, 2};
    const auto& res ( ratios_t::value_type(256) / a );
    for(const auto& r : ratios_t{res})
        std::cout << r << " " << std::endl;
    return 0;  
}

如果我编译并运行它像这样:

g++ -O0 main.cpp && ./a.out

输出符合预期:

512 256 128 

但是,如果我编译并运行它:

g++ -O3 main.cpp && ./a.out

输出是:

0 0 0 

如果我使用相同的话 -O1 优化参数。

GCC版本是(最新的Archlinux):

$ g++ --version
g++ (GCC) 6.1.1 20160707

但是,如果我尝试使用clang,两者都可以

clang++ -std=gnu++14 -O0 main.cpp && ./a.out

clang++ -std=gnu++14 -O3 main.cpp && ./a.out

产生相同的正确结果:

512 256 128 

Clang版本是:

$ clang++ --version
clang version 3.8.0 (tags/RELEASE_380/final)

我也尝试过在Debian上使用GCC 4.9.2,其中可执行文件产生正确的结果。

这是GCC中可能存在的错误还是我做错了什么?任何人都可以重现这个吗?

编辑:我设法在Mac OS上的Homebrew版本的GCC 6上重现了这个问题。


3966
2017-07-13 14:28


起源

运用 melpon.org/wandbox 似乎行为从4.9.3变为5.1。 - NathanOliver
不幸的是,在我的代码库中,我也设法重现了类似的问题(但是使用uint32_t)甚至在GCC 4.9.3上,但是当它放在最小的例子中时它可以工作。我正在调查...... - DoDo


答案:


valarray 和 auto 不要混合好。

这会创建一个临时对象,然后应用 operator/ 它:

const auto& res ( ratios_t::value_type(256) / a );

libstdc ++ valarray 使用表达式模板,以便 operator/ 返回一个轻量级对象,它引用原始参数并懒惰地计算它们。你用 const auto& 这会导致表达式模板绑定到引用,但不会延长表达式模板引用的临时表的生命周期,因此当评估发生时,临时表已超出范围,并且其内存已被重用。

如果您这样做,它将正常工作:

ratios_t res = ratios_t::value_type(256) / a;

更新: 截至今天,GCC主干将给出这个例子的预期结果。我修改了我们的 valarray 表达式模板容易出错,因此创建悬空引用更难(但仍然不是不可能)。新的实施应该包括在明年的GCC 9中。


6
2017-07-13 16:28



谢谢你的详细解答。我会接受的。我只想指出即使我写 auto res (ratios_t::value_type(256)/a),在优化开启的情况下,我仍然得到相同的结果。但是,您的建议有效(即不使用 auto)。使用clang和msvc原始代码都能正常工作。 - DoDo
是的,因为 auto 推导出包含对过期临时的引用的表达式模板的类型。你需要明确地创建一个 valarray,迫使评估在临时消失之前发生。就像我说的, auto 和 valarray 不要混合好。 - Jonathan Wakely
gcc.gnu.org/bugzilla/show_bug.cgi?id=57997 是一个非常相似的问题。 - Jonathan Wakely


这是粗心实施的结果 operator/ (const T& val, const std::valarray<T>& rhs) (并且很可能是使用valarrays的其他运算符)使用延迟评估:

#include <iostream>
#include <valarray>

int main() {
    using ratios_t = std::valarray<float>;

    ratios_t a{0.5, 1, 2};
    float x = 256;
    const auto& res ( x / a );
    // x = 512;  //  <-- uncommenting this line affects the output
    for(const auto& r : ratios_t{res})
        std::cout << r << " ";
    return 0;
}

随着“x = 512“线条评论说,输出是

512 256 128 

取消注释该行并将输出更改为

1024 512 256 

因为在您的示例中,除法运算符的左侧参数是临时的,结果是未定义的。

UPDATE

乔纳森威克利 正确地 指出,由于使用,基于惰性评估的实现在该示例中成为问题 auto


3
2017-07-13 16:22



谢谢您的回答。乔纳森是第一个,所以我接受了他的答案,但你的信息也非常丰富。太糟糕SO不会让你接受多个答案:-)。 - DoDo