2017-10-27 78 views
6

Zoptymalizowałem kod C++, w którym napotkałem sytuację, którą można uprościć w następujący sposób.Efekt optymalizacji gcc na pętlach z pozornie stałą zmienną

Rozważmy następujący kod:

#include <iostream> 
#include <thread> 

using namespace std; 

bool hit = false; 

void F() 
{ 
    this_thread::sleep_for(chrono::seconds(1)); 
    hit = true; 
} 

int main() 
{ 
    thread t(F); 

    while (!hit) 
     ; 

    cout << "finished" << endl; 
    t.join(); 
    return 0; 
} 

Zasadniczo rozpoczyna wątek, który po chwili zmieni wartość hit do true. W tym samym czasie kod wchodzi w pustą pętlę, która będzie kontynuowana, dopóki wartość hit nie zmieni się na true. Skompilowałem to przy pomocy gcc-5.4 przy użyciu flagi -g i wszystko było w porządku. Kod wyświetli kod finished i zakończy się. Ale potem skompilowałem go z flagą -O2 i tym razem kod utknął w pętli w nieskończoność.

Patrząc na demontażu, kompilator wygenerował następujące dane, które jest przyczyną nieskończonej pętli:

jmp 0x6ba6f3! 0x00000000006ba6f3

OK, więc wyraźnie, kompilator wywnioskować, że hit „s wartość false i to nie zmieni się w pętli, więc dlaczego nie założyć, że jest to pętla nieskończona, nie biorąc pod uwagę, że inny wątek może zmienić swoją wartość ! Ten tryb optymalizacji jest dodawany na wyższym poziomie (-O2). Ponieważ nie jestem ekspertem od optymalizacji, czy ktoś może mi powiedzieć, który z nich jest odpowiedzialny za ten wynik, więc mogę go wyłączyć? A wyłączenie go ma jakieś poważne koszty wydajności dla innych kawałków kodu? Chodzi mi o to, jak bardzo ten wzór kodu jest rzadki?

+1

użyj 'std :: atomic '. – Jarod42

+2

Co się stanie, jeśli zadeklarujesz "trafienie" jako niestabilne? – Milack27

+0

@ Milack27 tak, to rozwiązuje problem! Człowieku, jest wiele rzeczy w C++, o których wciąż nie wiem! – Sinapse

Odpowiedz

6

Ten kod ma niezdefiniowane zachowanie. Modyfikujesz hit z jednego wątku i czytasz go z innego, bez synchronizacji.

Optymalizowanie hit na false jest prawidłowym wynikiem niezdefiniowanego zachowania. Możesz rozwiązać ten problem, tworząc hit a std::atomic<bool>. Powoduje to, że jest dobrze zdefiniowany i blokuje optymalizację.

+0

Tak, to też rozwiąże problem (myślę, że lotny zrobi to wydajniej w moim przypadku), ale OMG !! W jaki sposób kompilator wie, kiedy definiujesz zmienną jako atomową ?! Mam na myśli lotny sens, skoro dodajesz kwalifikator, ale w przypadku std :: atomic, jak kompilator wywnioskuje, że może się zmienić w innym wątku? – Sinapse

+0

Zgadnij co! W klasie 'std :: atomic ' występują zmienne elementy. :) Spróbuj otworzyć plik "atomowy" z katalogu g ++ włącznie, możesz go tam zobaczyć. – Milack27

+0

Fajnie, to było godne! – Sinapse

2

Jeśli chcesz odczytać/zapisać hit z kilku wątków w tym samym czasie, potrzebujesz synchronizacji, w przeciwnym razie wprowadzisz warunek wyścigu. Można albo dokonać hit i std::atomic<bool> lub dodać mutex, która musi być zablokowana podczas uzyskiwania dostępu do wartości hit. Jeśli chcesz tylko poczekać, aż wątek zakończy pracę, możesz pozostawić tylko thread.join() (i wydrukować "gotowy" po nim) bez wprowadzania żadnych dodatkowych flag.

1

Zgłaszając hit jako zmienny, mówisz kompilatorowi, że zmienna ta może być modyfikowana przez czynniki zewnętrzne w dowolnym momencie, więc kompilator nie zakłada, że ​​jej wartość nie zmieni się wzdłuż funkcji main.

Dopóki istnieje tylko jeden wątek zapisujący do zmiennej hit, kod powinien działać poprawnie, bez żadnych warunków wyścigowych. Jednakże, gdy mamy do czynienia z wieloma wątkami, zawsze bezpieczniej jest używać narzędzi synchronizujących, takich jak obiekty atomowe, muteksy i semafory, jak już wspomniano w innych odpowiedziach tutaj.

+0

'volatile' [nie zapewnia niezbędnego zamówienia] (https://stackoverflow.com/a/4558031/15416). – MSalters

+0

Tak. Jak już powiedziałem, zawsze bezpieczniej jest używać narzędzi synchronizujących_. Używanie 'volatile' powinno działać poprawnie w tym przypadku. – Milack27

Powiązane problemy