2017-12-15 7 views
1

Clojure를 처음 사용했습니다.Clojure : 대형 게으름 수집을 줄이면 메모리가 늘어납니다.

(defn generator [seed factor] 
    (drop 1 (reductions 
      (fn [acc _] (mod (* acc factor) 2147483647)) 
      seed 
      ; using dummy infinite seq to keep the reductions going 
      (repeat 1)))) 

순서대로 각 숫자는 이전 계산에 따라 달라집니다 : 나는 숫자의 무한 게으른 시퀀스를 만드는 다음 코드를 가지고있다. 모든 중간 결과가 필요하기 때문에 reductions을 사용하고 있습니다.

나는 다음과 같이 두 개의 발전기를 인스턴스화 :

(def gen-a (generator 59 16807)) 
(def gen-b (generator 393 48271)) 

나는 다음 큰 N에 대해,이 시퀀스의 n 연속적인 결과를 비교, 그들은 동일한 횟수를 반환합니다.

는 처음 엔 내가 좋아하는 일을했다 : 그것은 너무 오래 복용하고 있었고, 난이 약 4GB로 프로그램의 메모리 사용 스파이크를 보았다

(defn run [] 
    (->> (interleave gen-a gen-b) 
     (partition 2) 
     (take 40000000) 
     (filter #(apply = %)) 
     (count))) 

. 아직도

(defn run-2 [] 
    (reduce 
    (fn [acc [a b]] 
     (if (= a b) 
     (inc acc) 
     acc)) 
    0 
    (take 40000000 (partition 2 (interleave gen-a gen-b))))) 

: 일부 println들과 함께 나는 약 1000 만 반복 후 정말 느린있어 것을보고, 그래서 어쩌면 count 메모리에 전체 시퀀스를 저장하기 위해 필요하다고 생각하고 있었다, 그래서 나는 사용 reduce 그것을 변경 그것은 많은 메모리를 할당하고 수백만의 처음 몇 후 상당히 느려졌어요. 나는 그것이 메모리에 전체 게으른 시퀀스를 저장하는 것하지만 난 이유를 잘 모르겠어요 것을 확신 해요, 그래서 수동으로 머리를 던져하려고 :

(defn run-3 [] 
    (loop [xs (take 40000000 (partition 2 (interleave gen-a gen-b))) 
     total 0] 
    (cond 
     (empty? xs) total 
     (apply = (first xs)) (recur (rest xs) (inc total)) 
     :else (recur (rest xs) total)))) 

다시 동일한 결과를. 이것은 내가 내 xs 시퀀스를 만드는 데 사용하고있는 모든 함수가 게으르다는 것을 읽었 기 때문에 나에게 당황 스럽습니다. 그리고 현재의 아이템만을 사용하고 있기 때문에 상수 메모리를 사용할 것으로 기대하고 있습니다.

파이썬 배경에서 오는 것은 기본적으로 Python Generators을 에뮬레이트하려고합니다. 아마도 뭔가 명백한 것이 빠졌을 것입니다. 감사!

답변

6

생성자는 (게으른) 시퀀스가 ​​아닙니다.

는 현재 머리에 들고됩니다

(def gen-a (generator 59 16807)) 
(def gen-b (generator 393 48271)) 

gen-agen-b 머리에 시퀀스를 참조 gobal 바르 있습니다. 또한, 함수로 gen-agen-b을 정의

(defn run [] 
    (->> (interleave (generator 59 16807) (generator 393 48271)) 
     (partition 2) 
     (take 40000000) 
     (filter #(apply = %)) 
     (count))) 

:

(defn gen-a 
    [] 
    (generator 59 16807))) 
... 

(defn run [] 
    (->> (interleave (gen-a) (gen-b)) 
     (partition 2) 
     (take 40000000) 
     (filter #(apply = %)) 
     (count))) 
+0

감사합니다. 나는'generator '가 2 -> 무한대의 게으른 시퀀스를 리턴했다고 생각했다. 편집 : 신경 쓰지 마라, 나는'def'가 서열의 머리를 잡고 있다는 것을 몰랐다. – orangeblock

+0

@orangeblock, 문제는 전역 (수집 할 수없는) var에서 지연 목록의 헤드를 참조했다는 것입니다. 편집 : 예, 그게 무슨 뜻이야 :-) –

-2

당신은 Clojure의 using the Tupelo library 파이썬 스타일 발생기 기능을 얻을 수 있습니다

당신은 아마 뭔가를 원한다.그냥과 같이 lazy-genyield를 사용 결과에

(ns tst.demo.core 
    (:use tupelo.test) 
    (:require 
    [tupelo.core :as t] )) 

(defn rand-gen 
    [seed factor] 
    (t/lazy-gen 
    (loop [acc seed] 
     (let [next (mod (* acc factor) 2147483647)] 
     (t/yield next) 
     (recur next))))) 

(defn run2 [num-rand] 
    (->> (interleave 
     ; restrict to [0..99] to simulate bad rand #'s 
     (map #(mod % 100) (rand-gen 59 16807)) 
     (map #(mod % 100) (rand-gen 393 48271))) 
     (partition 2) 
     (take num-rand) 
     (filter #(apply = %)) 
     (count))) 

(t/spyx (time (run2 1e5))) ; expect ~1% will overlap => 1e3 
(t/spyx (time (run2 1e6))) ; expect ~1% will overlap => 1e4 
(t/spyx (time (run2 1e7))) ; expect ~1% will overlap => 1e5 

:

"Elapsed time: 409.697922 msecs" (time (run2 100000.0)) => 1025 
"Elapsed time: 3250.592798 msecs" (time (run2 1000000.0)) => 9970 
"Elapsed time: 32995.194574 msecs" (time (run2 1.0E7))  => 100068 
-1

을 오히려 reductions을 사용하는 것보다 직접 게으른 순서를 만들 수있다. 이 대답은 lazy-consfrom the Tupelo library입니다 (use lazy-seq from clojure.core도 가능). 결과

(ns tst.demo.core 
    (:use tupelo.test) 
    (:require 
    [tupelo.core :as t] )) 

(defn rand-gen 
    [seed factor] 
    (let [next (mod (* seed factor) 2147483647)] 
    (t/lazy-cons next (rand-gen next factor)))) 

(defn run2 [num-rand] 
    (->> (interleave 
     ; restrict to [0..99] to simulate bad rand #'s 
     (map #(mod % 100) (rand-gen 59 16807)) 
     (map #(mod % 100) (rand-gen 393 48271))) 
     (partition 2) 
     (take num-rand) 
     (filter #(apply = %)) 
     (count))) 

(t/spyx (time (run2 1e5))) ; expect ~1% will overlap => 1e3 
(t/spyx (time (run2 1e6))) ; expect ~1% will overlap => 1e4 
(t/spyx (time (run2 1e7))) ; expect ~1% will overlap => 1e5 

: 우리는 우리가 정말 어쨌든 사용하지 않은 발전기 기능의 물건을 잘라 이후 실행 시간이 더 빨리 배에 대한 것을

"Elapsed time: 90.42 msecs" (time (run2 100000.0)) => 1025 
"Elapsed time: 862.60 msecs" (time (run2 1000000.0)) => 9970 
"Elapsed time: 8474.25 msecs" (time (run2  1.0E7)) => 100068 

참고.

+0

고마워. 나는 여전히 Clojure를 배우고 있으며 몇 번 lazy-seq를 보았지만 아직 사용하지는 않았다. 다음 번에 확실히 확인해 보겠습니다. 또한이 단계에서 제 3 자 라이브러리를 사용하고 싶지는 않지만 메모리 문제에 대한 질문이 많습니다. – orangeblock

관련 문제