Каковы наилучшие простые способы ускорить эту функцию? Эквивалентный код в java почти в 50 раз быстрее, согласно Criterium.
Бьюсь об заклад, если я использую Java-массив и уменьшаю количество бокса, которое бы все помогло, но я думал, что сначала опубликую здесь, чтобы увидеть, были ли какие-то основные ошибки, которые я делал, что можно было легко устранить. Заметьте, что я уже указывал (double...) для Clojure, что значительно улучшило производительность, но все равно ничего подобного Java. Я также сначала преобразовал seq используя (double-array...) вместо использования (vec...) внутри функции, а также улучшил производительность, но опять же, ничего подобного Java.
(defn cosine-similarity [ma mb]
(let [va (vec ma), vb (vec mb)]
(loop [p (double 0)
na (double 0)
nb (double 0)
i (dec (count va))]
(if (neg? i)
(/ p (* (Math/sqrt na) (Math/sqrt nb)))
(let [a (double (va i))
b (double (vb i))]
(recur (+ p (* a b))
(+ na (* a a))
(+ nb (* b b))
(dec i)))))))
Заметим, что ma и mb - оба seqs, содержащие 200 пар каждый. В java-версии они передаются как double [] args.
Использование (double 0)
не имеет преимущества в производительности, которое вы бы не получили от прямого указания 0.0
(двойной литерал).
Вы получите значительно лучшую производительность, если вы передадите ma
и mb
виде double-array
и назовите args как doubles
, не конвертируйте их в вектор через vec
и не используйте aget
для поиска элемента. Это должно оставить вам что-то очень близкое к производительности java-кода.
double
вызовы внутри блока let не понадобятся, если вы используете двойные массивы как функции args.
конечный результат должен выглядеть примерно так:
(defn cosine-similarity [^doubles ma ^doubles mb]
(loop [p 0.0
na 0.0
nb 0.0
i (dec (count va))]
(if (neg? i)
(/ p (* (Math/sqrt na) (Math/sqrt nb)))
(let [a (aget va i)
b (aget vb i)]
(recur (+ p (* a b))
(+ na (* a a))
(+ nb (* b b))
(dec i))))))
Вы пытались добавить?
(set! *unchecked-math* true)
Поскольку вы знаете диапазон, вы, вероятно, можете использовать его для получения дополнительной скорости.
Изменение: @noisesmith прав, двойной массив и ввод ввода делает огромную разницу.
Edit2: Быстрое получение быстрых результатов после комментариев Алекс Миллер.
(set! *unchecked-math* true)
(defn ^double cosine-similarity
[^doubles va ^doubles vb]
(loop [p 0.0
na 0.0
nb 0.0
i (dec (alength va))]
(if (< i 0)
(/ p (* (Math/sqrt na) (Math/sqrt nb)))
(let [a (aget va i)
b (aget vb i)]
(recur (+ p (* a b))
(+ na (* a a))
(+ nb (* b b))
(dec i))))))
(defn rand-double-arr [n m]
(double-array
(take n (repeatedly #(rand m)))))
(def ma (rand-double-arr 200 10000))
(def mb (rand-double-arr 200 10000))
; using do times
(dotimes [_ 30] (time (cosine-similarity ma mb)))
; ...
; "Elapsed time: 0.003537 msecs"
; using criterium: [criterium "0.4.3"]
(use 'criterium.core)
(quick-bench (cosine-similarity ma mb))
;
; Execution time mean : 2.072280 µs
; Execution time std-deviation : 214.653997 ns
; Execution time lower quantile : 1.765412 µs ( 2.5%)
; Execution time upper quantile : 2.284536 µs (97.5%)
Overhead used : 6.128119 ns
Первая версия была в диапазоне 500 ~ 1000 мс...