Której wartości lepiej użyć? Wartość logiczna prawda czy liczba całkowita 1?
Powyższy temat mnie zrobić kilka eksperymentów z bool
i int
w if
stanie. Tak więc z ciekawości napisałem ten program:
int f(int i)
{
if ( i ) return 99; //if(int)
else return -99;
}
int g(bool b)
{
if ( b ) return 99; //if(bool)
else return -99;
}
int main(){}
g++ intbool.cpp -S
generuje kod asm dla każdej funkcji w następujący sposób:
kod asm dla
f(int)
__Z1fi: LFB0: pushl %ebp LCFI0: movl %esp, %ebp LCFI1: cmpl $0, 8(%ebp) je L2 movl $99, %eax jmp L3 L2: movl $-99, %eax L3: leave LCFI2: ret
kod asm dla
g(bool)
__Z1gb: LFB1: pushl %ebp LCFI3: movl %esp, %ebp LCFI4: subl $4, %esp LCFI5: movl 8(%ebp), %eax movb %al, -4(%ebp) cmpb $0, -4(%ebp) je L5 movl $99, %eax jmp L6 L5: movl $-99, %eax L6: leave LCFI6: ret
O dziwo, g(bool)
generuje więcej asm
instrukcji! Czy to znaczy, że if(bool)
jest trochę wolniejsze niż if(int)
? Kiedyś uważałem, że bool
jest specjalnie zaprojektowany do użycia w instrukcjach warunkowych, takich jak if
, więc spodziewałem g(bool)
się wygenerować mniej instrukcji asm, dzięki czemu będę g(bool)
bardziej wydajny i szybki.
EDYTOWAĆ:
Na razie nie używam żadnej flagi optymalizacji. Ale nawet jego brak, dlaczego generuje więcej asm, g(bool)
jest pytaniem, na które szukam rozsądnej odpowiedzi. Powinienem też powiedzieć, że -O2
flaga optymalizacji generuje dokładnie to samo co m. Ale to nie jest pytanie. Pytanie brzmi, o co zadałem.
g(bool)
jest pytaniem, na które szukam rozsądnej odpowiedzi.Odpowiedzi:
Dla mnie to ma sens. Twój kompilator najwyraźniej definiuje wartość a
bool
jako wartość 8-bitową, a systemowy ABI wymaga, aby „promował” małe (<32-bitowe) argumenty liczb całkowitych do 32-bitowych, gdy są one umieszczane na stosie wywołań. Aby porównać abool
, kompilator generuje kod, aby wyodrębnić najmniej znaczący bajt z 32-bitowego argumentu, który otrzymuje g, i porównuje go zcmpb
. W pierwszym przykładzieint
argument używa pełnych 32 bitów, które zostały odłożone na stos, więc po prostu porównuje go z całością zcmpl
.źródło
__int64
szybszych niżint
? A może procesor obsługuje osobno 32-bitowe liczby całkowite z 32-bitowymi zestawami instrukcji?Kompilowanie z
-03
daje mi:fa:
pushl %ebp movl %esp, %ebp cmpl $1, 8(%ebp) popl %ebp sbbl %eax, %eax andb $58, %al addl $99, %eax ret
sol:
pushl %ebp movl %esp, %ebp cmpb $1, 8(%ebp) popl %ebp sbbl %eax, %eax andb $58, %al addl $99, %eax ret
.. tak zestawia się zasadniczo ten sam kod, za wyjątkiem
cmpl
vscmpb
. Oznacza to, że różnica, jeśli istnieje, nie ma znaczenia. Ocenianie po niezoptymalizowanym kodzie jest niesprawiedliwe.Edytuj, aby wyjaśnić mój punkt widzenia. Niezoptymalizowany kod służy do prostego debugowania, a nie do szybkości. Porównywanie szybkości niezoptymalizowanego kodu jest bezsensowne.
źródło
cmpl
do jednego icmpb
do drugiego?bool
to jeden bajt, a anint
to cztery. Nie sądzę, żeby było coś bardziej wyjątkowego.bool
jako typ 8-bitowy.char
, który z definicji jest bajtem i jest najmniejszą adresowalną jednostką.bool
Rozmiar jest zdefiniowany w implementacji i może wynosić 1, 4 lub 8 lub cokolwiek. Jednak kompilatory mają tendencję do tego.Kiedy kompiluję to z rozsądnym zestawem opcji (konkretnie -O3), oto co otrzymuję:
Dla
f()
:.type _Z1fi, @function _Z1fi: .LFB0: .cfi_startproc .cfi_personality 0x3,__gxx_personality_v0 cmpl $1, %edi sbbl %eax, %eax andb $58, %al addl $99, %eax ret .cfi_endproc
Dla
g()
:.type _Z1gb, @function _Z1gb: .LFB1: .cfi_startproc .cfi_personality 0x3,__gxx_personality_v0 cmpb $1, %dil sbbl %eax, %eax andb $58, %al addl $99, %eax ret .cfi_endproc
Nadal używają innych instrukcji do porównania (
cmpb
dla wartości logicznych vs.cmpl
dla int), ale poza tym ciała są identyczne. Szybkie spojrzenie na podręczniki Intela mówi mi: ... niewiele. Nie ma czegoś takiego jakcmpb
lubcmpl
w podręcznikach Intela. To wszystkocmp
i nie mogę w tej chwili znaleźć tabel czasowych. Domyślam się jednak, że nie ma różnicy w zegarze między porównaniem bajtu natychmiastowego i długiego natychmiastowego, więc ze wszystkich praktycznych powodów kod jest identyczny.edytowane, aby dodać następujące na podstawie dodanego tekstu
Powodem, dla którego kod jest inny w niezoptymalizowanym przypadku, jest to, że jest niezoptymalizowany. (Tak, to jest cykliczne, wiem.) Kiedy kompilator przechodzi przez AST i bezpośrednio generuje kod, nie „wie” niczego poza tym, co jest w bezpośrednim punkcie AST, w którym się znajduje. W tym momencie brakuje mu wszystkich potrzebnych informacji kontekstowych aby wiedzieć, że w tym konkretnym momencie może traktować zadeklarowany typ
bool
jakoint
. Wartość logiczna jest oczywiście domyślnie traktowana jako bajt i podczas manipulowania bajtami w świecie Intela musisz zrobić takie rzeczy, jak rozszerzenie znaku, aby uzyskać określone szerokości, aby umieścić je na stosie itp. (Nie możesz przesunąć bajtu .)Kiedy jednak optymalizator ogląda AST i wykonuje swoją magię, patrzy na otaczający kontekst i „wie”, kiedy może zastąpić kod czymś bardziej wydajnym bez zmiany semantyki. Więc „wie”, że może użyć liczby całkowitej w parametrze i tym samym stracić niepotrzebne konwersje i poszerzenie.
źródło
l
ib
są sufiksami używanymi tylko w składni AT&T. Po prostu odnoszą się do wersjicmp
używających odpowiednio 4-bajtowych (long) i 1-bajtowych (bajtowych) operandów. W przypadku, gdy istnieje jakiekolwiek niejasności składni Intel, Tradycyjnie argumentu pamięci jest oznaczonyBYTE PTR
,WORD PTR
lubDWORD PTR
zamiast zawieszania sufiks kodu maszynowego.cmp
mają taką samą wydajność i nie ma kar za częściowy rejestr do odczytu%dil
. (Ale to nie przeszkadza w zabawnym tworzeniu przeciągnięcia częściowego rejestru przy użyciu rozmiaru bajtówand
na AL jako części bez rozgałęzienia przełączania wielkości liter między 99 a -99.)GCC 4.5 na Linux i Windows co najmniej,
sizeof(bool) == 1
. Na x86 i x86_64 nie możesz przekazać funkcji mniejszej niż wartość rejestru ogólnego przeznaczenia (czy to przez stos, czy przez rejestr w zależności od konwencji wywoływania itp.).Więc kod bool, gdy nie jest zoptymalizowany, w rzeczywistości idzie do pewnej długości, aby wyodrębnić tę wartość bool ze stosu argumentów (używając innego miejsca stosu do zapisania tego bajtu). Jest to bardziej skomplikowane niż zwykłe pobieranie natywnej zmiennej wielkości rejestru.
źródło
sizeof(bool)
isizeof(wchar_t)
są zdefiniowane w ramach implementacji ” . To powiedzeniesizeof(bool) == 1
nie jest do końca poprawne, chyba że mówisz o konkretnej wersji konkretnego kompilatora.Na poziomie maszyny nie ma czegoś takiego jak bool
Bardzo niewiele architektur zestawów instrukcji definiuje dowolny rodzaj argumentu logicznego, chociaż często istnieją instrukcje, które wyzwalają akcję na wartościach niezerowych. Dla procesora zwykle wszystko jest jednym z typów skalarnych lub ich ciągiem.
Dany kompilator i dany ABI będą musieli wybrać określone rozmiary dla
int
ibool
kiedy, tak jak w Twoim przypadku, są to różne rozmiary, mogą generować nieco inny kod, a na niektórych poziomach optymalizacji jeden może być nieco szybszy.Dlaczego w wielu systemach wartość logiczna to jeden bajt?
Bezpieczniej jest wybrać
char
typ bool, ponieważ ktoś mógłby stworzyć naprawdę dużą ich tablicę.Aktualizacja: przez „bezpieczniejsze” rozumiem: dla kompilatorów i implementatorów bibliotek. Nie mówię, że ludzie muszą ponownie zaimplementować typ systemu.
źródło
bool
reprezentowany przez bity; więc bajt będzie dobrym kompromisem dla szybkości / zwartości danych w wielu implementacjach.char
zamiastbool
”, ale zamiast tego użył „char
typ”, aby oznaczać „1 bajt”, odnosząc się do rozmiaru wybranego przez kompilator dlabool
obiektów.Tak, dyskusja jest fajna. Ale po prostu to przetestuj:
Kod testowy:
#include <stdio.h> #include <string.h> int testi(int); int testb(bool); int main (int argc, char* argv[]){ bool valb; int vali; int loops; if( argc < 2 ){ return 2; } valb = (0 != (strcmp(argv[1], "0"))); vali = strcmp(argv[1], "0"); printf("Arg1: %s\n", argv[1]); printf("BArg1: %i\n", valb ? 1 : 0); printf("IArg1: %i\n", vali); for(loops=30000000; loops>0; loops--){ //printf("%i: %i\n", loops, testb(valb=!valb)); printf("%i: %i\n", loops, testi(vali=!vali)); } return valb; } int testi(int val){ if( val ){ return 1; } return 0; } int testb(bool val){ if( val ){ return 1; } return 0; }
Skompilowany na 64-bitowym laptopie Ubuntu 10.10 z: g ++ -O3 -o / tmp / test_i /tmp/test_i.cpp
Porównanie na podstawie liczb całkowitych:
sauer@trogdor:/tmp$ time /tmp/test_i 1 > /dev/null real 0m8.203s user 0m8.170s sys 0m0.010s sauer@trogdor:/tmp$ time /tmp/test_i 1 > /dev/null real 0m8.056s user 0m8.020s sys 0m0.000s sauer@trogdor:/tmp$ time /tmp/test_i 1 > /dev/null real 0m8.116s user 0m8.100s sys 0m0.000s
Boolean test / print bez komentarzy (i liczba całkowita z komentarzem):
sauer@trogdor:/tmp$ time /tmp/test_i 1 > /dev/null real 0m8.254s user 0m8.240s sys 0m0.000s sauer@trogdor:/tmp$ time /tmp/test_i 1 > /dev/null real 0m8.028s user 0m8.000s sys 0m0.010s sauer@trogdor:/tmp$ time /tmp/test_i 1 > /dev/null real 0m7.981s user 0m7.900s sys 0m0.050s
Są takie same z 1 przypisaniem i 2 porównaniami w każdej pętli ponad 30 milionów pętli. Znajdź coś innego do optymalizacji. Na przykład nie używaj niepotrzebnie strcmp. ;)
źródło
Zależy to głównie od kompilatora i optymalizacji. Tutaj jest interesująca dyskusja (bez względu na język):
Czy „if ([bool] == true)” wymaga o jeden krok więcej niż „if ([bool])”?
Spójrz także na ten post: http://www.linuxquestions.org/questions/programming-9/c-compiler-handling-of-boolean-variables-290996/
źródło
Podchodząc do pytania na dwa różne sposoby:
Jeśli mówisz konkretnie o C ++ lub jakimkolwiek języku programowania, który będzie generował kod asemblera w tym zakresie, jesteśmy zobowiązani do tego, jaki kod wygeneruje kompilator w ASM. Jesteśmy również zobowiązani do reprezentacji prawdy i fałszu w języku c ++. Liczba całkowita będzie musiała być zapisana w 32 bitach, a ja mógłbym po prostu użyć bajtu do przechowywania wyrażenia logicznego. Fragmenty ASM dla instrukcji warunkowych:
Dla liczby całkowitej:
mov eax,dword ptr[esp] ;Store integer cmp eax,0 ;Compare to 0 je false ;If int is 0, its false ;Do what has to be done when true false: ;Do what has to be done when false
Dla bool:
mov al,1 ;Anything that is not 0 is true test al,1 ;See if first bit is fliped jz false ;Not fliped, so it's false ;Do what has to be done when true false: ;Do what has to be done when false
Dlatego właśnie porównanie szybkości jest tak zależne od kompilacji. W powyższym przypadku wartość bool byłaby nieco szybka, ponieważ
cmp
wymagałaby odejmowania przy ustawianiu flag. Jest to również sprzeczne z tym, co wygenerował Twój kompilator.Innym podejściem, o wiele prostszym, jest przyjrzenie się logice samego wyrażenia i próba nie martwienia się o to, jak kompilator przetłumaczy twój kod. Myślę, że jest to znacznie zdrowszy sposób myślenia. Ostatecznie nadal wierzę, że kod generowany przez kompilator w rzeczywistości próbuje dać prawdziwe rozwiązanie. Chodzi mi o to, że być może, jeśli zwiększysz liczbę przypadków testowych w instrukcji if i będziesz trzymać się wartości logicznej po jednej stronie i liczby całkowitej po drugiej, kompilator sprawi, że wygenerowany kod będzie wykonywał się szybciej z wyrażeniami boolowskimi na poziomie maszyny.
Uważam, że jest to pytanie koncepcyjne, więc podam koncepcyjną odpowiedź. Ta dyskusja przypomina mi dyskusje, które często prowadzę na temat tego, czy wydajność kodu przekłada się na mniej wierszy kodu w asemblerze. Wydaje się, że ta koncepcja jest powszechnie akceptowana jako prawdziwa. Biorąc pod uwagę, że śledzenie, jak szybko jednostka ALU będzie obsługiwać każdą instrukcję, nie jest wykonalne, drugą opcją byłoby skupienie się na skokach i porównaniach w asemblerze. W takim przypadku rozróżnienie między instrukcjami logicznymi lub liczbami całkowitymi w przedstawionym kodzie staje się raczej reprezentatywne. Wynik wyrażenia w C ++ zwróci wartość, która następnie otrzyma reprezentację. Z drugiej strony podczas montażu skoki i porównania będą oparte na wartościach liczbowych, niezależnie od tego, jaki typ wyrażenia był oceniany z powrotem w instrukcji C ++ if. W tych pytaniach ważne jest, aby pamiętać, że czysto logiczne stwierdzenia, takie jak te, kończą się ogromnym narzutem obliczeniowym, nawet jeśli jeden bit byłby zdolny do tego samego.
źródło