2016-09-10 44 views
37

Po wielu bólach i nędzy, wyśledziłem kilka bardzo dziwnych zachowań, w których std::distance nigdy nie wróci, gdy otrzymają zakres boost::filter_iterator s ponad std::deque. Wydaje się, że problem dotyczy tylko GCC (6.1+) z optymalizacją -O3. Oto przykład wykazanie zachowania naruszające:Dlaczego GCC -O3 powoduje nieskończoną std :: odległość z iteratorami filtru ponad std :: deque?

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

Niektóre obserwacje:

  • z GCC optymalizacji -O2 lub mniej zwrotów, jak oczekiwano.
  • Clang (3.8) zwraca prawidłową odpowiedź z dowolnym poziomem optymalizacji.
  • Zmiana std::deque na std::vector lub std::list powoduje oczekiwane zachowanie.
  • Najważniejsze jest ustawienie 14; cokolwiek mniej, a problem znika.
  • Ważne jest sizeof(Foo); usunięcie s lub a powoduje, że problem znika.
  • Przechwytywanie test przez odniesienie lub po prostu w porównaniu do stałego wyrażenia (np. foo.bar == " ") powoduje normalne zachowanie.
  • Brak ostrzeżeń kompilatora (z -Wall -Wextra -pedantic).
  • Valgrind nie zgłasza żadnych błędów.
  • Użyj fsanitize=undefined, a problem zniknie.

Co się dzieje?

+1

Której wersji wzmocnienia używasz –

+1

@ M.M doładowania 1.61. – Daniel

+0

Również na jakiej platformie jesteś? (i686? x86_84?) –

Odpowiedz

3

Takie zachowanie było spowodowane GCC bug spowodowane złym optymalizacji wektoryzacji. Została wydana poprawka, która powinna pojawić się w GCC 6.3.

Dla tych, którzy utknęli przy GCC 5.4 - 6.2, opcja kompilatora -fno-tree-slp-vectorize "rozwiąże" problem.

4

Uważam, że poniższe odkrycia mogą być przydatne zarówno do poprawy raportu o błędzie, jak i do wykorzystania go w kodzie w celu obejścia problemu.

Debugując zoptymalizowane dane wyjściowe i korzystając z flag optymalizacyjnych oraz z drobnymi zmianami w kodzie, doszedłem do wniosku o konkretnych flagach optymalizacyjnych, które powodują błąd.

Zestaw opcji są:

-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 

Niestety tak długo ustawione ale co ja właściwie chciałem było coś takiego: -O0 -ftree-copy-prop -ftree-pta -ftree-dce -fstrict-aliasing -ftree-slp-vectorize (Próbowałem też z -OG) plus magicznych krokach O1 .. .

Należy pamiętać, że tylko -O3 -f-no-tree-slp-vectorize będzie już rozwiązać ten problem, ale za pomocą pełnych możliwości Wysłałem debugowanie jest prawie proste ...

Ponadto, wygląda istnienia operatora ==(string, string) jest generowanie zamieszanie w kompilatorze.

Jeśli przyjrzeć się kodowi wklejonemu poniżej, gdzie wszystkie skomentowane przez kod #if 0, po aktywacji działa w miejscu oryginalnego kodu, może znaleźć problem, gdzie nie.

Należy zauważyć, że operator ==() nie jest nawet wywoływany, ponieważ foo.a != '\0' jest zawsze prawdziwy w teście. Wygląda więc na to, że jego istnienie sprawia, że ​​kompilator generuje zły kod.

Należy również zauważyć, że dowolny z komentowanych kodów wewnątrz pętli również zmienia zachowanie do oczekiwanego i dlatego podejrzewałem o flagi wektoryzacji dla starterów.

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

}