2016-09-10 4 views
37

많은 고통과 불행을 겪은 후 std::distanceboost::filter_iterator의 범위가 std::deque을 넘을 때 절대 반환하지 않는 매우 이상한 행동을 추적했습니다. 문제는 -O3 개의 최적화가있는 GCC (6.1+)에서만 발생합니다.왜 GCC -O3은 std :: deque 필터 반복자로 무한 std :: distance를 발생 시키는가?

#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::dequestd::vector 또는 std::list으로 변경하면 예상되는 결과가 발생합니다.
  • 14은 중요합니다. 아무것도 덜하고 문제가 사라집니다.
  • sizeof(Foo)은 중요합니다. s 또는 a을 제거하면 문제가 해결됩니다. 참고로 test을 캡처하거나, 상수 표현 (예 : foo.bar == " ")과 비교하면 정상적인 동작을하게됩니다.
  • 컴파일러 경고가 없습니다 (-Wall -Wextra -pedantic).
  • Valgrind는 오류를보고하지 않습니다.
  • fsanitize=undefined을 사용하면 문제가 해결됩니다.

무슨 일 이니?

+1

어떤 부스트 버전을 사용하고 있습니까 –

+1

@ M.M boost 1.61. – Daniel

+0

또한 어떤 플랫폼을 사용하고 계십니까? (i686-x86_84?) –

답변

3

이 문제는 나쁜 vectorisation 최적화에 의한 GCC bug 때문이었다. GCC 6.3에 나와있는 수정본이 발행되었습니다.

컴파일러 옵션 -fno-tree-slp-vectorize은 GCC 5.4 - 6.2와 관련되어있는 경우 문제를 '수정'합니다.

4

아래의 결과는 버그 리포트를 개선하고 문제 해결을 위해 코드를 사용하는 데 유용 할 것으로 생각됩니다.

최적화 된 출력을 디버깅하고 최적화 플래그 및 마이너 코드 변경 사항을 재생하여 오류의 원인이되는 특정 최적화 플래그에 대한 결론에 도달했습니다.

옵션의 설정은 다음과 같습니다

-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 

그 시간을 설정할 죄송하지만 실제로 원하는 것은 같은 것이었다 : O1의 -O0 -ftree-copy-prop -ftree-pta -ftree-dce -fstrict-aliasing -ftree-slp-vectorize (나는 또한 -OG으로 시도했다)을 더한 마법의 단계를 .. . 단지 -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; 

}