2012-04-16 12 views
6

aver letto un paio di post di blog sul tema, ho trovato che mutando una matrice in Clojure come questo:Ottimizzazione serie mutazione in Clojure

(defn m [xs ys] 
    (dotimes [i (count xs)] 
    (aset #^ints ys (int i) 
    (int (* (int 3) (int (aget #^ints xs (int i)))))))) 

dove (def xs (into-array Integer/TYPE (range 1000000))) e (def ys (into-array Integer/TYPE (range 1000000)))

ha preso una media di 14ms secondo Criterium, mentre Java per fare lo stesso,

public static int[] m(int[] x, int[] y) 
{ 
    for(int i=0; i<x.length; i++) 
    y[i] = 3*x[i]; 
    return y; 
} 

richiede una media di 800us. **

Sto facendo tutto il possibile per rendere le cose andare veloce e c'è altro posso andare giù per il percorso di ottimizzazione?

** ho cronometrato questi utilizzando Criterium con (report-result (bench (m xs ys)) :verbose) e (report-result (bench (. Test m xs ys)) :verbose)

+0

se si sta facendo un sacco di questo tipo di cose, si dovrebbe probabilmente essere guardando 'core.matrix' e/o' vectorz-clj' piuttosto che mano -codifica delle operazioni matematiche sugli array. – mikera

+0

yeh, ora che esistono sicuramente – Hendekagon

risposta

5

Prova questo su Clojure 1.3:

(set! *unchecked-math* true) 

(defn m [^longs xs ^longs ys] 
    (dotimes [i (count xs)] 
    (aset ys i 
     (* 3 (aget xs i))))) 
+0

evviva - questo è il più pulito – Hendekagon

+0

indietro sul mio computer di lavoro Ottengo 2ms per il Clojure sopra e 1.95ms per Java che utilizza long per tutto. Quindi sono uguali. È interessante notare che ottengo 800us per Java usando ints su questa macchina anche se è la stessa versione di JVM e Linux (non c'è differenza tra gli interi e long per Clojure). – Hendekagon

+0

(impostato! * Unchecked-math * true): significa che tutte le operazioni matematiche saranno deselezionate globalmente o solo questo spazio dei nomi? Come controllare l'ambito? – Hendekagon

5

Se volete la velocità, è necessario entrare nel mondo dei primitivi e non lasciarlo fino a quando il gioco è fatto. È inutile iniziare con una scatola Integer i e quindi convertirla in una primitiva in ogni sito di utilizzo. Forse è possibile effettuare dotimes produrre ints (digitare-suggerire la dichiarazione di i), ma non è sicuro. Quello che so funziona è un costrutto loop-recur con inizializzatori primitivi di loop vars: (loop [i (int 0)] ... (recur (unchecked-inc i)). Inoltre, nel tuo esempio hai (int 3). È necessario let che in anticipo in modo da non ripetere l'unboxing in ogni iterazione.

BTW, è possibile utilizzare (int-array (range 1000000)) per creare l'array inizializzato e solo (int-array 1000000) per quello vuoto.

UPDATE

Come di Clojure 1.3, con la sua maggiore supporto per i primitivi, la maggior parte di quello che ho scritto sopra non si applica più. dotimes utilizza già l'aritmetica primitivo, quindi tutto è necessario scrivere per ottenere il massimo delle prestazioni è

(dotimes [i (alength ^ints xs)] 
    (aset ^ints ys i (unchecked-multiply (aget ^ints xs i) 3) 

In sostanza, non int costruttori necessario, e utilizzare unchecked-multiply.

+0

ok quindi ora sono su una macchina diversa ma i risultati finora: originale è 36ms, (let [z (int 3)] (dotimes ... è 37ms, (int (count xs)) ... è 35ms, (entrambi i precedenti 2) 37ms, rimuovendo i suggerimenti di tipo (int i) è anch'esso 37ms e la forma loop-recur è anch'essa 37ms (non sorprendendo come dotimes è implementato con loop-recur) – Hendekagon

+0

Il problema è non se è implementato con 'loop-recur' (ovviamente lo è) ma se la variabile loop è vincolata a un numero primitivo o in box. Si scopre che in Clojure 1.3 è una variabile primitiva, quindi cosa ho proposto di implementare in raw loop-recur equivale esattamente alla stessa cosa con l'uso di semplici dotimes.Tuttavia, devi usare long invece di ints. Non forzare mai il clojure a forzare da long back a int. –

+0

ok cambiando a longs - 36ms https: // gist. github.com/2397842 – Hendekagon