【问题标题】:Why does GCC -O3 cause infinite std::distance with filter iterators over a std::deque?为什么 GCC -O3 在 std::deque 上使用过滤器迭代器导致无限 std::distance?
【发布时间】:2017-01-18 09:10:48
【问题描述】:

在经历了很多痛苦和痛苦之后,我发现了一些非常奇怪的行为,当boost::filter_iterators 的范围超过std::deque 时,std::distance 永远不会返回。看来这个问题是 GCC (6.1+) 所独有的 -O3 优化。这是一个演示违规行为的示例:

#include <string>
#include <deque>
#include <iterator>
#include <iostream>

#include <boost/iterator/filter_iterator.hpp>

struct Foo
{
    std::string bar, s = "";
    char a = '\0';
};

int main()
{
    const std::deque<Foo> foos(14, {""});
    const std::string test {};
    const auto p = [test] (const auto& foo) { return foo.bar == test; };
    using boost::make_filter_iterator;
    const auto begin = make_filter_iterator(p, std::cbegin(foos), std::cend(foos));
    const auto end   = make_filter_iterator(p, std::cend(foos), std::cend(foos));
    std::cout << std::distance(begin, end) << std::endl;
}

一些观察:

  • 经过优化的 GCC -O2 或更少的预期回报。
  • Clang (3.8) 以任何优化级别返回正确答案。
  • std::deque 更改为 std::vectorstd::list 会导致预期的行为。
  • 14 很关键;少一点,问题就消失了。
  • sizeof(Foo) 很重要;删除 sa 会使问题消失。
  • 通过引用捕获test,或仅与常量表达式(例如foo.bar == " ")进行比较会导致正常行为。
  • 没有编译器警告(-Wall -Wextra -pedantic)。
  • Valgrind 未报告任何错误。
  • 使用fsanitize=undefined,问题就会消失。

发生了什么事?

【问题讨论】:

  • 你使用的是哪个版本的 boost
  • @M.M x86_64。这个问题也可以在coliru 上重现。不确定他们使用的是什么系统。
  • SLP1(矢量化)显然出了点问题......
  • 我想你可以把它报告给 gcc 的 bugzilla。
  • Bug reported。确认后将作为答案发布。

标签: c++ c++11 gcc deque filter-iterator


【解决方案1】:

此行为是由于矢量化优化错误导致的GCC bug。现在已经发布了一个修复程序,它应该出现在 GCC 6.3 中。

对于那些坚持使用 GCC 5.4 - 6.2 的用户,编译器选项 -fno-tree-slp-vectorize 将“修复”问题。

【讨论】:

    【解决方案2】:

    我认为下面的这些发现可能对改进错误报告以及在您的代码中用于解决问题都有用。

    通过调试优化的输出并使用优化标志和少量代码更改,我得出了关于导致错误的特定优化标志的结论。

    选项集是:

    -O -fno-auto-inc-dec -fno-branch-count-reg -fno-combine-stack-adjustments -fno-compare-elim -fno-cprop-registers -fno-dce -fno-defer-pop -fno-delayed-branch -fno-dse -fno-forward-propagate -fno-guess-branch-probability -fno-if-conversion2 -fno-if-conversion -fno-inline-functions-called-once -fno-ipa-pure-const -fno-ipa-profile -fno-ipa-reference -fno-merge-constants -fno-move-loop-invariants -fno-reorder-blocks -fno-shrink-wrap -fno-split-wide-types -fno-ssa-backprop -fno-ssa-phiopt -fno-tree-bit-ccp -fno-tree-ccp -fno-tree-ch -fno-tree-coalesce-vars -fno-tree-phiprop -fno-tree-sink -fno-tree-slsr -fno-tree-dse -fno-tree-forwprop -fno-tree-fre -fno-unit-at-a-time -fno-tree-ter -fno-tree-sra -fno-tree-copy-prop -fstrict-aliasing -ftree-slp-vectorize -std=c++14
    

    很抱歉这么长的设置,但我真正想要的是:-O0 -ftree-copy-prop -ftree-pta -ftree-dce -fstrict-aliasing -ftree-slp-vectorize(我也尝试过 -Og)加上 O1 的神奇步骤......

    请注意,-O3 -f-no-tree-slp-vectorize 已经修复了该行为,但是通过使用我发送的完整选项,调试几乎很容易...

    此外,运算符==(string, string) 的存在似乎在编译器中产生了混乱。

    如果您检查粘贴在下面的所有由#if 0 代码注释的代码,当激活时可以代替原始代码工作,您可能会发现我没有发现的问题。

    请注意,==() 运算符甚至没有被调用,因为 foo.a != '\0' 在测试中始终为真。因此看起来它的存在正在使编译器生成错误的代码。

    还要注意,循环内的任何注释代码也会将行为更改为预期的行为,这就是为什么我怀疑初学者的矢量化标志。

    #include <string>
    #include <deque>
    #include <iterator>
    #include <iostream>
    
    #include <boost/iterator/filter_iterator.hpp>
    #include <string.h>
    
    struct Foo
    {
        std::string bar, s = "";
        char a = 'n';
    };
    
    std::ostream& operator<<(std::ostream& os, const Foo& f)
    {
        os << f.bar << '/' << f.a;
        return os;
    }
    
    int main()
    {
        std::deque<Foo> foos(14, {"abc"});
        const std::string test {"abc"};
        Foo other;
        other.bar = "last"; other.a = 'l';
        foos.push_back(other);
        other.bar = "first";
        other.a = 'f';
        foos.push_front(other);
        //
    #if 0
        const auto p = [test] (const auto& foo) { return foo.a != '\0'; };
    #elif 0
        const auto p = [test] (const auto& foo) {
            bool  rc =  (foo.a != '\0');
            if (!rc)
                rc = (foo.bar == std::string(test));
            return rc;
        };
    #elif 1
        const auto p = [test] (const auto& foo) {
            bool  rc =  (foo.a != '\0');
            if (!rc)
                rc = (foo.bar == test);
            return rc;
        };
    #endif
        using boost::make_filter_iterator;
        const auto begin = make_filter_iterator(p, std::cbegin(foos), std::cend(foos));
        const auto end   = make_filter_iterator(p, std::cend(foos), std::cend(foos));
        std::cout << std::distance(end, end) << std::endl;
        std::cout << std::distance(begin, begin) << std::endl;
        std::cout << std::distance(std::cbegin(foos), std::cend(foos)) << std::endl;
    
        auto __first = begin;
        auto __last = end;
    
        int __n = 0;
        //std::cout << __last << std::endl;
        //std::deque<char> trace;
        //Foo trace[21];
        const int max = foos.size();
        char trace[max+5]; memset(trace, 'c', sizeof(trace));
    
        std::cout << max << std::endl;
        std::cout << *__last << std::endl;
    
        while (__first != __last)
        {
            trace[__n] = (*__first).a;
            //trace[__n] = (*__first);
            //trace.push_back((*__first).a);
            //std::cout << *__first << std::endl;
            ++__n;
            ++__first;
            if (__n > max + 5)
                break;
            //std::cout << __n << std::endl;
            //std::cout << (__first != __last) << std::endl;
        }
    
        for (auto f: trace)
            std::cout << f  << std::endl;
        std::cout << "Tadaaaaa: " <<  __n << std::endl;
    
        //std::cout << std::distance(begin, end) << std::endl;
    
    }
    

    【讨论】:

      猜你喜欢
      • 2015-11-01
      • 2015-04-29
      • 2020-09-24
      • 1970-01-01
      • 2012-11-24
      • 2017-04-15
      • 2012-03-26
      • 1970-01-01
      • 2018-08-13
      相关资源
      最近更新 更多