게시글

강좌61 - JavaScript - 2년 전 등록 / 한 달 전 수정

Lazy evaluation

지연 평가

안녕하세요. 이번 시간에는 자바스크립트의 Lazy Evaluation에 대한 소개를 해볼까 합니다. 자바스크립트에서는 배열 데이터 구조를 많이 쓰는데 배열 연산의 성능을 끌어올리기 위해 필요한 기법 중 하나입니다. 문자열과 같이 반복되는 것(Iterable)이라면 일부는 Lazy Evaluation 적용이 가능합니다.

간단한 예시를 들어봅시다. 효과를 보여드리기 위해 조금 극단적입니다.

Array(100000).fill().map((v, i) => i)
  .map((v) => v + 1)
  .filter((v) => v % 3 === 0)
  .slice(0, 10);

0부터 십만까지의 숫자가 담긴 배열이 있다면 각각의 요소에 1씩을 더해서 3의 배수가 되는 숫자 10개만 추려보고 싶은 상황입니다. 보통 위와 같이 작성하실텐데요. 그러면 연산량은 map에서 십만 filter에서 십만, slice에서 10해서 200010이 됩니다. 숫자 열 개 추리는 데 조금 오바인 것 같죠?

사람이었다면 아이디어를 내서 앞에서부터 찾아보기 시작할 것입니다. 최종적으로 10개만 뽑으면 되니까 앞에서부터 차례대로 검사하면 2, 5, 8, 11, 14, 17, 20, 23, 26, 29입니다. 30개만 검사하고 끝낼 수 있는 것이죠. 1 더하는 작업과 필터링하는 작업을 다 쳐봐도 작업이 총 100번도 안 됩니다. 컴퓨터는 200010개의 작업을 수행했는데 컴퓨터보다 사람이 낫죠?

왜 컴퓨터보다 사람이 더 뛰어난 경우가 나왔을까요? 바로 컴퓨터는 작업을 순차적으로 수행하기 때문입니다. 10만 개의 숫자가 있다면 다음 작업이 1씩을 더하는 것이므로 10만 개의 숫자에 1씩 더합니다. 그리고 나서야 그 다음 작업을 보는데, 3의 배수만 걸러내는 것입니다. 그래서 10만 개의 숫자를 검사해서 3의 배수만 걸러냅니다. 그 다음에 10개를 추려내는 것을 보고는 앞에서 10개를 자르는 것이죠. 컴퓨터 입장에서는 앞선 작업을 할 때 최종적으로 10개만 추려내면 되는지 몰랐던 것입니다.

다음과 같은 경우를 생각해봅시다.

Array(100000).fill().map((v, i) => i)
  .slice(0, 30)
  .map((v) => v + 1)
  .filter((v) => v % 3 === 0)
  .slice(0, 10);

위와 같은 경우에는 결과는 같지만 작업 횟수가 slice 30, map 30, filter 30, slice 10해서 총 100번이 됩니다. 처음 연산을 시작하기 전에 slice(0, 30)만 넣어주었는데 말이죠. 작업 범위를 줄여주었더니 확 차이가 납니다. 컴퓨터가 가끔씩 작업에서 사람보다 비효율적인 경우는 이런 경우로, 주어진 작업에서 이렇게 작업 범위를 제한하는 일을 스스로 하지 못하기 때문입니다.

어떻게 해야 작업 범위를 스스로 제한하게 할 수 있을까요? 답은 사람이 하는 것처럼 하는 것입니다. 사람은 문제를 전체적으로 보고 filter나 slice같이 범위를 줄일 수 있는 작업을 먼저 발견합니다. 컴퓨터도 이렇게 작업을 시키면 됩니다.

기존에는 0~십만까지 수를 1~십만일까지로 바꾸고, 3의 배수를 걸러내고, 10개를 추려냈다면, 이번에는 숫자 하나 하나에 전체 작업을 통째로 하는 겁니다. 예를 들어 0에 1을 더해보고, 3의 배수인지 찾고, 아니니까 다음으로 넘어갑니다. 다음엔 1에 1을 더해보고, 3의 배수인지 찾고, 아니니까 다음으로 넘어갑니다. 다음엔 2에 1을 더해보고, 3의 배수인지 찾고, 맞으니까 결과물 배열에 담습니다. 이런 식으로 반복해서 결과물 배열이 10개인지 검사해서 10개라면 작업을 중단하는 것이죠.

단, 이것을 직접 구현하기는 조금 어렵고 lodash 라이브러리가 이러한 일을 해줍니다. lodash 라이브러리는 npm을 통해서 설치하거나 <script src='https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js'></script>를 넣으면 됩니다.

_().range(100001)
  .map((v) => v + 1)
  .filter((v) => v % 3 === 0)
  .slice(0, 10)
  .value();

range(100001)이 lodash를 사용해 0부터 100000까지의 숫자를 만드는 코드입니다. lodash가 알아서 지연 평가를 해주므로 코드는 비슷해보이지만 성능이 2000배 차이납니다. 작업량 100번 대 2십만10번이니까 2000배 차이가 맞네요. 단, lodash의 지연 평가를 사용할 때는 최종적으로 value 함수를 써야한다는 사실을 기억하세요!

구현에 따라서 엄청나게 배열 성능이 차이날 수 있다는 사실! Lazy Evaluation이라는 키워드를 기억하세요.

조회수:
0
목록
투표로 게시글에 관해 피드백을 해주시면 게시글 수정 시 반영됩니다. 오류가 있다면 어떤 부분에 오류가 있는지도 알려주세요! 잘못된 정보가 퍼져나가지 않도록 도와주세요.
Copyright 2016- . 무단 전재 및 재배포 금지. 출처 표기 시 인용 가능.

댓글

아직 댓글이 없습니다.