Non sto chiedendo come funziona il concetto matematico di modulo o come 20 % 3 == 2 lo capisco bene. Sono più curioso di sapere come il compilatore / interprete determina questo. Posso pensare a un modo molto ingenuo per farlo, in questo modo:

int modulus(int a, int b) { while (a > b) { a -= b; } return a; } 

(ovviamente questo richiede qualche caso in più perché a può essere più piccolo di b, ma hai unidea.) Ma questo avrebbe alcuni seri problemi di prestazioni. Ho messo insieme un po di C ++ per testare lefficienza di entrambi i moduli operatore e la mia funzione ingenua.

#include <iostream> #include <chrono> #include <limits> #include "chrono_io" using namespace std; typedef chrono::high_resolution_clock Clock; int mod(int a, int b) { while (a > b) { a -= b; } return a; } int main() { auto start = Clock::now(); cout << 20 % 3 << endl; auto end = Clock::now(); cout << "Modulus on a small number took " << end - start << " seconds to calculate.\n"; start = Clock::now(); cout << numeric_limits<int>::max() % 3 << endl; end = Clock::now(); cout << "Modulus on a large number took " << end - start << " seconds to calculate.\n"; start = Clock::now(); cout << mod(20, 3) << endl; end = Clock::now(); cout << "My modulus function on a small number took " << end - start << " seconds to calculate.\n"; start = Clock::now(); cout << mod(numeric_limits<int>::max(), 3) << endl; end = Clock::now(); cout << "My modulus function on a large number took " << end - start << " seconds to calculate.\n"; } 

e loutput è:

2 Modulus on a small number took 43629 nanoseconds seconds to calculate. 1 Modulus on a large number took 5644 nanoseconds seconds to calculate. 2 My modulus function on a small number took 3682 nanoseconds seconds to calculate. 1 My modulus function on a large number took 2153824554 nanoseconds seconds to calculate. 

Il Loperatore modulo variava da 4.000-70.000 nanosecondi. La mia funzione vinceva sempre sui numeri piccoli, ma impiegava circa 2 secondi sui numeri grandi. Poiché loperatore modulo è molto più coerente, presumo che confronti il numero a livello bit per bit.

Allora, come funziona loperatore modulo? Posso pensare ad alcuni casi che sarebbero molto facili da determinare. Ad esempio, con la mod 2, puoi semplicemente guardare ultimo byte oppure con il mod 4 si può guardare dal penultimo b yte. Ma non tutti i numeri hanno uno schema così semplice.

Commenti

  • Sui PC, questo viene generalmente fatto in hardware (il x86 DIV e IDIV, che eseguono la divisione di numeri interi, memorizzano anche il resto in un registro). Stai chiedendo come funziona l hardware ?
  • Letture correlate: Assembly Language – How to Do Modulo?
  • La divisione lunga funziona allo stesso modo con i numeri binari come con i decimali. Provalo, quindi pensa a come potresti codificarlo (suggerimento: ‘ utilizzerai molti turni).
  • @DJMcMayhem: Sì. A volte il compilatore deve eseguire alcune modifiche ai risultati per essere conforme alle specifiche del linguaggio per laritmetica con segno. In generale, la velocità di divisione dellhardware è abbastanza buona che quando non si conoscono i valori di input in fase di compilazione, è meglio lasciare che lo faccia lhardware invece dellemulazione del software. Tuttavia, se il divisore è noto in fase di compilazione, ci sono trucchi che potrebbero essere eseguiti più velocemente. Vedi: questo e questo .
  • Tieni presente che il modo in cui stai misurando le cose in realtà stai solo misurando il tempo necessario per cout << la maggior parte delle operazioni. Dubito che tu possa effettivamente misurare il tempo impiegato dal processore per eseguire 20 % 3 anche se il compilatore ‘ non lo ha fatto comunque in fase di compilazione .

Risposta

Quasi tutte le CPU hanno una singola istruzione che restituirà il modulo di un valore. Ad esempio, considera questo programma:

int main() { int i = 10; return i % 3; } 

Se lo compilo sulla mia macchina Intel OS X usando g ++ -S, il risultato sarà un boilerplate e questo:

movl $3, %eax movl $0, -4(%rbp) movl $10, -8(%rbp) movl -8(%rbp), %ecx movl %eax, -12(%rbp) ## 4-byte Spill movl %ecx, %eax cltd movl -12(%rbp), %ecx ## 4-byte Reload idivl %ecx movl %edx, %eax 

Il modulo effettivo si verifica con questa istruzione: idivl %ecx. Quando questa istruzione viene eseguita, il quoziente verrà inserito in %eax e il resto verrà inserito in %edx.

Dal momento che in realtà, questo significa che loperazione % richiederà solo pochi cicli di clock, la maggior parte dei quali sta effettivamente spostando i dati nel registro corretto. Tieni inoltre presente che almeno con Intel, la stessa operazione trova sia il quoziente che il resto, quindi in riferimento al tuo commento, / e % prendi esattamente lo stesso tempo. È la stessa operazione. Lunica cosa che cambia è il registro da cui il compilatore ottiene i risultati.

Con qualsiasi CPU realizzata negli ultimi due decenni, puoi presumere che qualsiasi operazione matematica di base (incluse cose che sembrano librerie funzioni come sqrt o cos) è in realtà una singola istruzione della macchina e generalmente richiede solo pochi cicli di clock al massimo.

[UPDATE]

Come le persone hanno notato nei commenti, per vedere qualcosa che si avvicina a un tempismo corretto, tu è necessario rimuovere loutput dalla sezione temporizzata in questo modo:

int i; auto start = Clock::now(); i = 20 % 3; auto end = Clock::now(); cout << i << endl; 

Ma probabilmente anche questo non è accurato in quanto la granularità effettiva dei tempi potrebbe superare quello che sei cercando di tempo. Invece, potresti voler fare questo:

int i=0; int x; auto start = Clock::now(); for(i=0;i<1000000;i++) x = i % 3; auto end = Clock::now(); cout << i << endl; 

Quindi dividi il tuo tempo per 1.000.000. (Questo sarà leggermente alto in quanto include il tempo impiegato per un compito, un confronto e un incremento.) Sulla mia macchina, questo dà un tempo di 5 nanosecondi.

Commenti

  • Davvero? Mi è stato detto che sqrt è unistruzione molto lenta.
  • Vedi stackoverflow.com / questions / 7724061 / …
  • @StevenBurnap quella risposta è un po datata, in base allarchitettura dellanno 2011. Secondo Agner Fog ‘ s tabella, che viene aggiornata almeno una volta allanno , istruzioni Haswell SSE2 SQRTSS / SQRTPS (32 bit float, con 23 bit di significato) ha una latenza di 11 cicli, SQRTSD / SQRTPD (64 bit float) ha una latenza di 16 cicli. Queste istruzioni sono quelle precise, cioè non delle varianti di veloce approssimazione. È davvero molto più lento nelle vecchie architetture. Tieni presente anche che la chiamata a std::sqrt() comporta una chiamata alla libreria nella maggior parte dei compilatori C ++.
  • I compilatori possono inoltre applicare lottimizzazione della riduzione della forza per casi speciali. Ad esempio, x% 2 potrebbe essere ottimizzato per una maschera di bit se ‘ è più veloce su unarchitettura specifica.
  • @rwong Avrei pensato che std:sqrt di solito sarebbe inline (e usa lassembly inline.) Ma ‘ non ho mai controllato.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *