Visto che non posso ancora aggiungere commenti, farò un po 'più di lavoro e analizzerò tutti. Sto mettendo l'analisi in cima; tuttavia, i dati rilevanti sono di seguito. (Nota: tutto questo è fatto anche in 6.12.3 - non ancora GHC 7 magic.)
Analisi:
Versione 1: spettacolo è abbastanza buona per interi, in particolare quelli più breve che abbiamo. Rendere le stringhe in realtà tende a essere decente in GHC; tuttavia, la lettura di stringhe e la scrittura di stringhe di grandi dimensioni su file (o stdout, anche se non si vorrebbe farlo) sono il punto in cui il codice può assolutamente eseguire la scansione. Avrei il sospetto che molti dei dettagli alla base del perché questo sia così veloce sono dovuti a intelligenti ottimizzazioni in mostra per Ints.
Versione 2: Questo era il più lento del gruppo quando compilato. Alcuni problemi: il contrario è rigido nel suo argomento. Ciò significa che non si è in grado di eseguire calcoli sulla prima parte dell'elenco mentre si calcolano gli elementi successivi; devi calcolarli tutti, girarli e poi fare i tuoi calcoli (cioè (`mod` 10)) sugli elementi della lista. Anche se questo può sembrare piccolo, può portare a un maggiore utilizzo della memoria (notare anche i 5 GB di memoria heap allocati qui) e calcoli più lenti. (Per farla breve: non utilizzare il contrario.)
Versione 3: Ricordare come ho appena detto di non utilizzare il contrario? Risulta, se lo estrai, questo scende a 1.79s tempo di esecuzione totale - appena più lento della linea di base. L'unico problema qui è che mentre approfondisci il numero, stai costruendo la colonna vertebrale dell'elenco nella direzione sbagliata (essenzialmente, stai andando "nella" lista con la ricorsione, invece che con "accedervi"). la lista).
Versione 4: Questa è un'implementazione molto intelligente. Beneficiate di molte cose carine: per esempio, quotRem dovrebbe usare l'algoritmo euclideo, che è logaritmico nel suo argomento più ampio. (Forse è più veloce, ma non credo che ci sia qualcosa che è più di un fattore costante più veloce di Euclid.) Inoltre, cons off sulla lista come discusso l'ultima volta, in modo che non devi risolvere alcun thunks di lista come vai - la lista è già interamente costruita quando torni indietro per analizzarla. Come puoi vedere, le prestazioni ne traggono vantaggio.
Questo codice era probabilmente il più lento in GHCi perché molte delle ottimizzazioni eseguite con il flag -O3 in GHC si occupavano di creare elenchi più velocemente, mentre GHCi non avrebbe fatto nulla di tutto ciò.
Lezioni: contro il modo in cui a destra in una lista, orologio per severità intermedia che può rallentare calcoli, e fare qualche noia nel guardare le statistiche a grana fine delle prestazioni del codice. Compilati anche con le bandiere -O3: ogni volta che non lo fai, tutte quelle persone che impiegano molte ore a fare in modo che GHC super veloce ottenga grandi occhi da cucciolo.
dati:
Ho appena preso tutte le quattro funzioni, li bloccato in un unico .hs file, quindi cambiato, se necessario, in modo da riflettere la funzione in uso. Inoltre, ho superato il limite fino a 5e6, perché in alcuni casi il codice compilato veniva eseguito in meno di mezzo secondo su 1e6 e questo può iniziare a causare problemi di granularità con le misurazioni che stiamo facendo.
Opzioni del compilatore: utilizzare ghc --make -O3 [nome file] .hs per fare in modo che GHC esegua l'ottimizzazione. Effettueremo il dump delle statistiche sull'errore standard usando le cifre + RTS -sstderr.
Dumping a -sstderr ci dà in uscita che assomiglia a questo, nel caso di digits1:
digits1 +RTS -sstderr
12000000
2,885,827,628 bytes allocated in the heap
446,080 bytes copied during GC
3,224 bytes maximum residency (1 sample(s))
12,100 bytes maximum slop
1 MB total memory in use (0 MB lost due to fragmentation)
Generation 0: 5504 collections, 0 parallel, 0.06s, 0.03s elapsed
Generation 1: 1 collections, 0 parallel, 0.00s, 0.00s elapsed
INIT time 0.00s ( 0.00s elapsed)
MUT time 1.61s ( 1.66s elapsed)
GC time 0.06s ( 0.03s elapsed)
EXIT time 0.00s ( 0.00s elapsed)
Total time 1.67s ( 1.69s elapsed)
%GC time 3.7% (1.5% elapsed)
Alloc rate 1,795,998,050 bytes per MUT second
Productivity 96.3% of total user, 95.2% of total elapsed
Ci sono tre statistiche chiave qui:
- memoria totale in uso: solo 1MB mezzi questa versione è molto efficiente in termini di spazio.
- Tempo totale: 1,61s non significa nulla ora, ma vedremo come appare rispetto alle altre implementazioni.
- Produttività: questo è solo il 100% meno raccolta dei rifiuti; visto che siamo al 96,3%, ciò significa che non stiamo creando un sacco di oggetti che lasciamo in giro in memoria ..
Va bene, passiamo alla versione 2.
digits2 +RTS -sstderr
12000000
5,512,869,824 bytes allocated in the heap
1,312,416 bytes copied during GC
3,336 bytes maximum residency (1 sample(s))
13,048 bytes maximum slop
1 MB total memory in use (0 MB lost due to fragmentation)
Generation 0: 10515 collections, 0 parallel, 0.06s, 0.04s elapsed
Generation 1: 1 collections, 0 parallel, 0.00s, 0.00s elapsed
INIT time 0.00s ( 0.00s elapsed)
MUT time 3.20s ( 3.25s elapsed)
GC time 0.06s ( 0.04s elapsed)
EXIT time 0.00s ( 0.00s elapsed)
Total time 3.26s ( 3.29s elapsed)
%GC time 1.9% (1.2% elapsed)
Alloc rate 1,723,838,984 bytes per MUT second
Productivity 98.1% of total user, 97.1% of total elapsed
Bene, quindi stiamo vedendo uno schema interessante.
- Stessa quantità di memoria utilizzata. Ciò significa che questa è un'implementazione piuttosto buona, anche se potrebbe significare che dobbiamo testare su input di campioni più alti per vedere se possiamo trovare una differenza.
- Ci vuole il doppio del tempo. Torneremo ad alcune speculazioni sul motivo per cui questo è più tardi.
- In realtà è leggermente più produttivo, ma dato che GC non è una porzione enorme di entrambi i programmi, questo non ci aiuta in alcun modo significativo.
Versione 3:
digits3 +RTS -sstderr
12000000
3,231,154,752 bytes allocated in the heap
832,724 bytes copied during GC
3,292 bytes maximum residency (1 sample(s))
12,100 bytes maximum slop
1 MB total memory in use (0 MB lost due to fragmentation)
Generation 0: 6163 collections, 0 parallel, 0.02s, 0.02s elapsed
Generation 1: 1 collections, 0 parallel, 0.00s, 0.00s elapsed
INIT time 0.00s ( 0.00s elapsed)
MUT time 2.09s ( 2.08s elapsed)
GC time 0.02s ( 0.02s elapsed)
EXIT time 0.00s ( 0.00s elapsed)
Total time 2.11s ( 2.10s elapsed)
%GC time 0.7% (1.0% elapsed)
Alloc rate 1,545,701,615 bytes per MUT second
Productivity 99.3% of total user, 99.3% of total elapsed
Va bene, quindi stiamo vedendo alcuni modelli strani.
- Siamo ancora a 1 MB di memoria totale in uso. Quindi non abbiamo colpito nulla di memoria-inefficiente, che è buono.
- Non siamo abbastanza a cifre1, ma abbiamo digits2 battere abbastanza facilmente.
- GC molto piccolo. (Tenete a mente che tutto ciò che oltre il 95% la produttività è molto buono, quindi non siamo in realtà si tratta di qualcosa di troppo importante qui.)
E, infine, la versione 4:
digits4 +RTS -sstderr
12000000
1,347,856,636 bytes allocated in the heap
270,692 bytes copied during GC
3,180 bytes maximum residency (1 sample(s))
12,100 bytes maximum slop
1 MB total memory in use (0 MB lost due to fragmentation)
Generation 0: 2570 collections, 0 parallel, 0.00s, 0.01s elapsed
Generation 1: 1 collections, 0 parallel, 0.00s, 0.00s elapsed
INIT time 0.00s ( 0.00s elapsed)
MUT time 1.09s ( 1.08s elapsed)
GC time 0.00s ( 0.01s elapsed)
EXIT time 0.00s ( 0.00s elapsed)
Total time 1.09s ( 1.09s elapsed)
%GC time 0.0% (0.8% elapsed)
Alloc rate 1,234,293,036 bytes per MUT second
Productivity 100.0% of total user, 100.5% of total elapsed
Wowza! Scopriamolo:
- Siamo ancora a 1 MB totale. Questa è quasi certamente una caratteristica di queste implementazioni, poiché rimangono a 1 MB sugli ingressi di 5e5 e 5e7. Una testimonianza di pigrizia, se vuoi.
- Abbiamo tagliato circa il 32% del tempo originale, il che è piuttosto impressionante.
- Sospetto che le percentuali qui riflettano la granularità nel monitoraggio di -sstderr piuttosto che qualsiasi calcolo su particelle superluminali.
compilazione del codice sopra invece di eseguire in GHCi dà risultati molto diversi. 'digits4' è 1,8 volte più veloce di' digits' quando compilato w/-O3. – gawi
Il motivo è probabilmente che 'showInt' può essere ottimizzato dal compilatore, mentre ghci non farà alcuna ottimizzazione. – fuz
compila il codice con almeno -O2 (come ha detto gawi) quindi valuta usando il criterio, e per l'amore di tutto ciò che è buono non usare 'mod', usa' rem' !!! –