2012-04-16 2 views
6

Прочитав несколько постов в блоге на эту тему, я обнаружил, что мутирует массив в Clojure так:Оптимизация мутации массива в Clojure

(defn m [xs ys] 
    (dotimes [i (count xs)] 
    (aset #^ints ys (int i) 
    (int (* (int 3) (int (aget #^ints xs (int i)))))))) 

где (def xs (into-array Integer/TYPE (range 1000000))) и (def ys (into-array Integer/TYPE (range 1000000)))

уходило в среднем 14ms согласно Критерию, тогда как Java должен делать то же самое,

public static int[] m(int[] x, int[] y) 
{ 
    for(int i=0; i<x.length; i++) 
    y[i] = 3*x[i]; 
    return y; 
} 

занимает в среднем 800US. **

Я делаю все, что в моих силах, чтобы все ускорилось, и есть ли еще какие-нибудь пути оптимизации?

** Я приуроченная их с помощью критериум с (report-result (bench (m xs ys)) :verbose) и (report-result (bench (. Test m xs ys)) :verbose)

+0

если вы делаете много такого рода вещи, вероятно, вы должны смотреть на 'core.matrix' и/или' vectorz-clj', а не руки -кодирование математических операций на массивах. – mikera

+0

yeh, теперь, когда они существуют, конечно, – Hendekagon

ответ

5

Попробуйте это на Clojure 1.3:

(set! *unchecked-math* true) 

(defn m [^longs xs ^longs ys] 
    (dotimes [i (count xs)] 
    (aset ys i 
     (* 3 (aget xs i))))) 
+0

cheers - это самый чистый – Hendekagon

+0

на моей рабочей машине. Я получаю 2 мс для вышеперечисленного Clojure и 1,95 мс для Java, используя longs для всего. Так они и есть. Интересно, что я получаю 800us для Java, используя ints на этой машине, хотя это одна и та же версия JVM и Linux (без разницы между ints и longs для Clojure). – Hendekagon

+0

(set! * Unchecked-math * true) - означает ли это, что все математические операции будут отключены глобально или только это пространство имен? Как контролировать область действия? – Hendekagon

5

Если вы хотите скорость, вы должны войти в мир примитивов, а не оставлять его, пока вы не закончите. Это бесполезно, начиная с коробкой Integer i, а затем преобразуя ее в примитив на каждом сайте использования. Может быть, вы можете сделать dotimes произвести ints (тип-hint объявление i), но не уверен. То, что я знаю, является конструкцией loop-recur с примитивными инициализаторами циклов: (loop [i (int 0)] ... (recur (unchecked-inc i)). Кроме того, в вашем примере у вас есть (int 3). Вам нужно let, что заранее, чтобы не повторять распаковку на каждой итерации.

BTW, вы можете использовать (int-array (range 1000000)) для создания вашего инициализированного массива и всего лишь (int-array 1000000) для пустого.

UPDATE

По состоянию на Clojure 1.3 с расширенной поддержкой примитивов, большинство из того, что я написал выше, не применяется больше. dotimes уже использует примитивную арифметику, поэтому все, что вам нужно написать, чтобы получить полную производительность является

(dotimes [i (alength ^ints xs)] 
    (aset ^ints ys i (unchecked-multiply (aget ^ints xs i) 3) 

В принципе, нет int Конструкторов необходимо, и использовать unchecked-multiply.

+0

ОК, поэтому я сейчас на другой машине, но результаты пока: оригинал - 36 мс, (пусть [z (int 3)] (dotimes ... is 37ms, (int (count xs)) ... составляет 35 мс (оба предыдущих 2) 37 мс, удаление типов подсказок (int i) также равно 37 мс, а форма петли-повтора тоже равна 37 мс (не удивительно, поскольку dotimes реализуется с помощью loop-recur) – Hendekagon

+0

Проблема не реализовано ли оно с помощью цикла loop-recur (конечно, это так), но связано ли циклическое var с примитивным или коробочным числом. Оказывается, что в Clojure 1.3 это как примитивный var, поэтому я предложил реализовать в raw loop-recur составляет ровно то же самое, что и просто использовать простые точки. Однако вам нужно использовать longs вместо ints. Никогда не принуждайте clojure к принуждению от long back to int. –

+0

ok change to longs - 36ms https: // gist. github.com/2397842 – Hendekagon

Смежные вопросы