Clojure Оптимизация производительности по сравнению с эквивалентной Java

1

Каковы наилучшие простые способы ускорить эту функцию? Эквивалентный код в 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.

Теги:
performance
clojure

2 ответа

4
Лучший ответ

Использование (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))))))
  • 1
    Благодарю. Версия Clojure теперь только в два раза медленнее, чем Java, и в 25 раз быстрее, чем раньше!
  • 0
    Но передача примечаний в аргументах в виде double-array не помогла, она предоставляла typehint ^ doubles и затем использовала aget, как вы предлагаете.
Показать ещё 1 комментарий
1

Вы пытались добавить?

(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 мс...

  • 0
    Я попробовал это, но, похоже, это не имело никакого значения.
  • 0
    Кстати, какие спектакли вы получаете? в Clojure против Java?
Показать ещё 4 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню