Я бы действительно ожидал, что Clojure будет значительно быстрее, чем Javascript, если вы оптимизируете свой код для повышения производительности.
Clojure будет статически компилироваться в довольно оптимизированный байт-код Java, когда вы дадите достаточно информации о статических типах (например, подсказки типов или приведение к примитивным типам).По крайней мере, теоретически, вы должны быть в состоянии приблизиться к чистой скорости Java, которая сама по себе очень близка к производительности собственного кода.
Так что давайте докажем это!
В этом случае у вас есть несколько проблем, из-за которых код Clojure работает медленно:
- Clojure по умолчанию поддерживает арифметику произвольной точности, поэтому любые арифметические операции автоматически проверяются напереполнение и, если необходимо, числа повышаются до BigIntegers и т. д. Эта дополнительная проверка добавляет небольшое количество служебных данных, которое обычно незначительно, но может проявиться, если вы выполняете арифметические операции в узком цикле, подобном этому.Самый простой способ исправить это в Clojure 1.2 - это использовать функции unchecked- * (это немного не элегантно, но будет значительно улучшено в Clojure 1.3)
- Если вы не укажете иначе, Clojure ведет себя динамически и блокируетаргументы функции.Поэтому я подозреваю, что ваш код создает и упаковывает много Integer / Longs.Способ удалить это для переменных цикла - использовать подсказки примитивного типа и использовать такие конструкции, как loop / recur.
- Аналогично,
n
упакован, что означает, что вызов функции <= не может быть оптимизирован дляиспользовать примитивную арифметику.Этого можно избежать, приведя n к длинному примитиву с локальным разрешением. </li> (time (some-function))
также является ненадежным способом для сравнения в Clojure, поскольку он не обязательно позволяет задействовать оптимизацию компиляции JIT.сначала нужно запустить (некоторую функцию) несколько раз, чтобы у JIT была возможность выполнить свою работу.
Поэтому мое предложение для оптимизированной версии дополнения Clojure будет более:
(defn add-up
"Adds up numbers from 1 to n"
[n]
(let [n2 (long n)] ; unbox loop limit
(loop [i (long 1) ; use "loop" for primitives
acc (long 0)] ; cast to primitive
(if (<= i n2) ; use unboxed loop limit
(recur (unchecked-inc i) (unchecked-add acc i)) ; use unchecked maths
acc))))
И лучший способ рассчитать время таков (чтобы JIT-компиляция состоялась):
(defn f [] (add-up 10000000))
(do
(dotimes [i 10] (f))
(time (f)))
Если я сделаю выше, я получу 6мс для решения Clojure в Clojure 1.2.Это примерно в 15-20 раз быстрее, чем код Node.js, и, возможно, в 80-100 раз быстрее, чем ваша оригинальная версия Clojure.
Кстати, это также почти так же быстро, как я могу заставить этот цикл работать чистоJava, поэтому я сомневаюсь, что можно было бы улучшить это на любом языке JVM.Это также ставит нас примерно в 2 машинных цикла на одну итерацию ... так что это, вероятно, недалеко от собственной скорости машинного кода!
(извините, не могу сравнить с Node.js на моей машине, но это 3,3 ГГц ядро i7 980X для всех, кто заинтересован)