簡體   English   中英

為什么GCC -O3與std :: deque上的過濾器迭代器導致無限的std :: distance?

[英]Why does GCC -O3 cause infinite std::distance with filter iterators over a std::deque?

在經歷了很多痛苦和痛苦之后,我已經找到了一些非常奇怪的行為,當std::deque給出一系列boost::filter_iterator時, 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;
}

一些觀察:

  • 優化率為-O2或更低的GCC按預期返回。
  • Clang(3.8)返回任何優化級別的正確答案。
  • std::deque更改為std::vectorstd::list導致預期的行為。
  • 14是至關重要的; 什么都沒有,問題就消失了。
  • sizeof(Foo)很重要; 刪除sa會使問題消失。
  • 通過引用捕獲test ,或者只是與常量表達式(例如foo.bar == " " )進行比較會導致正常行為。
  • 沒有編譯器警告(使用-Wall -Wextra -pedantic )。
  • Valgrind報告沒有錯誤。
  • 使用fsanitize=undefined ,問題就消失了。

這是怎么回事?

我認為下面的這些發現可能對改進錯誤報告以及在代碼中使用以解決問題都很有用。

通過調試優化輸出並使用優化標志並進行少量代碼更改,我得出了導致錯誤的特定優化標志的結論。

選項集是:

-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'在測試中始終為true。 因此看起來它的存在正在使編譯器生成錯誤的代碼。

另請注意,循環中的任何注釋代碼也會將行為更改為預期的行為,這就是我懷疑啟動器的矢量化標志的原因。

#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;

}

這種行為是由於錯誤的矢量化優化導致的GCC錯誤 現已發布修正案,該修正案應出現在GCC 6.3中。

對於那些堅持使用GCC 5.4 - 6.2的人來說,編譯器選項-fno-tree-slp-vectorize將“解決”這個問題。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM