Я начал решать project euler проблемы в clojure как средство изучения языка. при внедрении сита для эратосфенов для проблемы 10 i изначально испытывала крайне низкую производительность, которая, по-видимому, вытекает из прохождения boolean-array
. я нашел, что могу обойти проблему, добавив подсказки типа или переупорядочивая код, чтобы вспомогательные функции могли получить доступ к boolean-array
непосредственно из родительской области, но я не понимаю, почему это необходимо. (type sieve)
в любой из вспомогательных функций возвращает [Z
, поэтому кажется, что clojure уже знает, что это boolean-array
. может ли кто-нибудь объяснить, почему здесь нужны подсказки типа?Плохая производительность при прохождении вокруг булевых массивов в clojure
извинения за стену кода, я не знаю, какие части можно удалить, пока еще иллюстрирует проблему.
;;; returns a vector containing all primes smaller than limit
(defn gen-primes-orig [limit]
(defn next-unmarked [sieve v]
(loop [i (inc v)]
(cond
(>= i limit) nil
(false? (aget sieve i)) i
:true (recur (inc i)))))
(defn mark-powers-of! [sieve v]
(loop [i (+ v v)]
(if (>= i limit) sieve
(do
(aset-boolean sieve i true)
(recur (+ i v))))))
(defn collect-primes [sieve]
(loop [primes []
i 0]
(cond
(>= i limit) primes
(false? (aget sieve i)) (recur (conj primes i) (inc i))
:true (recur primes (inc i)))))
(let [sieve (boolean-array limit false)]
;; 0 and 1 are not primes
(aset-boolean sieve 0 true)
(aset-boolean sieve 1 true)
(loop [v 0]
(let [v (next-unmarked sieve v)]
(if (nil? v) (collect-primes sieve)
(do
(mark-powers-of! sieve v)
(recur v)))))))
(defn gen-primes-hint [limit]
(defn next-unmarked [^booleans sieve v]
;; same body as in gen-primes-orig
)
(defn mark-powers-of! [^booleans sieve v]
;; same body as in gen-primes-orig
)
(defn collect-primes [^booleans sieve]
;; same body as in gen-primes-orig
)
(let [sieve (boolean-array limit false)]
;; same body as in gen-primes-orig
))
(defn gen-primes-letfn [limit]
(let [sieve (boolean-array limit false)]
;; 0 and 1 are not primes
(aset-boolean sieve 0 true)
(aset-boolean sieve 1 true)
(letfn [(next-unmarked [v]
;; same body as in gen-primes-orig
)
(mark-powers-of! [v]
;; same body as in gen-primes-orig
)
(collect-primes []
;; same body as in gen-primes-orig
)]
(loop [v 0]
(let [v (next-unmarked v)]
(if (nil? v) (collect-primes)
(do
(mark-powers-of! v)
(recur v))))))))
и вот время работы для каждой из трех смесей. (Результат был удален. Все смеси дают одинаковый, правильный результат.)
user> (time (apply + (gen-primes-orig 2000000)))
"Elapsed time: 108001.047327 msecs"
user> (time (apply + (gen-primes-hint 2000000)))
"Elapsed time: 2599.091978 msecs"
user> (time (apply + (gen-primes-letfn 2000000)))
"Elapsed time: 2768.060355 msecs"
ОК, спасибо. Я думал, что clojure будет использовать вывод типа для определения правильного типа вызовов. и для этого конкретного случая не следует ли определять тип во время компиляции, так как функции являются «внутренними», и, следовательно, все точки вызова известны? – MrGnu