Rozważ następującą funkcję:
void func(bool& flag)
{
if(!flag) flag=true;
}
Wydaje mi się, że jeśli flaga ma prawidłową wartość logiczną, byłoby to równoważne z bezwarunkowym ustawieniem jej na true
, na przykład:
void func(bool& flag)
{
flag=true;
}
Jednak ani gcc, ani clang nie optymalizują go w ten sposób - oba generują następujące informacje na -O3
poziomie optymalizacji:
_Z4funcRb:
.LFB0:
.cfi_startproc
cmp BYTE PTR [rdi], 0
jne .L1
mov BYTE PTR [rdi], 1
.L1:
rep ret
Moje pytanie brzmi: czy po prostu kod jest zbyt szczególny, aby go zoptymalizować, czy też istnieją dobre powody, dla których taka optymalizacja byłaby niepożądana, biorąc pod uwagę, że flag
nie jest to odniesienie volatile
? Wydaje się, że jedynym powodem, który może być, jest to, że flag
może w jakiś sposób mieć wartość inną niż - true
lub - false
bez niezdefiniowanego zachowania w momencie czytania, ale nie jestem pewien, czy jest to możliwe.
c++
optimization
Rusłan
źródło
źródło
1
użycie stałej czasowej kompilacji . godbolt.org/g/swe0tcOdpowiedzi:
Może to negatywnie wpłynąć na wydajność programu ze względu na kwestie spójności pamięci podręcznej . Pisanie do
flag
każdegofunc()
wywołania spowodowałoby zanieczyszczenie zawierającej ją linii pamięci podręcznej. Stanie się tak niezależnie od tego, że zapisywana wartość dokładnie odpowiada bitom znalezionym pod adresem docelowym przed zapisem.EDYTOWAĆ
hvd podało kolejny dobry powód, który uniemożliwia taką optymalizację. Jest to bardziej przekonujący argument przeciwko proponowanej optymalizacji, ponieważ może skutkować nieokreślonym zachowaniem, podczas gdy moja (oryginalna) odpowiedź dotyczyła tylko aspektów wydajności.
Po dłuższej refleksji mogę zaproponować jeszcze jeden przykład, dlaczego kompilatorom należy bezwzględnie zakazać - chyba że udowodnią, że transformacja jest bezpieczna w określonym kontekście - wprowadzania bezwarunkowego zapisu. Rozważ ten kod:
Z bezwarunkowym zapisem w
func()
ten sposób zdecydowanie wyzwala niezdefiniowane zachowanie (zapis do pamięci tylko do odczytu zakończy program, nawet jeśli w przeciwnym razie efekt zapisu byłby bez operacji).źródło
const
przekazania do funkcji, która może modyfikować dane będące źródłem niezdefiniowanego zachowania, a nie bezwarunkowy zapis. Doktorze, boli, kiedy to robię ...Oprócz odpowiedzi Leona na temat wydajności:
Przypuśćmy, że
flag
taktrue
. Załóżmy, że dwa wątki ciągle dzwoniąfunc(flag)
. Zapisana funkcja w tym przypadku nic nie przechowujeflag
, więc powinna być bezpieczna dla wątków. Dwa wątki mają dostęp do tej samej pamięci, ale tylko w celu jej odczytu. Bezwarunkowe ustawienieflag
natrue
oznacza, że dwa różne wątki będą zapisywać w tej samej pamięci. To nie jest bezpieczne, jest to niebezpieczne, nawet jeśli zapisywane dane są identyczne z danymi, które już tam są.źródło
[intro.races]/21
.0x01
do bajtu, który już jest,0x01
powoduje „niebezpieczne” zachowanie. W systemie z dostępem do pamięci typu słowo lub dword tak by się stało; ale optymalizator powinien być tego świadomy. Na nowoczesnym komputerze PC lub telefonie nie ma problemu. Więc to nie jest ważny powód.flag
jest to strona kopiowana przy zapisie. Teraz, na poziomie procesora, zachowanie może być zdefiniowane (błąd strony, niech system operacyjny to obsłuży), ale na poziomie systemu operacyjnego może być nadal niezdefiniowany, prawda?Nie jestem pewien co do zachowania C ++ tutaj, ale w C pamięć może się zmienić, ponieważ jeśli pamięć zawiera niezerową wartość inną niż 1, pozostałaby niezmieniona podczas sprawdzania, ale zmieniłaby się na 1 podczas sprawdzania.
Ale ponieważ nie jestem biegły w C ++, nie wiem, czy taka sytuacja jest w ogóle możliwa.
źródło
_Bool
?bool
/_Bool
i średnichtrue
, to w tym konkretnym ABI prawdopodobnie masz rację._Bool
i C ++bool
są tego samego typu lub są zgodne z tymi samymi regułami. W przypadku MSVC mają ten sam rozmiar i wyrównanie, ale nie ma oficjalnego oświadczenia, czy używają tych samych reguł.<stdbool.h>
zawiera atypedef _Bool bool;
I tak, na x86 (przynajmniej w ABI Systemu V),bool
/_Bool
muszą mieć wartość 0 lub 1, z wyczyszczonymi górnymi bitami bajtu. Nie sądzę, aby to wyjaśnienie było wiarygodne.func
został przekazany w RDI, podczas gdy Windows używałby RDX).