2013-03-24 7 views
28

Suppongo che il calcolo del modulo di un numero sia un'operazione alquanto costosa, almeno rispetto ai semplici test aritmetici (come vedere se un numero supera la lunghezza di un array). Se questo è davvero il caso, è più efficiente per sostituire, ad esempio, il seguente codice:è meglio evitare l'uso dell'operatore mod quando possibile?

res = array[(i + 1) % len]; 

con il seguente? :

res = array[(i + 1 == len) ? 0 : i + 1]; 

Il primo è più facile per gli occhi, ma mi chiedo se il secondo potrebbe essere più efficiente. Se è così, potrei aspettarmi un compilatore ottimizzante per sostituire il primo frammento con il secondo, quando viene utilizzato un linguaggio compilato?

Ovviamente questa "ottimizzazione" (se si tratta in effetti di un'ottimizzazione) non funziona in tutti i casi (in questo caso funziona solo se i+1 non è mai più di len).

+10

Questo potrebbe essere il caso di mancare la foresta per gli alberi. –

+1

se 'len' è una costante in fase di compilazione un recente compilatore GCC (con' -02') sta di solito facendo cose intelligenti, evitando spesso l'istruzione della macchina modulo del processore di destinazione. –

+2

Questo è davvero il tipo di ottimizzazione da dimenticare. Il compilatore ottimizzante farà meglio di quanto potresti. Ciò che conta di più è la leggibilità del tuo codice. –

risposta

20

Il mio consiglio generale è il seguente. Usa la versione che ritieni più facile per gli occhi e quindi profila il tuo intero sistema. Ottimizza solo quelle parti del codice che il profiler contrassegna come colli di bottiglia. Scommetto il mio dollaro in meno che l'operatore modulo non sarà tra loro.

Per quanto riguarda l'esempio specifico, solo il benchmarking può indicare quale sia più veloce sulla propria architettura specifica utilizzando il proprio compilatore specifico. È potenzialmente possibile sostituire modulo con branching ed è tutto fuorché ovvio che sarebbe più veloce.

+0

Nelle macchine recenti l'aritmetica dei numeri è quasi gratuita; ciò che importa molto di più è la cache ..... che sono molto più costosi. una mancanza di cache L1 blocca il processore per centinaia di cicli, durante i quali il processore potrebbe eseguire dozzine di divisioni o moduli; quindi l'eventuale costo del modulo è il rumore –

+3

@BasileStarynkevitch: Beh, il comportamento della cache sarà identico tra i due frammenti di codice. Ciò che conta è se la versione # 2 utilizza o meno la ramificazione e, in caso affermativo, quanto bene farà il lavoro del predittore di ramo. – NPE

+0

@Basile Starynkevitch Ho visto un fattore di circa 300 tra modulo e l'accesso a un grande tavolo su un laptop. (Aggiungere un test per la divisibilità di 17 al quadrato per evitare che l'accesso alla matrice fosse ancora vantaggioso.) – starblue

0

Modulo può essere eseguito con un'istruzione a processore singolo sulla maggior parte delle architetture (ad esempio DIV su x86). Tuttavia è probabile un'ottimizzazione prematura per ciò di cui hai bisogno.

+14

Solo perché esiste un'unica istruzione per un'operazione, non significa che si verifichi in un singolo ciclo di clock. –

+2

@ChrisDesjardins Concordato, ma '%' se il secondo operatore è potere di 2 può essere rappresentato come una maschera di bit. – Alex

+5

Scusate, è stato necessario un downvote. Ho lavorato con molte architetture (ma non con x86) e devo ancora lavorare con uno che compili mod/div in un'unica istruzione. E ho visto app dove mod è una delle prime 10 chiamate di funzione che consumano CPU a causa di tutto il buffering circolare: ogni copia di "sample" seguita da un% di buffer. Nel mio caso cerco di evitare mod se posso, in genere affermando che le dimensioni del buffer di input sono divisibili per 2, quindi il compilatore può ottimizzare il mod. –

16

Qualche semplice misura:

#include <stdio.h> 
#include <stdlib.h> 

int main(int argc, char *argv[]) 
{ 
    int test = atoi(argv[1]); 
    int divisor = atoi(argv[2]); 
    int iterations = atoi(argv[3]); 

    int a = 0; 

    if (test == 0) { 
     for (int i = 0; i < iterations; i++) 
      a = (a + 1) % divisor; 
    } else if (test == 1) { 
     for (int i = 0; i < iterations; i++) 
      a = a + 1 == divisor ? 0 : a + 1; 
    } 

    printf("%d\n", a); 
} 

compilazione sia con gcc o clang con -O3, e in esecuzione time ./a.out 0 42 1000000000 (versione modulo) o time ./a.out 1 42 1000000000 (confronto versione)

  • 6,25 secondi runtime utente per la versione modulo,
  • 1,03 secondi per la versione di confronto.

(usando gcc 5.2.1 o 3.6.2 clang; Intel Core i5-4690K @ 3.50GHz; Linux a 64 bit)

Ciò significa che è probabilmente una buona idea di utilizzare la versione confronto .

+2

Sui dati più realistici (ad esempio se il numero sarebbe casuale) la differenza non sarebbe così grande – user1209304

+1

La versione di confronto è solo più veloce perché il risultato dell'istruzione if è lo stesso ogni volta, quindi il predittore di ramo ottiene correttamente ogni tempo. Se hai randomizzato l'input, la versione di confronto potrebbe anche essere peggio di mod – Bigminimus

+1

@Bigminimus Hmm ma il risultato della clausola if è lo stesso per entrambi i test sempre? –