2013-04-22 16 views
7

Po pierwsze: wiem, że większość błędów optymalizacyjnych wynika z błędów programistycznych lub z faktów, które mogą się zmieniać w zależności od ustawień optymalizacyjnych (wartości zmiennoprzecinkowe, problemy z wielowątkowością, ...).Błąd optymalizatora lub błąd programowania?

Jednak doświadczyłem bardzo trudnego do znalezienia błędu i nie jestem pewien, czy istnieje sposób, aby zapobiec występowaniu tego rodzaju błędów, bez wyłączania optymalizacji. Czy czegoś brakuje? Czy to naprawdę może być błąd optymalizatora? Oto uproszczony przykład:

struct Data { 
    int a; 
    int b; 
    double c; 
}; 

struct Test { 
    void optimizeMe(); 

    Data m_data; 
}; 

void Test::optimizeMe() { 
    Data * pData; // Note that this pointer is not initialized! 

    bool first = true; 

    for (int i = 0; i < 3; ++i) { 
    if (first) { 
     first = false; 

     pData = &m_data; 

     pData->a = i * 10; 
     pData->b = i * pData->a; 
     pData->c = pData->b/2; 
    } else { 
     pData->a = ++i; 
    } // end if 
    } // end for 
}; 

int main(int argc, char *argv[]) { 
    Test test; 
    test.optimizeMe(); 
    return 0; 
} 

Rzeczywisty program oczywiście ma o wiele więcej do zrobienia niż to. Wszystko sprowadza się jednak do tego, że zamiast bezpośredniego dostępu do m_data używany jest wskaźnik (poprzednio ujednolicony). Jak tylko dodać wystarczającą oświadczenia do if (first) -part optymalizator wydaje się, aby zmienić kod na coś wzdłuż tych linii:

if (first) { 
    first = false; 

    // pData-assignment has been removed! 

    m_data.a = i * 10; 
    m_data.b = i * m_data.a; 
    m_data.c = m_data.b/m_data.a; 
} else { 
    pData->a = ++i; // This will crash - pData is not set yet. 
} // end if 

Jak widać, zastępuje niepotrzebnego wskaźnik dereference z bezpośredniego zapisu do struktura członkowska. Jednak nie robi tego w gałęzi else. Usuwa również funkcję-pData. Ponieważ wskaźnik jest teraz nadal unitaryzowany, program ulegnie awarii w gałęzi else.

Oczywiście istnieje wiele rzeczy, które można poprawić tutaj, więc można zrzucić winę na programatorze:

  • Zapomnij o wskaźnik i robić to, co robi optymalizator - użyj m_data bezpośrednio.
  • Zainicjuj pData na nullptr - w ten sposób optymalizator wie, że else -branch zakończy się niepowodzeniem, jeśli wskaźnik nie zostanie nigdy przypisany. Przynajmniej wydaje się, że rozwiązuje problem w moim środowisku testowym.
  • Przenieś przypisanie wskaźnika przed pętlą (efektywnie inicjując pData z &m_data, która może być również referencją zamiast wskaźnika (dla dokładnej miary) .To ma sens, ponieważ pData jest potrzebna we wszystkich przypadkach, więc nie ma potrzeby powód, aby to zrobić wewnątrz pętli

Kod jest oczywiście śmierdzący, co najmniej, a ja nie staram się „winić” optymalizator za robienie tego, ale pytam:.. Co am I robić niewłaściwy? Program może być brzydki, ale to ważny kod ...

należy dodać, że używam VS2012 z C + +/CLI i v110_xp-Zestaw narzędzi. Optymalizacja jest ustawiona na/O2. Zwróć też uwagę, że jeśli naprawdę chcesz odtworzyć problem (nie jest to naprawdę kwestia tego pytania), musisz pogodzić się ze złożonością programu. Jest to bardzo uproszczony przykład, a optymalizator czasami nie usuwa przypisania wskaźnika. Ukrywanie &m_data za funkcją wydaje się "pomagać".

EDIT:

Q: Skąd mam wiedzieć, że kompilator optymalizuje go do czegoś podobnego przykładu warunkiem?

A: Nie jestem bardzo dobry w czytaniu asemblera, I spojrzał na nią jednak i dokonały 3 obserwacje, które sprawiają mi uwierzyć, że to zachowuje się w ten sposób:

  1. Jak tylko kopnięcia optymalizacja (dodawanie kolejnych przydziałów zwykle wykonuje lewę) przypisanie wskaźnika nie ma powiązanej instrukcji asemblera. Nie został również przeniesiony do deklaracji, więc tak naprawdę pozostało niezainicjalizowane (przynajmniej dla mnie).
  2. W przypadkach, gdy program się zawiesza, debugger pomija instrukcję przypisania. W przypadkach, gdy program działa bezproblemowo, debugger zatrzymuje się w tym miejscu.
  3. Gdybym oglądać zawartość pData oraz zawartości m_data podczas debugowania, to wyraźnie pokazuje, że wszystkie zadania w if -branch mieć wpływ na m_data i m_data odbiera poprawne wartości. Sam wskaźnik nadal wskazuje na niezainicjowaną wartość od początku. Dlatego muszę założyć, że w rzeczywistości nie używa on wskaźnika do wykonania zadań.

P: Czy musi coś zrobić z i (rozwijanie pętli)?

A: Nie, rzeczywisty program faktycznie używa do {...} while(), aby zapętlić zestaw wyników SQL SELECT, więc liczba iteracji jest całkowicie specyficzna dla środowiska wykonawczego i nie może być z góry określona przez kompilator.

+0

Zredukuj to do [SSCCE] (http://sscce.org). – djechlin

+7

@dłamlin: Błędy z optymalizacją mogą być trudne do zredukowania do małych próbek kodu. Pytanie jednoznacznie stwierdza, że ​​jest to już uproszczony przykład. –

+0

nie potrafię jeszcze jednoznacznie wyjaśnić pomysłu, ale czy działa on tak samo, jeśli tworzysz klasę zamiast struct? – evilruff

Odpowiedz

5

To na pewno wygląda jak błąd. Optymalizator może wyeliminować niepotrzebne przekierowanie, ale nie powinien eliminować przypisania do pData.

Oczywiście można obejść problem, przypisując do pData przed pętlą (przynajmniej w tym prostym przykładzie). Rozumiem, że problem w twoim rzeczywistym kodzie nie jest tak łatwy do rozwiązania.

+1

Właściwe usunięcie wskaźnika jako całości było rozwiązaniem w aktualnym kodzie. Kod ma ponad 15 lat i większość takich błędów jest łatwa do rozwiązania, ponieważ kod wymaga prostego refaktoryzacji. Znalezienie błędu to zupełnie inna historia. – Excelcius

+0

Ponieważ jestem dość pewna, że ​​to był błąd kompilatora, zaakceptuję to jako odpowiedź. Jak tylko wypełniłem zgłoszenie błędu w systemie Microsoft, dodam link do tego pytania, więc będzie dostępny prawdziwy, możliwy do odtworzenia przykład. Myślę, że jest to raczej coś w stylu C++/CLI, ale normalny kompilator nie zepsuł się. – Excelcius

1

Głosuję również za błędem optymalizatora, jeśli jest naprawdę odtwarzalny w tym przykładzie. Aby anulować optymalizator, możesz spróbować zadeklarować pData jako volatile.

Powiązane problemy