Co jest szybsze: if (bool) czy if (int)?

Która wartość jest lepsza do wykorzystania? Logiczna prawda czy liczba całkowita 1?

Powyższy temat zmusił mnie do zrobienia kilku eksperymentów z bool i int w stanie if. Tak 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
    

Zaskakująco, g(bool) generuje więcej asm instrukcje! Czy to znaczy, że if(bool) jest trochę wolniejsze niż if(int)? Kiedyś myślałem, że {[3] } jest specjalnie zaprojektowany do użycia w instrukcjach warunkowych, takich jak if, więc spodziewałem się, że g(bool) wygeneruje mniej instrukcji asm, dzięki czemu g(bool) będzie bardziej wydajny i szybki.

EDIT:

Na razie nie używam żadnej flagi optymalizacji. Ale nawet brak tego, dlaczego generuje więcej asm dla g(bool) to pytanie, na które Szukam rozsądnej odpowiedzi. Powinienem ci też powiedzieć. znacznik optymalizacji -O2 generuje dokładnie taki sam asm. Ale to nie jest pytanie. Pytanie jest takie, jakie zadałem.


Author: Community, 2011-04-23

8 answers

Dla mnie to ma sens. Twój kompilator najwyraźniej definiuje bool jako wartość 8-bitową, a Twój system ABI wymaga, aby "promować" małe (bool, kompilator generuje kod, aby wyizolować najmniej znaczący bajt 32-bitowego argumentu, który otrzymuje g, i porównuje go z cmpb. W pierwszym przykładzie argument int używa pełnych 32 bitów, które zostały zepchnięte na stos, więc po prostu porównuje się z całość z cmpl.

 94
Author: Sherm Pendley,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-04-23 15:20:54

Kompilacja z -03 daje mi następujące:

F:

    pushl   %ebp
    movl    %esp, %ebp
    cmpl    $1, 8(%ebp)
    popl    %ebp
    sbbl    %eax, %eax
    andb    $58, %al
    addl    $99, %eax
    ret

G:

    pushl   %ebp
    movl    %esp, %ebp
    cmpb    $1, 8(%ebp)
    popl    %ebp
    sbbl    %eax, %eax
    andb    $58, %al
    addl    $99, %eax
    ret

.. tak więc kompiluje się do zasadniczo tego samego kodu, z wyjątkiem cmpl vs cmpb. Oznacza to, że różnica, jeśli istnieje, nie ma znaczenia. Sądząc po nieoptymalizowanym kodzie nie jest fair.


Edytuj aby wyjaśnić mój punkt widzenia. Unoptimized code służy do prostego debugowania, a nie szybkości. Porównywanie szybkości nieoptymalizowanego kodu jest bezsensowne.

 76
Author: Alexander Gessler,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-04 01:10:08

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ą różnych instrukcji do porównania ({[4] } dla boolean vs. cmpl dla int), ale poza tym ciała są identyczne. Szybkie spojrzenie na instrukcje Intela mówi mi: ... niewiele. Nie ma czegoś takiego jak cmpb lub cmpl w podręcznikach Intela. Wszystkie są cmp i nie mogę znaleźć tabel czasowych na chwila. Domyślam się jednak, że nie ma różnicy między porównaniem bajtu natychmiastowego a porównaniem długiego natychmiastowego, więc dla wszystkich praktycznych celów kod jest identyczny.


edytowano, aby dodać następujące na podstawie Twojego dodania

Powodem, dla którego kod jest inny w przypadku nieoptimized, jest to, że jest nieoptimized. (Tak, jest okrągły, wiem.) Gdy kompilator chodzi po AST i generuje kod bezpośrednio, nie" wie " nic poza co znajduje się w bezpośrednim punkcie AST, w którym się znajduje. W tym momencie brakuje mu wszystkich informacji kontekstowych potrzebnych, aby wiedzieć, że w tym konkretnym momencie może on traktować zadeklarowany Typ bool jako int. Boolean jest oczywiście domyślnie traktowany jako bajt i podczas manipulowania bajtami w świecie Intela musisz zrobić rzeczy takie jak sign-extend, aby doprowadzić go do pewnych szerokości, aby umieścić go na stosie, itp. (Nie można wcisnąć bajtu.)

Gdy optymalizator widzi AST i robi jego magię, to jednak 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, a tym samym stracić niepotrzebne konwersje i poszerzenie.

 26
Author: JUST MY correct OPINION,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-04-23 15:31:14

Z GCC 4.5 przynajmniej na Linuksie i Windows, sizeof(bool) == 1. Na x86 i x86_64 nie można przekazać funkcji mniej niż wartość rejestru ogólnego przeznaczenia (czy to przez stos, czy rejestr w zależności od konwencji wywołania itp...).

Więc kod boola, gdy nie jest zoptymalizowany, w rzeczywistości osiąga pewną długość, aby wyodrębnić tę wartość boola ze stosu argumentów (używając innego gniazda stosu, aby zapisać ten bajt). Jest to bardziej skomplikowane niż wyciąganie natywnej zmiennej wielkości rejestru.

 13
Author: Mat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-04-23 15:42:32

Na poziomie maszyny nie ma czegoś takiego jak bool

Bardzo niewiele architektur zestawów instrukcji definiuje dowolny typ operandu boolean, chociaż często są instrukcje, które uruchamiają akcję na niezerowych wartościach. Dla procesora zwykle wszystko jest jednym z typów skalarnych lub ich ciągiem.

Dany kompilator i dane ABI będą musiały wybrać określone rozmiary dla int i bool i gdy, jak w Twoim przypadku, są to różne rozmiary, mogą wygenerować nieco inny kod, a na niektórych poziomach optymalizacji może być nieco szybszy.

Dlaczego bool jest jednym bajtem na wielu systemach?

Bezpieczniej jest wybrać typ char dla boola, ponieważ ktoś może zrobić naprawdę dużą tablicę z nich.

Update: by "safer", mam na myśli: dla kompilatorów i implementatorów bibliotek. nie mówię, że ludzie muszą ponownie wdrożyć typ systemu.

 9
Author: DigitalRoss,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-02 05:49:32

Tak, dyskusja jest fajna. Ale po prostu przetestuj:

Kod badania:

#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 oparte na liczbie całkowitej:

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 commented (and integer commented):

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 każda pętla ponad 30 milionów pętli. Znajdź coś innego do optymalizacji. Na przykład, nie używaj strcmp niepotrzebnie. ;)

 7
Author: dannysauer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-04-24 05:32:53

Będzie to głównie zależeć od kompilatora i optymalizacji. Tu jest ciekawa dyskusja (język agnostyczny):

Czy "if ([bool] = = true) "wymaga o jeden krok więcej niż"if ([bool])"?

Zobacz też ten post: http://www.linuxquestions.org/questions/programming-9/c-compiler-handling-of-boolean-variables-290996/

 2
Author: Aleadam,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:32:28

Podchodzenie do twojego pytania na dwa różne sposoby:

Jeśli konkretnie mówisz o C++ lub jakimkolwiek języku programowania, który stworzy kod asemblera, jesteśmy związani z tym, jaki kod kompilator wygeneruje w ASM. Jesteśmy również związani z reprezentacją true i false w c++. Liczba całkowita musi być przechowywana w 32 bitach, a ja mogę po prostu użyć bajtu do przechowywania wyrażenia logicznego. Fragmenty ASM dla instrukcji warunkowych:

Dla liczba całkowita:

  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 boola:

  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 porównanie prędkości jest tak zależne od kompilacji. W powyższym przypadku, bool byłby nieco szybki, ponieważ cmp oznaczałoby odejmowanie dla ustawiania FLAG. Jest to również sprzeczne z tym, co wygenerował kompilator.

Innym podejściem, o wiele prostszym, jest spojrzenie na logikę wyrażenia na własną rękę i staraj się nie martwić o to, jak kompilator przetłumaczy Twój kod, I myślę, że jest to o wiele zdrowsze sposób myślenia. Nadal wierzę, ostatecznie, że kod generowany przez kompilator rzeczywiście próbuje dać prawdziwą rozdzielczość. Chodzi mi o to, że może jeśli zwiększysz liczbę przypadków testowych w instrukcji if I będziesz trzymać się logiki w jednej stronie, a liczby całkowitej w drugiej, kompilator sprawi, że wygenerowany kod będzie działał szybciej z wyrażeniami logicznymi na poziomie maszyny.

Rozważam, że to pytanie koncepcyjne, więc dam koncepcyjną odpowiedź. To dyskusja przypomina mi dyskusje, które często mam na temat tego, czy wydajność kodu przekłada się na mniej linijek kodu w assembly. Wydaje się, że koncepcja ta jest ogólnie akceptowana jako prawdziwa. Biorąc pod uwagę, że śledzenie, jak szybko ALU poradzi sobie z każdym stwierdzeniem, nie jest opłacalne, drugą opcją byłoby skupienie się na skokach i porównaniach w montażu. W takim przypadku rozróżnienie między wyrażeniami logicznymi lub liczbami całkowitymi w prezentowanym kodzie staje się raczej reprezentatywne. Wynik wyrażenia w języku C++ zwróci wartość, która następnie otrzyma reprezentację. Z drugiej strony, w assembly, skoki i porównania będą oparte na wartościach liczbowych, niezależnie od tego, jakiego typu wyrażenie było ewaluowane w instrukcji C++ if. Ważne jest, aby w tych kwestiach pamiętać, że czysto logiczne stwierdzenia, takie jak te, kończą się ogromnym obciążeniem obliczeniowym, nawet jeśli pojedynczy bit byłby zdolny do tego samego.
 0
Author: Artie,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-12-12 07:30:38