Przed eksperymentów dodałem kolejną linię w project.clj:
:jvm-opts ^:replace [] ; Makes measurements more accurate
podstawowe pomiary:
(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 ...
Jest to mniej więcej zgodne z różnicą czasu w pytaniu. Spróbujmy nie używać tablic Java (które nie są naprawdę idiomatyczne dla Clojure):
(def b (mapv (partial * 1.0) (range 1000000))) ; Persistent vector
(quick-bench (sum-of-squares b)) ; ... Execution time mean : 14.808644 ms ...
prawie 2 razy szybciej. Teraz usuńmy podpowiedzi typu:
(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
Czas realizacji zwiększył się nieznacznie w porównaniu do wersji z podpowiedziami typu. Nawiasem mówiąc, wersja z transducers ma bardzo podobną wydajność i jest znacznie czystsze:
(defn sum-of-squares3 [v]
(transduce (map #(* % %)) + v))
Teraz o dodatkowy typ podpowiadania. Możemy rzeczywiście zoptymalizować pierwszy sum-of-squares
realizacji:
(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 ...
Uwaga # 1: typ wskazówek na temat argumentów i wartości zwracanej sum-of-squares4
i sum-of-squares5
mieć żadnych dodatkowych korzyści z wydajnością.
Uwaga # 2: Na ogół złą praktyką jest rozpoczynać od optimizations. Prosta wersja (apply + (map square v))
będzie miała wystarczająco dobrą wydajność w większości sytuacji. sum-of-squares2
jest bardzo daleki od idiomatycznego i nie używa dosłownie żadnych koncepcji Clojure. Jeśli jest to naprawdę krytyczny pod względem wydajności kod - lepiej go zaimplementować w Javie i używać współdziałania. Kod będzie znacznie czystszy pomimo posiadania 2 języków. Lub nawet zaimplementować go w niezarządzanym kodzie (C, C++) i używać JNI (nie bardzo konserwowalny, ale jeśli poprawnie zaimplementowany, może dać najlepszą możliwą wydajność).
Dzięki Rodrigo. Nie zdawałem sobie sprawy z kiczu. Dokładnie tego potrzebowałem, sposób, żeby powiedzieć, że redukcja ma zastosowanie aget ... – Scott
Cieszę się, że pomogę, Scott! –