2015-12-03 3 views
1

Я просматриваю книгу программирования «Clojure» Chas Emerick, Brian Carper и Christophe Grand.Clojure последовательный вызов функции и памятка

В части, касающейся memoization, я заметил значительно меньшее время, прошедшее для второго (и других) вызовов функции, даже не используя memoization.

Уверенный, используя memoization уменьшенное время на порядок величины, но мне интересно, если Clojure выполняет что-то вроде memoization внутренне.

Вот код:

(defn prime? 
    [n] 
    (cond 
    (== 1 n) false 
    (== 2 n) true 
    (even? n) false 
    :else (->> (range 3 (inc (Math/sqrt n)) 2) 
       (filter #(zero? (rem n %))) 
       empty?))) 


(def n 123) 

(time (prime? n)) 
(time (prime? n)) 

(let [m-prime? (memoize prime?)] 
    (time (m-prime? n)) 
    (time (m-prime? n))) 

И выход:

"Elapsed time: 0.235977 msecs" 
"Elapsed time: 0.054549 msecs" 
"Elapsed time: 0.045127 msecs" 
"Elapsed time: 0.003814 msecs" 

Итак, почему второй вызов почти в 5 раз быстрее, чем первый?

+3

Я думаю, вы пропустили JIT. Я думаю, что первое повышение производительности - это работа JIT. – fl00r

+0

ОК, может быть, кто-то может предложить хорошее вступительное чтение о том, как Clojure компилируется на Java-байт-код? –

ответ

2

В первый раз, когда вы вызываете любую функцию Clojure, она медленнее. Я думаю, что Clojure задерживает некоторую внутреннюю связь или настройку, пока не будет вызвана функция.

Это может быть JIT, но я так не думаю. Это будет срабатывать, когда функция определена.

Меморандум не является панацеей. Если то, что вы вызываете, на самом деле не очень медленное, тогда оно может быть медленнее, чем просто выполнять исходный код. Например, когда я написал алгоритм спаривания турниров, я экспериментировал с memoization на функции, которая вызывалась многократно и всегда возвращала тот же результат, и это значительно замедляло алгоритм сопоставления. Это было в 2-3 раза медленнее с воспоминаниями, чем без.

+0

Yah, я когда-то профилировал несоответствующую мемуатацию, потребовалось много времени, векторизация, хеширование и кеш, поиск в arg-списке, который даже превышает простое вычисление. – Davyzhu

2

Внутренние функции каждой функции clojure представлены jvm-классами - первый вызов длиннее, потому что загрузчик классов загружает класс.

Как сказано в предыдущем ответе - это не JIT точно, поскольку JIT для большей оптимизации имеет определенный порог - можно изменить с помощью XX: CompileThreshold - значение по умолчанию установлено на 10000 - это одна из причин, почему jvm должен перед началом бенчмаркинга прогреться.

Имейте в виду, что memoization - это только кеш. И функция должна быть «чистой» для правильной работы - для компилятора было бы чрезвычайно сложно угадать, какая функция может быть потенциально автоматически сохранена в памяти.

+2

Класс загружается, когда функция читается, а не в первый раз, когда она вызывается. Запустите repl с параметром '-verbose: class', и вы можете это увидеть. – Bill

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