VisualVM을 이용한 Garbage Collection 확인
Programming Clojure 책을 보면서 Clojure 공부를 하고 있던 중, 함수형 프로그래밍 부분에서 피보나치 수열을 이용하여 재귀 프로그래밍 및 지연 연산 관련 내용을 다루면서 성능 별로 몇 가지 버전을 다루고 있길래 마침 귀에 들어온 VisualVM이라는 자바 모니터링 툴을 통해 Garbage Collection이 되는 모습을 직접 보기로 했습니다.
내용 자체는 별 것 없습니다. 그냥 '무겁게 돌아가는 걸 보니 진짜 무겁게 돌아가네'라는, 당연한 것을 확인한 수준이라... 그냥 Clojure 공부 겸 VisualVM 체험 학습 정도로만 봐주시기 바랍니다.
피보나치 수열 구하는 함수 중에 힙 메모리를 많이 잡아먹는 stack-consuming-fibo
를 돌려봤습니다.
(stack-consuming-fibo 45)
나온 그래프는 아래와 같습니다. 붉은 색 박스 구간이 위 함수를 실행한 때입니다.
위 그림의 아래 그래프는 힙 메모리 사용량으로, 서서히 증가하다가 일정 수준에 도달하면 GC 작업에 의해 다시 리셋되는 것을 확인할 수 있습니다. 아래 그래프는 GC 동작 자체를 나타내는 그래프입니다. 함수를 실행하면 힙 메모리 사용량이 급증하면서 수시로 GC가 실행되고 힙 메모리가 리셋되는 것을 확인할 수 있습니다. 위 예에서는 45번째 수열 값을 구하라고 했는데, 50번째 수열을 구하려 했더니 안멈추더군요 :)
참고로, Programming Clojure 책에서는 무척 큰 값 (1000000000) 을 넣어도 결과값을 얻어내던데, 제가 직접 돌렸더니 92번째 수열 값부터는 결과 값이 정수 값 범위를 벗어났다면서 튕기더군요. 이유는 모르겠습니다. Clojure 버전에 따라 다르게 동작한 것 같은데... (저는 1.4.0 또는 1.5.0을 사용하고 있습니다)
그래서 아래와 같이 명시적으로 Big Number로 지정해주면 실행됩니다. 다만 결과 값도 끝에 'N'이 붙어서 별로 안 이쁩니다.
(defn stack-consuming-fibo [n]
(cond
(= n 0N) 0N
(= n 1N) 1N
:else (+ (stack-consuming-fibo (- n 1N))
(stack-consuming-fibo (- n 2N)))))
(defn recur-fibo [n]
(letfn [(fib
[current next n]
(if (zero? n)
current
(recur next (+ current next) (dec n))))]
(fib 0N 1N n)))
(defn lazy-seq-fibo
([]
(concat [0N 1N] (lazy-seq-fibo 0N 1N)))
([a b]
(let [n (+ a b)]
(lazy-seq
(cons n (lazy-seq-fibo b n))))))
(defn fibo []
(map first (iterate (fn [[a b]] [b (+ a b)]) [0N 1N])))