Повышение производительности программы ClojureScript

У меня есть программа ClojureScript, которая в основном выполняет математические вычисления для коллекций. Он был разработан на идиоматическом, независимом от хоста Clojure, поэтому его легко протестировать. К моему удивлению (и вопреки тому, что ответы предложили бы Что быстрее , Clojure или ClojureScript (и почему)?), тот же код в ClojureScript работает в 5-10 раз медленнее, чем его эквивалент в Clojure.

Вот что я сделал. Я открыл lein repl и ответил в браузере на http://clojurescript.net/. Затем я попробовал эти фрагменты в обоих REPL.

 (time (dotimes [x 1000000] (+ 2 8)))

 (let [coll (list 1 2 3)] (time (dotimes [x 1000000] (first coll))))

Затем я открыл консоль javascript в ответе браузера и написал минималистичную функцию тестирования,

 function benchmark(count, fun) {
   var t0 = new Date();
   for (i = 0; i < count; i++) {
     fun();
   }
   var t1 = new Date();
   return t1.getTime() - t0.getTime();
 }

Вернуться в браузер REPL:

 (defn multiply [] (* 42 1.2))

Затем попробуйте как собственное умножение javascript, так и его вариант clojurescript в консоли javascript,

 benchmark(1000000, cljs.user.multiply);

 benchmark(1000000, function(){ 42 * 1.2 });

Что я нашел

  • Собственная математика JavaScript сопоставима с математикой в ​​Clojure
  • ClojureScript в 5-10 раз медленнее любого из них

Теперь мой вопрос: как мне улучшить производительность моей программы ClojureScript?

Есть несколько подходов, которые я рассмотрел до сих пор

  • Вернитесь к использованию изменяемых массивов javascript и объектов за кулисами. (Это вообще возможно?)
  • Вернитесь к использованию собственных математических операторов javascript. (Это вообще возможно?)
  • Явное использование массивов javascript с (aget js/v 0)
  • Используйте менее амбициозную реализацию clojure-for-javascript, например https://github.com/chlorinejs/chlorine или https://github.com/gozala/wisp Они генерируют более идиоматический javascript, но они не поддерживают пространства имен, которые я часто использую.

person Adam Schmideg    schedule 14.05.2013    source источник


Ответы (2)


JavaScript имеет явный возврат, поэтому

function () { 42 * 1.2 }

ничего не делает; вы хотите сравнить

function () { return 42 * 1.2 }

вместо. Это именно то, что компилируется версия ClojureScript, поэтому не будет никакой разницы (в ClojureScript базовые арифметические функции при использовании не высшего порядка встраиваются как обычные выражения JavaScript на основе операторов).

Теперь Clojure определенно быстрее ClojureScript на этом этапе. Отчасти причина в том, что Clojure по-прежнему более тщательно настроен, чем ClojureScript, хотя ClojureScript в этой области улучшается довольно быстрыми темпами. Другая часть заключается в том, что Clojure имеет более зрелый JIT, которым можно воспользоваться (современные движки JS, в частности V8, довольно хороши, но пока еще не совсем для HotSpot).

Однако величину разницы довольно сложно измерить; тот факт, что задействованы JIT, означает, что цикл с телом, свободным от каких-либо побочных эффектов, таких как тот, который в вопросе, вероятно, будет оптимизирован, возможно, даже при первом запуске через него (за счет использования замены в стеке , используется HotSpot и я думаю также и V8 - хотя мне бы пришлось проверить, чтобы быть уверенным). Итак, лучше протестировать что-то вроде

(def arr (long-array 1))

;;; benchmark this
(dotimes [_ 1000000]
  (aset (longs arr) 0 (inc (aget (longs arr) 0))))

(longs вызов, чтобы избежать отражения в Clojure; также можно использовать ^longs подсказку).

Наконец, как в Clojure, так и в ClojureScript, для определенных типов кода, особенно чувствительного к производительности, лучше всего использовать собственные массивы и тому подобное. К счастью, с этим нет проблем: на стороне ClojureScript у вас есть array, js-obj, aget, aset, make-array, вы можете использовать :mutable метаданные в полях в deftype, чтобы иметь возможность set! в телах методов и т. Д.

person Michał Marczyk    schedule 14.05.2013

Математика ClojureScript - это математика JavaScript. Да, если производительность критична, используйте массивы JavaScript и предоставленные низкоуровневые операторы, они гарантируют создание оптимального кода там, где это возможно (т. Е. Без использования более высокого порядка). Постоянные структуры данных ClojureScript записываются следующим образом: изменение массива, арифметика, изменение битов.

У меня есть небольшой пример эффективного ClojureScript - http://github.com/swannodette/cljs-stl/blob/master/src/cljs_stl/spectral/demo.cljs, который может оказаться полезным в качестве руководства.

person dnolen    schedule 14.05.2013
comment
Я думал, у вас где-то в ClojureScript есть спектральная норма! +1 к этому. - person Michał Marczyk; 15.05.2013