Prima di esperimenti che ho aggiunto nella riga successiva project.clj:
:jvm-opts ^:replace [] ; Makes measurements more accurate
misure di base:
(def a (double-array (range 1000000))) ; 10 is too small for performance measurements
(quick-bench (sum-of-squares a)) ; ... Execution time mean : 27.617748 ms ...
(quick-bench (sum-of-squares2 a)) ; ... Execution time mean : 1.259175 ms ...
Questo è più o meno coerente con la differenza di tempo nella domanda. Cerchiamo di non utilizzare array Java (che non sono realmente idiomatica per Clojure):
(def b (mapv (partial * 1.0) (range 1000000))) ; Persistent vector
(quick-bench (sum-of-squares b)) ; ... Execution time mean : 14.808644 ms ...
quasi 2 volte più veloce. Ora rimuoviamo i suggerimenti del tipo:
(defn sum-of-squares3
"Given a vector v, compute the sum of the squares of elements."
[v]
(r/fold + (r/map #(* % %) v)))
(quick-bench (sum-of-squares3 a)) ; Execution time mean : 30.392206 ms
(quick-bench (sum-of-squares3 b)) ; Execution time mean : 15.583379 ms
Tempo di esecuzione aumentato solo marginalmente rispetto alla versione con suggerimenti tipo. Tra l'altro, la versione con transducers ha prestazioni molto simili ed è molto più pulito:
(defn sum-of-squares3 [v]
(transduce (map #(* % %)) + v))
Ora circa ulteriore tipo hinting. anzi possiamo ottimizzare primo sum-of-squares
implementazione:
(defn square ^double [^double x] (* x x))
(defn sum-of-squares4
"Given a vector v, compute the sum of the squares of elements."
[v]
(r/fold + (r/map square v)))
(quick-bench (sum-of-squares4 b)) ; ... Execution time mean : 12.891831 ms ...
(defn pl
(^double [] 0.0)
(^double [^double x] (+ x))
(^double [^double x ^double y] (+ x y)))
(defn sum-of-squares5
"Given a vector v, compute the sum of the squares of elements."
[v]
(r/fold pl (r/map square v)))
(quick-bench (sum-of-squares5 b)) ; ... Execution time mean : 9.441748 ms ...
Nota # 1: tipo di suggerimenti su argomenti e valore di ritorno di sum-of-squares4
e sum-of-squares5
non hanno ulteriori vantaggi prestazionali.
Nota n. 2: In genere è una cattiva pratica iniziare con optimizations. La versione straight-forward (apply + (map square v))
avrà prestazioni sufficienti per la maggior parte delle situazioni. sum-of-squares2
è molto lontano dall'idiomatico e non utilizza letteralmente concetti di Clojure. Se questo è davvero un codice critico per le prestazioni - meglio implementarlo in Java e usare l'interoperabilità. Il codice sarà molto più pulito nonostante abbia 2 lingue. O addirittura implementarlo in codice non gestito (C, C++) e utilizzare JNI (non molto manutenibile ma se correttamente implementato, può fornire le migliori prestazioni possibili).
Grazie Rodrigo. Non ero a conoscenza di areduce. Questo è esattamente quello di cui avevo bisogno, un modo per dire a una riduzione di usare aget ... – Scott
Felice di aiutare, Scott! –