Qual è più veloce: if (bool) o if (int)?


94

Quale valore è meglio usare? Boolean true o Integer 1?

L'argomento sopra mi ha fatto fare alcuni esperimenti con boole intin ifcondizioni. Quindi solo per curiosità ho scritto questo programma:

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 genera codice asm per ciascuna funzione come segue:

  • codice asm per 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
    
  • codice asm per 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
    

Sorprendentemente, g(bool)genera più asmistruzioni! Significa che if(bool)è un po 'più lento di if(int)? Pensavo che boolfosse progettato specialmente per essere utilizzato in affermazioni condizionali come if, quindi mi aspettavo g(bool)di generare meno istruzioni asm, rendendole così g(bool)più efficienti e veloci.

MODIFICARE:

Al momento non sto utilizzando alcun flag di ottimizzazione. Ma anche in assenza di esso, perché genera più asm g(bool)è una domanda per la quale cerco una risposta ragionevole. Dovrei anche dirti che il -O2flag di ottimizzazione genera esattamente lo stesso asm. Ma non è questa la domanda. La domanda è ciò che ho chiesto.



32
È anche un test ingiusto a meno che non li confronti con ottimizzazioni ragionevoli abilitate.
Daniel Pryden

9
@ Daniel: non sto usando alcun flag di ottimizzazione con nessuno dei due. Ma anche in assenza di esso, perché genera più asm g(bool)è una domanda per la quale cerco una risposta ragionevole.
Nawaz

8
Perché dovresti prenderti la briga di leggere l'ASM, ma non solo di eseguire il programma e cronometrare il risultato ? Il numero di istruzioni non dice molto sulle prestazioni. È necessario tenere conto non solo della lunghezza delle istruzioni, ma anche delle dipendenze e dei tipi di istruzioni (alcune di esse sono decodificate utilizzando il percorso del microcodice più lento, quali unità di esecuzione richiedono, qual è la latenza e il throughput dell'istruzione, è un ramo Un accesso memmory??
JALF

2
@user unknown e @Malvolio: Ovviamente è così; Non sto facendo tutto questo per il codice di produzione. Come ho già accennato all'inizio del mio post che "Quindi solo per curiosità ho scritto questo programma" . Quindi sì, è puramente ipotetico .
Nawaz

3
È una domanda legittima. O sono equivalenti o uno è più veloce. L'ASM è stato probabilmente pubblicato nel tentativo di essere utile o pensare ad alta voce, quindi invece di usarlo come un modo per schivare la domanda e dire "scrivi solo codice leggibile", rispondi semplicemente alla domanda o STFU se non lo sai o non ho niente di utile da dire;) Il mio contributo è che la domanda ha una risposta, e "scrivi solo codice leggibile" non è altro che un schivare la domanda.
Triynko

Risposte:


99

Ha senso per me. Il tuo compilatore apparentemente definisce a boolcome un valore a 8 bit, e il tuo sistema ABI richiede che "promuova" argomenti interi piccoli (<32 bit) a 32 bit quando li inserisce nello stack di chiamate. Quindi, per confrontare a bool, il compilatore genera il codice per isolare il byte meno significativo dell'argomento a 32 bit che g riceve e lo confronta con cmpb. Nel primo esempio, l' intargomento utilizza tutti i 32 bit che sono stati inseriti nello stack, quindi si confronta semplicemente con l'intera cosa con cmpl.


4
Sono d'accordo. Questo aiuta a chiarire che quando si sceglie un tipo di variabile, lo si sceglie per due scopi potenzialmente concorrenti, spazio di archiviazione e prestazioni di calcolo.
Triynko

3
Questo vale anche per i processi a 64 bit, che __int64è più veloce di int? Oppure la CPU gestisce separatamente interi a 32 bit con set di istruzioni a 32 bit?
Crend King

1
@ CrendKing forse vale la pena fare un'altra domanda?
Nome visualizzato

81

La compilazione con -03dà quanto segue per me:

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

.. quindi compila per essenzialmente lo stesso codice, tranne che per cmplcontro cmpb. Ciò significa che la differenza, se presente, non ha importanza. Giudicare in base a un codice non ottimizzato non è giusto.


Modifica per chiarire il mio punto. Il codice non ottimizzato serve per il debug semplice, non per la velocità. Confrontare la velocità di un codice non ottimizzato non ha senso.


8
Per quanto concordo con la tua conclusione, penso che tu stia saltando la parte interessante. Perché si usa cmplper l'uno e cmpbper l'altro?
jalf

22
@jalf: perché a boolè un singolo byte e an intè quattro. Non credo ci sia niente di più speciale di questo.
CB Bailey

7
Penso che altre risposte abbiano prestato maggiore attenzione ai motivi: è perché la piattaforma in questione tratta boolcome un tipo a 8 bit.
Alexander Gessler

9
@ Nathan: No. C ++ non ha tipi di dati bit. Il tipo più piccolo è char, che è un byte per definizione, ed è l'unità indirizzabile più piccola. boolLa dimensione di è definita dall'implementazione e può essere 1, 4 o 8 o qualsiasi altra cosa. I compilatori tendono a renderlo uno, però.
GManNickG

6
@ Nathan: Beh, anche questo è complicato in Java. Java dice che i dati rappresentati da un booleano sono il valore di un bit, ma il modo in cui quel bit viene memorizzato è ancora definito dall'implementazione. I computer pragmatici semplicemente non affrontano i bit.
GManNickG

26

Quando lo compilo con un sano set di opzioni (in particolare -O3), ecco cosa ottengo:

Per 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

Per 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

Usano ancora istruzioni diverse per il confronto ( cmpbper booleano vs. cmplint), ma per il resto i corpi sono identici. Una rapida occhiata ai manuali Intel mi dice: ... non molto di niente. Non c'è niente come cmpbo cmplnei manuali Intel. Sono tutti cmpe al momento non riesco a trovare i tempi. Immagino, tuttavia, che non ci sia differenza di clock tra il confronto di un byte immediato e il confronto di un immediato lungo, quindi per tutti gli scopi pratici il codice è identico.


modificato per aggiungere quanto segue in base alla tua aggiunta

Il motivo per cui il codice è diverso nel caso non ottimizzato è che non è ottimizzato. (Sì, è circolare, lo so.) Quando il compilatore esegue l'AST e genera il codice direttamente, non "sa" nulla tranne cosa si trova nel punto immediato dell'AST in cui si trova. A quel punto mancano tutte le informazioni contestuali necessarie sapere che a questo punto specifico può trattare il tipo dichiarato boolcome un file int. Un booleano è ovviamente trattato per impostazione predefinita come un byte e quando si manipolano i byte nel mondo Intel devi fare cose come sign-extension per portarlo a determinate larghezze per metterlo nello stack, ecc. (Non puoi spingere un byte .)

Quando l'ottimizzatore visualizza l'AST e fa la sua magia, tuttavia, guarda al contesto circostante e "sa" quando può sostituire il codice con qualcosa di più efficiente senza cambiare la semantica. Quindi "sa" di poter utilizzare un numero intero nel parametro e quindi perdere le conversioni e gli ampliamenti non necessari.


1
Haha, mi piace come il compilatore ha restituito semplicemente 99, o 99 + 58 = 157 = -99 (overflow di 8 bit con segno) ... interessante.
deceleratedcaviar

@Daniel: anche a me è piaciuto. All'inizio ho detto "dov'è -99" e subito ho capito che stava facendo qualcosa di molto strano.
Nawaz

7
le bsono suffissi utilizzati solo nella sintassi AT&T. Si riferiscono solo alle versioni che cmputilizzano rispettivamente operandi a 4 byte (long) e 1 byte (byte). In caso di ambiguità nella sintassi Intel, convenzionalmente l'operando di memoria viene etichettato con BYTE PTR, WORD PTRo DWORD PTRinvece di mettere un suffisso sull'opcode.
CB Bailey

Tabelle dei tempi: agner.org/optimize Entrambe le dimensioni degli operandi cmphanno le stesse prestazioni e non ci sono penalità di registro parziale per la lettura %dil . (Ma questo non impedisce a clang di creare in modo divertente uno stallo di registri parziali utilizzando la dimensione dei byte andsu AL come parte del capovolgimento senza rami tra 99 e -99.)
Peter Cordes

13

Con GCC 4.5 su Linux e Windows, almeno, sizeof(bool) == 1. Su x86 e x86_64, non puoi passare meno del valore di un registro per uso generico a una funzione (sia tramite lo stack o un registro a seconda della convenzione di chiamata, ecc ...).

Quindi il codice per bool, quando non è ottimizzato, in realtà raggiunge una certa lunghezza per estrarre quel valore bool dallo stack degli argomenti (utilizzando un altro slot dello stack per salvare quel byte). È più complicato che estrarre una variabile nativa delle dimensioni di un registro.


Dallo standard C ++ 03, §5.3.3 / 1: " sizeof(bool)e sizeof(wchar_t)sono definiti dall'implementazione. " Quindi dire sizeof(bool) == 1non è del tutto corretto a meno che non si parli di una versione specifica di uno specifico compilatore.
ildjarn

9

A livello di macchina non esiste bool

Pochissime architetture di set di istruzioni definiscono qualsiasi tipo di tipo di operando booleano, sebbene ci siano spesso istruzioni che attivano un'azione su valori diversi da zero. Per la CPU, di solito, tutto è uno dei tipi scalari o una stringa di essi.

Un dato compilatore e un dato ABI dovranno scegliere dimensioni specifiche per inte boolquando, come nel tuo caso, queste sono dimensioni diverse potrebbero generare codice leggermente diverso, e ad alcuni livelli di ottimizzazione uno potrebbe essere leggermente più veloce.

Perché bool un byte su molti sistemi?

È più sicuro scegliere un chartipo per bool perché qualcuno potrebbe crearne un array molto ampio.

Aggiornamento: per "più sicuro", intendo: per il compilatore e gli implementatori della libreria. Non sto dicendo che le persone debbano reimplementare il tipo di sistema.


2
+1 Immagina l'overhead su x86 se boolfosse rappresentato da bit; quindi byte sarà un buon compromesso per velocità / compattezza dei dati in molte implementazioni.
hardmath

1
@ Billy: Penso che non stesse dicendo "usa charinvece di bool" ma invece ha semplicemente usato " chartipo" per significare "1 byte" quando si riferisce alla dimensione scelta dal compilatore per gli boologgetti.
Dennis Zickefoose

Oh, certo, non volevo dire che ogni programma dovesse scegliere, stavo solo proponendo una spiegazione logica del perché il tipo bool di sistema è 1 byte.
DigitalRoss

@ Dennis: Ah, ha senso.
Billy ONeal

7

Sì, la discussione è divertente. Ma provalo:

Codice di prova:

#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;
}

Compilato su un laptop Ubuntu 10.10 a 64 bit con: g ++ -O3 -o / tmp / test_i /tmp/test_i.cpp

Confronto basato su numeri interi:

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

Prova booleana / stampa non commentata (e commentata da interi):

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

Sono gli stessi con 1 assegnazione e 2 confronti ogni loop su 30 milioni di loop. Trova qualcos'altro da ottimizzare. Ad esempio, non utilizzare strcmp inutilmente. ;)



0

Affrontare la tua domanda in due modi diversi:

Se stai parlando specificamente di C ++ o di qualsiasi linguaggio di programmazione che produrrà codice assembly per quella materia, siamo vincolati a quale codice il compilatore genererà in ASM. Siamo anche vincolati alla rappresentazione di vero e falso in c ++. Un numero intero dovrà essere memorizzato a 32 bit e potrei semplicemente usare un byte per memorizzare l'espressione booleana. Snippet asm per istruzioni condizionali:

Per il numero intero:

  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

Per il 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

Quindi, ecco perché il confronto della velocità è così dipendente dalla compilazione. Nel caso precedente, il bool sarebbe leggermente veloce poiché cmpimplicherebbe una sottrazione per l'impostazione dei flag. Inoltre è in contraddizione con ciò che ha generato il compilatore.

Un altro approccio, molto più semplice, è guardare la logica dell'espressione da sola e cercare di non preoccuparsi di come il compilatore tradurrà il tuo codice, e penso che questo sia un modo di pensare molto più sano. Alla fine credo ancora che il codice generato dal compilatore stia effettivamente cercando di dare una risoluzione veritiera. Quello che voglio dire è che, forse se aumenti i casi di test nell'istruzione if e rimani con booleano in un lato e intero in un altro, il compilatore lo farà in modo che il codice generato venga eseguito più velocemente con espressioni booleane a livello di macchina.

Sto considerando che questa è una domanda concettuale, quindi darò una risposta concettuale. Questa discussione mi ricorda le discussioni che ho comunemente sul fatto che l'efficienza del codice si traduca o meno in meno righe di codice in assembly. Sembra che questo concetto sia generalmente accettato come vero. Considerando che tenere traccia della velocità con cui l'ALU gestirà ogni istruzione non è fattibile, la seconda opzione sarebbe quella di concentrarsi sui salti e sui confronti nell'assemblaggio. In questo caso, la distinzione tra istruzioni booleane o numeri interi nel codice presentato diventa piuttosto rappresentativa. Il risultato di un'espressione in C ++ restituirà un valore a cui verrà quindi data una rappresentazione. In assemblea, invece, i salti e i confronti saranno basati su valori numerici indipendentemente dal tipo di espressione che è stato valutato in base all'istruzione if in C ++. È importante su queste domande ricordare che affermazioni puramente logiche come queste finiscono con un enorme sovraccarico computazionale, anche se un singolo bit sarebbe in grado di fare la stessa cosa.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.