Lazy Evaluation과 메모리

--

오늘은 스터디에서 하스켈의 bytestrings를 설명하는 과정에서 있었던 열띤 토론을 정리해볼까 한다. 함수형 프로그래밍을 공부하다보면 lazy라는 단어를 자주접하는데, 나는 과연 그 의미에 대해서 제대로 이해하고 있는 걸까? 이 블로그를 통해서 내가 맞게 이해하고 있는 것인지 확인하고, 제대로 이해하고 싶어서 정리해본다. (틀린 부분이 있다면, 고수님들의 조언을 부탁드립니다.)

논란이 있었던 주제의 언어는 하스켈이었지만, 여기서는 많은 사람들이 이해할 수 있는 Java Stream API 예제를 사용하겠다.

먼저 Lazy Evaluation이란 어떤 코드 조각을 실행할때, 그때그때 값을 평가하지 않고 정말 결과값이 필요한 시점까지 평가를 늦추는 것이다.

보통은 Lazy Evaluation의 장점으로는 아래와 같은 것들이 있다.

  • 필요할때만 평가되기 때문에 메모리를 효율적으로 사용할 수 있음
  • 무한 자료구조를 만들어 사용할 수 있음
  • 실행 도중의 오류상태를 피할 수 있음(컴파일 타임 체킹)
  • 컴파일러 최적화 가능

여기서 집중하고 싶은 부분은 게으른 평가가 일어날때 메모리를 어떻게 쓰는가 이다. 그리고 메모리를 효율적으로 사용한다는 것의 정확한 의미이다. 아래 두 코드보자.

두 함수의 기능은 동일하다. 그러나 imperativeStyle은 for 루프의 시작 시점부터 이미 메모리를 사용한다. 그리고 함수가 수행되는 내내 메모리에 주기적으로 access하고 값을 가져오고 할당한다. functionalStyle은 코드 조각만 존재하고 메모리를 전혀 사용하지 않는다.

정말 그 결과값이 필요한 순간까지 메모리를 사용하지 않기때문에, 이런 특징을 “메모리를 효율적으로 사용한다”라는 말의 의미로 생각했다.

그러나 항상 Lazy Evaluation을 사용하는 것이 효율적이진 않다(여기서는 메모리의 효율성을 말하는 것은 아님). 예를들어 numbers가 엄청나게 큰 리스트라고 가정해보자. functionalStyle의 경우, 출력 시점에 엄청나게 큰 리스트의 계산을 한번에 수행해야 하기때문에 메모리 오버헤드가 부담될 수 있다. 반면에 imperativeStyle은 출력 시점에는 이미 결과값을 Optional에 담고있기 때문에 메모리 오버헤드가 거의 없다. 메모리에서 결과값을 꺼내서 출력만 하면된다. 결과값을 구하기 위해서 필요한 메모리 사용은 함수가 실행되면서 분산되었다.

보통 이런 경우는 파일의 내용을 읽어와서 처리할때 발생한다. 파일의 크기가 큰 경우, Lazy Evaluation을 사용면 한번에 모든 연산을 수행하는 것이 시스템에 큰 부담을 줄 수 있다. (파일의 내용 전체를 적어도 한번은 access 해야함)

하스켈의 경우, 이러한 문제를 해결하기 위해서 bytestrings라는 것을 사용한다. Bytestrings는 1바이트인 타입의 리스트이다. bytestrings는 strict bytestrings와 lazy bytestrings가 있다.

strict의 경우, 그때그때 값을 평가하고, 무한한 자료구조를 만들수 없으며, 첫번째 바이트가 평가되면 전체가 다 평가된다. (이 부분도 의견이 분분했는데, 나는 전체가 다 평가된다는 것이 전체를 다 한번에 메모리에 올린다라는 것과는 다른 의미로 생각한다. 원문은 “If you evaluate the first byte of a strict bytestring, you have to evaluate it whole.” 이다.) 장점은 Lazy와 반대로 오버헤드가 적고, 그때그때 메모리에서 읽기때문에 메모리 효율이 떨어진다.

lazy의 경우, 하스켈의 리스트만큼 완전히 게으르진 않다. 64K 크기 단위로 평가된다. 어떤 큰 파일의 내용을 평가하면 첫번째 64K만 평가하고, 나머지는 promise 상태가 된다. 64K 단위로 차례차례 평가하면 계산하는 방식이다. lazy bytestrings는 64K 크기의 strict bytestrings의 리스트와 같다. 이 방법으로 lazy evaluation의 단점인 오버헤드 문제를 해결했다.

자 이제 검증할 시간이다. 피드백을 받고, 잘못된 부분이 있다면 바로잡고, 이번 기회에 완벽하게 정리하고자 한다.

--

--