Dovrei usare la moltiplicazione o la divisione?


118

Ecco una domanda stupida e divertente:

Diciamo che dobbiamo eseguire una semplice operazione in cui abbiamo bisogno della metà del valore di una variabile. Ci sono in genere due modi per farlo:

y = x / 2.0;
// or...
y = x * 0.5;

Supponendo di utilizzare gli operatori standard forniti con la lingua, quale ha prestazioni migliori?

Immagino che la moltiplicazione sia in genere migliore, quindi cerco di attenermi a quella quando codifico, ma vorrei confermarlo.

Anche se personalmente sono interessato alla risposta per Python 2.4-2.5, sentiti libero di pubblicare una risposta anche per altre lingue! E se lo desideri, sentiti libero di pubblicare anche altri modi più elaborati (come l'utilizzo di operatori di spostamento bit per bit).


5
Hai eseguito un benchmark? Sono solo una dozzina di righe di codice. Cosa hai imparato eseguendo un benchmark? [Suggerimento: farlo sarebbe stato più veloce che postare la domanda qui.]
S.Lott

4
Ottima domanda, che ha generato alcune risposte / discussioni piuttosto interessanti. Grazie :)
stealthcopter

22
Anche se aveva appreso la risposta confrontandola, è comunque una domanda utile e ha generato alcune risposte interessanti e utili. Inoltre, vorrei che le persone si attengano al punto e si astengano dallo scrivere risposte e commenti alle risposte offrendo consigli irrilevanti sull'opportunità o meno di fare l'ottimizzazione in questione. Perché non presumere che l'OP stia ponendo la domanda così come è stata scritta invece di presumere che lui o lei "realmente" voglia un consiglio su una riscrittura su scala più ampia.
Kevin Whitefoot,

1
La divisione è molto più lenta della moltiplicazione. Ma alcuni compilatori / VM intelligenti trasformano la divisione in moltiplicazione, quindi i tuoi test avranno gli stessi risultati (entrambi i test testano la moltiplicazione).
Ivan Kuckir

4
Un po 'fuori tema, ma voglio solo dire quanto sono d'accordo con @KevinWhitefoot. Non c'è niente di così frustrante come leggere da sermonizzatori piuttosto che strai risposte tecniche a domande tecniche. Grazie Kevin per il tuo commento!
Jean-François

Risposte:


78

Pitone:

time python -c 'for i in xrange(int(1e8)): t=12341234234.234 / 2.0'
real    0m26.676s
user    0m25.154s
sys     0m0.076s

time python -c 'for i in xrange(int(1e8)): t=12341234234.234 * 0.5'
real    0m17.932s
user    0m16.481s
sys     0m0.048s

la moltiplicazione è del 33% più veloce

Lua:

time lua -e 'for i=1,1e8 do t=12341234234.234 / 2.0 end'
real    0m7.956s
user    0m7.332s
sys     0m0.032s

time lua -e 'for i=1,1e8 do t=12341234234.234 * 0.5 end'
real    0m7.997s
user    0m7.516s
sys     0m0.036s

=> nessuna differenza reale

LuaJIT:

time luajit -O -e 'for i=1,1e8 do t=12341234234.234 / 2.0 end'
real    0m1.921s
user    0m1.668s
sys     0m0.004s

time luajit -O -e 'for i=1,1e8 do t=12341234234.234 * 0.5 end'
real    0m1.843s
user    0m1.676s
sys     0m0.000s

=> è solo il 5% più veloce

conclusioni: in Python è più veloce moltiplicare che dividere, ma man mano che ci si avvicina alla CPU utilizzando VM o JIT più avanzati, il vantaggio scompare. È del tutto possibile che una futura VM Python lo renda irrilevante


Grazie per il suggerimento sull'utilizzo del comando time per il benchmarking!
Edmundito

2
La tua conclusione è sbagliata. Diventa più rilevante man mano che JIT / VM migliora. La divisione diventa più lenta rispetto al minore overhead della VM. Ricorda che i compilatori generalmente non possono ottimizzare molto il punto mobile per garantire la precisione.
rasmus

7
@rasmus: Man mano che JIT migliora, diventa più probabile che utilizzi un'istruzione di moltiplicazione della CPU anche se hai chiesto la divisione.
Ben Voigt

68

Usa sempre ciò che è più chiaro. Qualsiasi altra cosa che fai è cercare di superare in astuzia il compilatore. Se il compilatore è del tutto intelligente, farà del suo meglio per ottimizzare il risultato, ma niente può fare in modo che il prossimo ragazzo non ti odi per la tua schifosa soluzione di cambio di bit (ad ogni modo adoro la manipolazione dei bit, è divertente. Ma divertente! = Leggibile )

L'ottimizzazione prematura è la radice di tutti i mali. Ricorda sempre le tre regole di ottimizzazione!

  1. Non ottimizzare.
  2. Se sei un esperto, consulta la regola n. 1
  3. Se sei un esperto e puoi giustificare la necessità, utilizza la seguente procedura:

    • Codice non ottimizzato
    • determinare la velocità "Abbastanza veloce" - Annotare quale requisito / storia dell'utente richiede quella metrica.
    • Scrivi un test di velocità
    • Prova il codice esistente: se è abbastanza veloce, hai finito.
    • Ricodificalo ottimizzato
    • Prova il codice ottimizzato. SE non soddisfa la metrica, buttalo via e mantieni l'originale.
    • Se soddisfa il test, conserva il codice originale come commento

Inoltre, fare cose come rimuovere i loop interni quando non sono necessari o scegliere un elenco collegato su un array per un ordinamento per inserzione non sono ottimizzazioni, ma solo programmazione.


7
non è la citazione completa di Knuth; vedi en.wikipedia.org/wiki/…
Jason S

No, ci sono circa 40 citazioni diverse sull'argomento da altrettante fonti diverse. Ho messo insieme alcuni pezzi.
Bill K

La tua ultima frase rende poco chiaro quando applicare le regole n. 1 e n. 2, lasciandoci indietro da dove siamo partiti: dobbiamo decidere quali ottimizzazioni sono utili e quali no. Fingere che la risposta sia ovvia non è una risposta.
Matt

2
È davvero così confuso per te? Applicare sempre le regole 1 e 2 a meno che non si soddisfino effettivamente le specifiche del client e si abbia molta familiarità con l'intero sistema, comprese la lingua e le caratteristiche di memorizzazione nella cache della CPU. A quel punto, segui SOLO la procedura in 3, non pensare semplicemente "Ehi, se memorizzo questa variabile nella cache localmente invece di chiamare un getter, le cose saranno probabilmente più veloci. Prima prova che non è abbastanza veloce, quindi prova ciascuna ottimizzazione separatamente e Butta via quelli che non aiutano. Documenta pesantemente lungo tutto il percorso.
Bill K

49

Penso che questo stia diventando così nitido che faresti meglio a fare qualsiasi cosa renda il codice più leggibile. A meno che tu non esegua le operazioni migliaia, se non milioni, di volte, dubito che qualcuno noterà mai la differenza.

Se devi davvero fare la scelta, il benchmarking è l'unica strada da percorrere. Trova le funzioni che ti danno problemi, quindi scopri dove si verificano i problemi nella funzione e correggi quelle sezioni. Tuttavia, dubito ancora che una singola operazione matematica (anche una ripetuta molte, molte volte) potrebbe essere causa di eventuali colli di bottiglia.


1
Quando producevo processori radar, una singola operazione faceva la differenza. Ma stavamo ottimizzando manualmente il codice macchina per ottenere prestazioni in tempo reale. Per tutto il resto, voto per semplice e scontato.
S.Lott

Immagino che per alcune cose potrebbe interessarti una singola operazione. Ma mi aspetto che nel 99% delle applicazioni là fuori non abbia importanza.
Thomas Owens,

27
Soprattutto perché l'OP stava cercando una risposta in Python. Dubito che qualsiasi cosa che necessiti di quella quantità di efficienza sarebbe scritta in Python.
Ed S.

4
Una divisione è probabilmente l'operazione più costosa in una routine di intersezione triangolare, che è la base per la maggior parte dei raytracer. Se memorizzi il reciproco e moltiplichi invece di dividere, sperimenterai molte volte più velocità.
solinent

@solinent - sì un aumento di velocità ma dubito "molte volte" - la divisione e la moltiplicazione in virgola mobile non dovrebbero essere diverse di più di circa 4: 1, a meno che il processore in questione non sia davvero ottimizzato per la moltiplicazione e non per la divisione.
Jason S

39

La moltiplicazione è più veloce, la divisione è più accurata. Perderai un po 'di precisione se il tuo numero non è una potenza di 2:

y = x / 3.0;
y = x * 0.333333;  // how many 3's should there be, and how will the compiler round?

Anche se lasci che il compilatore calcoli la costante invertita con una precisione perfetta, la risposta può essere ancora diversa.

x = 100.0;
x / 3.0 == x * (1.0/3.0)  // is false in the test I just performed

È probabile che il problema della velocità abbia importanza solo nei linguaggi C / C ++ o JIT, e anche in questo caso solo se l'operazione è in un ciclo in un collo di bottiglia.


La divisione è accurata se stai dividendo per numeri interi.
zoccolo

7
La divisione in virgola mobile con denominatore> numeratore deve introdurre valori privi di significato nei bit di ordine inferiore; la divisione di solito riduce la precisione.
S.Lott

8
@ S.Lott: No, non è vero. Tutte le implementazioni in virgola mobile conformi a IEEE-754 devono arrotondare perfettamente i risultati di ogni operazione (cioè al numero in virgola mobile più vicino) rispetto alla modalità di arrotondamento corrente. Moltiplicare per il reciproco introdurrà sempre più errori, almeno perché deve avvenire un ulteriore arrotondamento.
Electro

1
So che questa risposta ha più di 8 anni, ma è fuorviante; è possibile eseguire la divisione senza una significativa perdita di precisione: y = x * (1.0/3.0);e il compilatore generalmente calcolerà 1/3 in fase di compilazione. Sì, 1/3 non è perfettamente rappresentabile in IEEE-754, ma quando esegui operazioni aritmetiche in virgola mobile stai comunque perdendo precisione , indipendentemente dal fatto che tu stia facendo moltiplicazione o divisione, perché i bit di ordine inferiore sono arrotondati. Se sai che il tuo calcolo è così sensibile all'errore di arrotondamento, dovresti anche sapere come affrontare al meglio il problema.
Jason S

1
@ JasonS Ho appena lasciato un programma in esecuzione durante la notte, iniziando da 1.0 e contando fino a 1 ULP; Ho confrontato il risultato della moltiplicazione per (1.0/3.0)con la divisione per 3.0. Sono arrivato a 1.0000036666774155 e in quello spazio il 7,3% dei risultati era diverso. Presumo che fossero diversi solo di 1 bit, ma poiché è garantito che l'aritmetica IEEE arrotonda al risultato corretto più vicino, sostengo la mia affermazione che la divisione è più accurata. Se la differenza è significativa dipende da te.
Mark Ransom

25

Se vuoi ottimizzare il tuo codice ma essere comunque chiaro, prova questo:

y = x * (1.0 / 2.0);

Il compilatore dovrebbe essere in grado di eseguire la divisione in fase di compilazione, in modo da ottenere una moltiplicazione in fase di esecuzione. Mi aspetto che la precisione sia la stessa del y = x / 2.0caso.

Dove questo può avere importanza, molto è nei processori incorporati in cui è richiesta l'emulazione in virgola mobile per calcolare l'aritmetica in virgola mobile.


12
Adatto a te stesso (ea chiunque l'abbia fatto): è una pratica standard nel mondo embedded e gli ingegneri del software in quel campo lo trovano chiaro.
Jason S

4
+1 per essere l'unico qui a rendersi conto che i compilatori non possono ottimizzare le operazioni in virgola mobile come vogliono. Non possono nemmeno cambiare l'ordine degli operandi in una moltiplicazione per garantire la precisione (a meno che non utilizzi una modalità rilassata).
rasmus

1
OMG, ci sono almeno 6 programmatori che pensano che la matematica elementare non sia chiara. AFAIK, la moltiplicazione IEEE 754 è commutativa (ma non associativa).
maaartinus

13
Forse stai perdendo il punto. Non ha nulla a che fare con la correttezza algebrica. In un mondo ideale dovresti essere in grado di dividere per due:, y = x / 2.0;ma nel mondo reale, potresti dover indurre il compilatore a eseguire una moltiplicazione meno costosa. Forse è meno chiaro il motivo per cui y = x * (1.0 / 2.0);è migliore, e sarebbe più chiaro y = x * 0.5;invece affermarlo . Ma cambia il 2.0in a 7.0e preferirei di gran lunga vedere y = x * (1.0 / 7.0);che y = x * 0.142857142857;.
Jason S,

3
Questo rende davvero chiaro il motivo per cui è più leggibile (e preciso) utilizzare il tuo metodo.
Juan Martinez

21

Aggiungo solo qualcosa per l'opzione "altre lingue".
C: Dato che questo è solo un esercizio accademico che non fa davvero differenza, ho pensato di contribuire con qualcosa di diverso.

Ho compilato in assembly senza ottimizzazioni e ho guardato il risultato.
Il codice:

int main() {

    volatile int a;
    volatile int b;

    asm("## 5/2\n");
    a = 5;
    a = a / 2;

    asm("## 5*0.5");
    b = 5;
    b = b * 0.5;

    asm("## done");

    return a + b;

}

compilato con gcc tdiv.c -O1 -o tdiv.s -S

la divisione per 2:

movl    $5, -4(%ebp)
movl    -4(%ebp), %eax
movl    %eax, %edx
shrl    $31, %edx
addl    %edx, %eax
sarl    %eax
movl    %eax, -4(%ebp)

e la moltiplicazione per 0,5:

movl    $5, -8(%ebp)
movl    -8(%ebp), %eax
pushl   %eax
fildl   (%esp)
leal    4(%esp), %esp
fmuls   LC0
fnstcw  -10(%ebp)
movzwl  -10(%ebp), %eax
orw $3072, %ax
movw    %ax, -12(%ebp)
fldcw   -12(%ebp)
fistpl  -16(%ebp)
fldcw   -10(%ebp)
movl    -16(%ebp), %eax
movl    %eax, -8(%ebp)

Tuttavia, quando ho cambiato quelle ints in doubles (che è quello che probabilmente farebbe Python), ho ottenuto questo:

divisione:

flds    LC0
fstl    -8(%ebp)
fldl    -8(%ebp)
flds    LC1
fmul    %st, %st(1)
fxch    %st(1)
fstpl   -8(%ebp)
fxch    %st(1)

moltiplicazione:

fstpl   -16(%ebp)
fldl    -16(%ebp)
fmulp   %st, %st(1)
fstpl   -16(%ebp)

Non ho confrontato nessuno di questo codice, ma solo esaminando il codice puoi vedere che usando i numeri interi, la divisione per 2 è più breve della moltiplicazione per 2. Usando i doppi, la moltiplicazione è più breve perché il compilatore usa i codici operativi a virgola mobile del processore, che probabilmente funzionano più velocemente (ma in realtà non lo so) rispetto a non usarli per la stessa operazione. Quindi, alla fine, questa risposta ha dimostrato che le prestazioni della multiplazione per 0,5 rispetto alla divisione per 2 dipendono dall'implementazione del linguaggio e dalla piattaforma su cui gira. In definitiva la differenza è trascurabile ed è qualcosa di cui non dovresti praticamente mai preoccuparti, tranne in termini di leggibilità.

Come nota a margine, puoi vedere che nel mio programma main()ritorna a + b. Quando tolgo la parola chiave volatile, non indovinerai mai come appare l'assembly (esclusa l'impostazione del programma):

## 5/2

## 5*0.5
## done

movl    $5, %eax
leave
ret

ha fatto sia la divisione, la moltiplicazione E l'addizione in un'unica istruzione! Chiaramente non devi preoccuparti di questo se l'ottimizzatore è rispettabile.

Scusa per la risposta troppo lunga.


1
Non è una "singola istruzione". È stato semplicemente piegato costantemente.
kvanberendonck

5
@kvanberendonck Ovviamente è un'unica istruzione. Contali: movl $5, %eax il nome dell'ottimizzazione non è importante né pertinente. Volevi solo essere condiscendente su una risposta di quattro anni.
Carson Myers

2
La natura dell'ottimizzazione è ancora importante da capire, perché è sensibile al contesto: si applica solo se stai aggiungendo / moltiplicando / dividendo / ecc. costanti in fase di compilazione, in cui il compilatore può semplicemente fare tutti i calcoli in anticipo e spostare la risposta finale in un registro in fase di esecuzione. La divisione è molto più lenta della moltiplicazione nel caso generale (divisori di runtime), ma suppongo che moltiplicare per reciproci aiuti solo se altrimenti dividi per lo stesso denominatore più di una volta. Probabilmente sai tutto questo, ma i nuovi programmatori potrebbero aver bisogno di spiegarlo, quindi ... per ogni evenienza.
Mike S

10

In primo luogo, a meno che tu non stia lavorando in C o ASSEMBLY, probabilmente sei in un linguaggio di livello superiore in cui i blocchi di memoria e le spese generali di chiamata genereranno assolutamente la differenza tra moltiplicare e dividere fino al punto di irrilevanza. Quindi, scegli quello che si legge meglio in quel caso.

Se parli da un livello molto alto, non sarà misurabilmente più lento per qualsiasi cosa per cui potresti usarlo. Vedrai in altre risposte, le persone devono fare un milione di moltiplicazioni / divisioni solo per misurare una differenza inferiore al millisecondo tra i due.

Se sei ancora curioso, da un punto di vista dell'ottimizzazione di basso livello:

Divide tende ad avere una pipeline significativamente più lunga di moltiplicare. Ciò significa che ci vuole più tempo per ottenere il risultato, ma se riesci a mantenere il processore occupato con attività non dipendenti, non ti costerà più di un moltiplicatore.

La durata della differenza nella pipeline dipende completamente dall'hardware. L'ultimo hardware che ho usato è stato qualcosa come 9 cicli per una FPU moltiplicata e 50 cicli per una divisione FPU. Sembra molto, ma poi perderesti 1000 cicli per una mancanza di memoria, quindi puoi mettere le cose in prospettiva.

Un'analogia è mettere una torta nel microonde mentre guardi un programma televisivo. Il tempo totale che ti ha portato via dal programma televisivo è quanto tempo ci è voluto per metterlo nel microonde e portarlo fuori dal microonde. Per il resto del tempo continuavi a guardare il programma televisivo. Quindi, se la torta ha impiegato 10 minuti per cuocere invece di 1 minuto, in realtà non ha utilizzato altro tempo per guardare la TV.

In pratica, se vuoi arrivare al livello di preoccuparti della differenza tra Multiply e Divide, devi comprendere le pipeline, la cache, gli stalli dei rami, la previsione di fuori servizio e le dipendenze della pipeline. Se questo non suona come dove avevi intenzione di andare con questa domanda, la risposta corretta è ignorare la differenza tra i due.

Molti (molti) anni fa era assolutamente fondamentale evitare le divisioni e usare sempre i moltiplicatori, ma a quei tempi i colpi di memoria erano meno rilevanti e le divisioni erano molto peggiori. In questi giorni considero la leggibilità più alta, ma se non c'è differenza di leggibilità, penso che sia una buona abitudine optare per i multipli.


7

Scrivi quello che è più chiaro per indicare il tuo intento.

Dopo che il tuo programma funziona, scopri cosa è lento e rendilo più veloce.

Non farlo al contrario.


6

Fai quello che ti serve. Pensa prima al tuo lettore, non preoccuparti delle prestazioni finché non sei sicuro di avere un problema di prestazioni.

Lascia che il compilatore esegua le prestazioni per te.


5

Se stai lavorando con numeri interi o tipi non in virgola mobile, non dimenticare gli operatori di cambio di bit: << >>

    int y = 10;
    y = y >> 1;
    Console.WriteLine("value halved: " + y);
    y = y << 1;
    Console.WriteLine("now value doubled: " + y);

7
questa ottimizzazione viene eseguita automaticamente dietro le quinte in qualsiasi compilatore moderno.
Dustin Getz

Qualcuno ha verificato se controlla (usando bit ops) se un operando (?) Ha una versione spostabile per usarla invece? funzione mul (a, b) {se (b è 2) restituisce a << 1; se (b è 4) restituisce a << 2; // ... ecc. restituisce a * b; } La mia ipotesi è che l'IF sia così costoso da essere meno efficiente.
Christopher Lightfoot,

Non si stampava da nessuna parte vicino a quello che immaginavo; Non importa.
Christopher Lightfoot,

Per le operazioni const un normale compilatore dovrebbe fare il lavoro; ma qui stiamo usando Python quindi non sono sicuro che sia abbastanza intelligente da saperlo? (Dovrebbe essere).
Christopher Lightfoot,

Buona scorciatoia, tranne per il fatto che non è immediatamente chiaro cosa sta realmente accadendo. La maggior parte dei programmatori non riconosce nemmeno gli operatori bitshift.
Blazemonger

4

In realtà c'è una buona ragione per cui, come regola generale, la moltiplicazione sarà più veloce della divisione. La divisione in virgola mobile nell'hardware viene eseguita con algoritmi di spostamento e sottrazione condizionale ("divisione lunga" con numeri binari) o - più probabilmente oggigiorno - con iterazioni come l' algoritmo di Goldschmidt . Spostamento e sottrazione richiedono almeno un ciclo per bit di precisione (le iterazioni sono quasi impossibili da parallelizzare come lo sono lo spostamento e la somma della moltiplicazione) e gli algoritmi iterativi eseguono almeno una moltiplicazione per iterazione. In entrambi i casi, è molto probabile che la divisione richiederà più cicli. Ovviamente questo non tiene conto delle stranezze nei compilatori, nello spostamento dei dati o nella precisione. In generale, però, se stai codificando un ciclo interno in una parte sensibile al tempo di un programma, scrivere 0.5 * xo 1.0/2.0 * xpiuttosto che x / 2.0è una cosa ragionevole da fare. La pedanteria di "codificare ciò che è più chiaro" è assolutamente vera, ma tutti e tre sono così vicini in leggibilità che la pedanteria in questo caso è solo pedante.


3

Ho sempre imparato che la moltiplicazione è più efficiente.


"efficiente" è la parola sbagliata. È vero che la maggior parte dei processori si moltiplica più velocemente di quanto si dividano. Tuttavia, con le moderne architetture pipeline il tuo programma potrebbe non vedere alcuna differenza. Come molti altri stanno dicendo, stai davvero molto meglio facendo ciò che legge meglio a un essere umano.
TED

3

La moltiplicazione è solitamente più veloce, certamente mai più lenta. Tuttavia, se non è critico per la velocità, scrivi quello che è più chiaro.


2

La divisione in virgola mobile è (generalmente) particolarmente lenta, quindi mentre la moltiplicazione in virgola mobile è anche relativamente lenta, è probabilmente più veloce della divisione in virgola mobile.

Ma sono più propenso a rispondere "non importa", a meno che la profilazione non abbia dimostrato che la divisione è un po 'un collo di bottiglia rispetto alla moltiplicazione. Immagino, tuttavia, che la scelta tra moltiplicazione e divisione non avrà un grande impatto sulle prestazioni nella tua applicazione.


2

Questo diventa più un problema quando si programma in assembly o forse C. Immagino che con la maggior parte dei linguaggi moderni quell'ottimizzazione come questa venga fatta per me.


2

Diffidare di "supporre che la moltiplicazione sia in genere migliore, quindi cerco di attenermi a quella quando codifico",

Nel contesto di questa domanda specifica, meglio qui significa "più veloce". Il che non è molto utile.

Pensare alla velocità può essere un grave errore. Ci sono profonde implicazioni di errore nella specifica forma algebrica del calcolo.

Vedere Aritmetica in virgola mobile con analisi degli errori . Vedere Problemi di base in Aritmetica in virgola mobile e analisi degli errori .

Mentre alcuni valori in virgola mobile sono esatti, la maggior parte dei valori in virgola mobile sono un'approssimazione; sono un valore ideale più qualche errore. Ogni operazione si applica al valore ideale e al valore di errore.

I problemi maggiori derivano dal tentativo di manipolare due numeri quasi uguali. I bit più a destra (i bit di errore) vengono a dominare i risultati.

>>> for i in range(7):
...     a=1/(10.0**i)
...     b=(1/10.0)**i
...     print i, a, b, a-b
... 
0 1.0 1.0 0.0
1 0.1 0.1 0.0
2 0.01 0.01 -1.73472347598e-18
3 0.001 0.001 -2.16840434497e-19
4 0.0001 0.0001 -1.35525271561e-20
5 1e-05 1e-05 -1.69406589451e-21
6 1e-06 1e-06 -4.23516473627e-22

In questo esempio, puoi vedere che man mano che i valori si riducono, la differenza tra numeri quasi uguali crea risultati diversi da zero in cui la risposta corretta è zero.


1

Ho letto da qualche parte che la moltiplicazione è più efficiente in C / C ++; Nessuna idea per quanto riguarda le lingue interpretate: la differenza è probabilmente trascurabile a causa di tutte le altre spese generali.

A meno che non diventi un problema, attenersi a ciò che è più manutenibile / leggibile - odio quando le persone me lo dicono ma è così vero.


1

Suggerirei la moltiplicazione in generale, perché non devi passare i cicli assicurandoti che il tuo divisore non sia 0. Questo non si applica, ovviamente, se il tuo divisore è una costante.


1

Java Android, profilato su Samsung GT-S5830

public void Mutiplication()
{
    float a = 1.0f;

    for(int i=0; i<1000000; i++)
    {
        a *= 0.5f;
    }
}
public void Division()
{
    float a = 1.0f;

    for(int i=0; i<1000000; i++)
    {
        a /= 2.0f;
    }
}

Risultati?

Multiplications():   time/call: 1524.375 ms
Division():          time/call: 1220.003 ms

La divisione è circa il 20% più veloce della moltiplicazione (!)


1
Per essere realistici, dovresti provare a = i*0.5, no a *= 0.5. È così che la maggior parte dei programmatori utilizzerà le operazioni.
Blazemonger

1

Come per i post # 24 (la moltiplicazione è più veloce) e # 30, ma a volte sono entrambi altrettanto facili da capire:

1*1e-6F;

1/1e6F;

~ Li trovo entrambi altrettanto facili da leggere e devo ripeterli miliardi di volte. Quindi è utile sapere che la moltiplicazione è solitamente più veloce.


1

C'è una differenza, ma dipende dal compilatore. All'inizio su vs2003 (c ++) non ho avuto differenze significative per i tipi doppi (virgola mobile a 64 bit). Tuttavia, eseguendo nuovamente i test su vs2010, ho rilevato un'enorme differenza, fino a un fattore 4 più veloce per le moltiplicazioni. Tracciando questo, sembra che vs2003 e vs2010 generino un codice fpu diverso.

Su un Pentium 4, 2.8 GHz, vs2003:

  • Moltiplicazione: 8.09
  • Divisione: 7.97

Su uno Xeon W3530, vs2003:

  • Moltiplicazione: 4,68
  • Divisione: 4.64

Su uno Xeon W3530, vs2010:

  • Moltiplicazione: 5.33
  • Divisione: 21.05

Sembra che su vs2003 una divisione in un ciclo (quindi il divisore è stato usato più volte) è stata tradotta in una moltiplicazione con l'inverso. Su vs2010 questa ottimizzazione non viene più applicata (suppongo perché il risultato è leggermente diverso tra i due metodi). Nota anche che la CPU esegue le divisioni più velocemente non appena il tuo numeratore è 0,0. Non conosco l'algoritmo preciso cablato nel chip, ma forse dipende dal numero.

Modifica 18-03-2013: l'osservazione per vs2010


Mi chiedo se c'è qualche motivo per cui un compilatore non può sostituire, ad esempio, n/10.0con un'espressione del modulo (n * c1 + n * c2)? Mi aspetto che sulla maggior parte dei processori una divisione richieda più tempo di due moltiplicazioni e una divisione, e credo che la divisione per qualsiasi costante possa produrre un risultato arrotondato correttamente in tutti i casi utilizzando la formulazione indicata.
supercat

1

Ecco una risposta sciocca e divertente:

x / 2.0 non è equivalente a x * 0,5

Supponiamo che tu abbia scritto questo metodo il 22 ottobre 2008.

double half(double x) => x / 2.0;

Ora, 10 anni dopo, impari che puoi ottimizzare questo pezzo di codice. Il metodo è referenziato in centinaia di formule in tutta l'applicazione. Quindi lo cambi e sperimenterai un notevole miglioramento delle prestazioni del 5%.

double half(double x) => x * 0.5;

È stata la decisione giusta cambiare il codice? In matematica, le due espressioni sono effettivamente equivalenti. In informatica, ciò non è sempre vero. Si prega di leggere Minimizzare l'effetto dei problemi di precisione per maggiori dettagli. Se i tuoi valori calcolati sono - a un certo punto - confrontati con altri valori, cambierai il risultato dei casi limite. Per esempio:

double quantize(double x)
{
    if (half(x) > threshold))
        return 1;
    else
        return -1;
}

La linea di fondo è; una volta che ti sei accontentato di uno dei due, seguilo!


1
Downvote? Che ne dici di un commento che spiega i tuoi pensieri? Questa risposta è decisamente pertinente al 100%.
l33t

In informatica, moltiplicare / dividere i valori in virgola mobile per potenze di 2 è senza perdite, a meno che il valore non venga denormalizzato o trabocchi.
Soonts

Poiché il virgola mobile non è senza perdite al momento della divisione, non importa se la tua affermazione è vera. Anche se sarei molto sorpreso se lo fosse.
l33t

1
"La virgola mobile non è senza perdite al momento della divisione" solo quando si costruisce con un vecchio compilatore che emette codice x87 deprecato. Sull'hardware moderno solo avere una variabile float / doppia è senza perdita, sia a 32 che a 64 bit IEEE 754: en.wikipedia.org/wiki/IEEE_754 A causa del modo in cui funziona IEEE 754, quando dividi per 2 o moltiplichi per 0,5, stai diminuendo l'esponente per 1, il resto dei bit (segno + mantissa) non cambia. Ed entrambi 2e i 0.5numeri possono essere rappresentati esattamente in IEEE 754, senza alcuna perdita di precisione (a differenza di 0.4o 0.1, non possono).
Soonts

0

Bene, se assumiamo che un'operazione di aggiunta / sottotraccia costi 1, moltiplicare costa 5 e dividere i costi circa 20.


Da dove hai preso questi numeri? Esperienza? istinto? articolo su Internet? come cambierebbero per diversi tipi di dati?
kroiz

0

Dopo una discussione così lunga e interessante, ecco la mia opinione su questo: non c'è una risposta definitiva a questa domanda. Come alcune persone hanno sottolineato, dipende sia dall'hardware (cf piotrk e gast128 ) che dal compilatore (cf @Javier 's tests). Se la velocità non è critica, se la tua applicazione non ha bisogno di elaborare in tempo reale enormi quantità di dati, puoi optare per la chiarezza usando una divisione, mentre se la velocità di elaborazione o il carico del processore sono un problema, la moltiplicazione potrebbe essere la più sicura. Infine, a meno che tu non sappia esattamente su quale piattaforma verrà distribuita la tua applicazione, il benchmark non ha senso. E per chiarezza del codice, un singolo commento farebbe il lavoro!


-3

Tecnicamente non esiste la divisione, esiste solo la moltiplicazione per elementi inversi. Ad esempio, non dividi mai per 2, anzi moltiplichi per 0,5.

"Divisione" - prendiamoci in giro che esiste per un secondo - è sempre più difficile quella moltiplicazione perché per "dividere" xper yuno bisogna prima calcolare il valore y^{-1}tale y*y^{-1} = 1e poi fare la moltiplicazione x*y^{-1}. Se lo sai già, y^{-1}non calcolarlo da ydeve essere un'ottimizzazione.


3
Che ignora completamente la realtà di entrambi i comandi esistenti nel silicio.
NPSF3000

@ NPSF3000 - Non seguo. Assumendo che esistano entrambe le operazioni, si afferma semplicemente che l'operazione di divisione implica implicitamente il calcolo di un inverso moltiplicativo e di una moltiplicazione, che sarà sempre più difficile di una semplice moltiplicazione. Il silicio è un dettaglio di implementazione.
satnhak

@ BTyler. Se entrambi i comandi esistono nel silicio, ed entrambi i comandi richiedono lo stesso numero di cicli [come ci si aspetterebbe] di quanto siano relativamente complesse le istruzioni è completamente irrilevante da un punto di vista delle prestazioni.
NPSF3000

@ NPSF3000 - ma non richiedono entrambi lo stesso numero di cicli perché la moltiplicazione è più veloce.
satnhak
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.