강의로 돌아가기
Wonmin Jeon

ES5로 변환을 한다면?

강의 재밌게 잘 보고 있습니다. 좋은 강의 만들어주셔서 감사합니다~^

질문:
브라우져에서의 폭 넓은 지원을 위해 babel같은것을 활용하여 변환을 한다면 range와 L.range에 성능에 차이가 있을거같은데요~제가 range와 L.range를 변환된 코드로 테스트해보니 오히려 range가 빠른것같습니다.
혹시 ES5로 변환을 하여 사용을 한다면 이런 부분은 어떻게 처리하는게 좋을까요~??

1 개의 답변
유인동

강의 봐주셔서 정말 감사합니다 :)

이 질문은 제가 오랫동안 고민해온 부분이기도 합니다. 그리고 꽤나 다각도로 봐야하는 문제이기도 합니다.

결국은 속도와 표현력(아름다운코드), 이 두 가지에 대한 이야기입니다.

일단 트랜스파일링을 하게 된다면, 시간 복잡도는 같지만 꽤나 복잡한 코드가 나오게 됩니다. 특히 async/await을 사용한 코드나, 제너레이터를 사용한 코드가 그렇습니다. 시간 복잡도는 같지만 콜스택이 필요 이상으로 많아지기도 하고, 각종 분기문들이 코드들 사이에 끼워 들어가게 됩니다. 그렇다보니 제너레이터 같은 ES6+ 한정 기능을 IE나 구 버전 Node.js를 트랜스파일링을 통해 지원하게 되면, 모던 브라우저에서도 속도 절감을 감수할 수 밖에 없게 됩니다.

이런 상황이되면 take, takeWhile 등으로 시간 복잡도 자체가 변경될 수 있는 로직이 아닌 경우에는 득보다는 실만 있는 상황이 생기는거죠. 일반적인 프로그램에서는 대부분 take나 takeWhile 등이 필요한 상황보다 그렇지 않은 상황이 더 많은 편입니다. take 없이 map+filter+reduce로 끝나는 로직들이죠. 이유는 보통 데이터베이스를 사용하게 되고, 데이터베이스에서 limit을 통해 이미 값을 추려서 가져오기 때문이기도 하고, 그렇지 않아도 보통 전체를 순회하면서 결과를 내야하는 경우가 경험상 더 많다고 느낍니다.

한 가지 더 말씀드리자면 lodash 같은 라이브러리들이 ES5기반에서 지연 평가를 지원하기도 하는데요. 이런 라이브러리 역시 지연성을 관리하기 위한 추가적인 로직이 들어갑니다. 사실 이 로직이 추가되는 것도 그렇지만 더 중요한 것은 자바스크립트에 존재하지 않는 '타입'을 만들어야만 이 문제를 ES5에서 해결할 수 있다는 점입니다. 혹은 트랜스파일링을 해야 자바스크립트에 있게 되는 '제너레이터/이터러블'을 이용해야 구현할 수 있습니다. 무엇을 선택하든 결국은 지연성을 위한 추가적인 로직이 복잡하게 들어가게 되는거죠. 그렇기 때문에 lodash도 지연평가가 동작하는 조건을 1) 체이닝에서만 되게 한다. 2) length가 200개 이상의 array에서만 동작한다. 같은 조건을 내부에 넣어두기도 한 것입니다.

속도 측면에서 좀 더 말씀을 드리면 각각 맨 위에 있을 수록 성능이 좋습니다.

  1. ES5 환경에서
    1) 명령형 코드가 가장 빠름.
    2) 지연성을 사용하지 않는 선의 함수형 코드가 빠름.
    3) 지연성을 사용하는 경우, 선택적으로 사용해야 빠름.
    4) 제너레이터를 사용하고 트랜스파일링을 한 코드가 가장 느림.

  2. ES6으로 동작하는 환경에서
    1) 명령형 코드가 가장 빠름.
    2) Array만 사용한다고 가정할 때의 코드에서 for...of 보다 for i++이 빠름. 그러나 이터러블이라는 훌륭한 추상을 사용하지 못함.
    3) 모던 브라우저나, 최신 Node.js 환경에서 ES6+ 기반으로 작성된 트랜스파일링하지 않은 코드로 지연성을 다루는게 가장 빠름.
    4) 만일 ES5 환경을 위해 트랜스파일링된 코드로 배포하면 여기도 함께 느려짐.

ES6으로 돌아가는 환경에서는 메모리 사용 측면에서 큰 Array를 다루게 될 수록, 또 꼭 그렇지 않더라도 꽤 많은 상황에서 지연성을 기반으로 사용하는 것이 유리한 부분이 있습니다. 그리고 사실 작은 배열이든 상관 없이 ES6을 쓰면서 명령형코드로 작성한다고 하더라도 for...of 를 사용한다면 내부적으로 이터레이터로 변환하여 사용하기 때문에 for i++을 사용하는 것이 아닌 이상 기본적으로 제너레이터/이터러블을 사용하는 것이, 추가적인 부하가 크다고 보기는 어렵고, 거의 모든 상황에 있어서 괜찮은 성능을 내는 제너레이터/이터러블에 표를 주는 것이 좋다고 생각합니다.

서론이 너무 길었는데요. 이것들에 대한 종합적인 제 의견은 다음과 같고, 저희 팀에서도 이렇게 하고 있기도 합니다.

  1. ES5를 함께 지원해야하는 프로그램의 경우

    • ES5로 작성하며, 지연성은 사용하지 않는다.
    • 지연성을 사용하지 않는다는 이야기는 find 같은 함수를 take(1, filter(f, arr))[0] 이런식으로 구현하지 않고, 따로 구현해둔다는 이야기입니다.
    • 로직이 필요로하는 시간 복잡도 자체가 다른 상황에서, 지연성과 표현력을 모두 얻고 싶은 경우에만 L.map, L.filter, L.take 로 개발자가 직접 선택해서 사용한다. partial.js가 이런식으로 구현되어있습니다.
    • lodash도 아마 이러한 고민과 생각으로 체이닝하지 않은 기본 함수들을 지연성을 이용하여 구현하지 않았을 것입니다.
    • 트랜스파일링하지 않는다.
    • 개발자가 사용하는 라이브러리가 트랜스파일링이 필요한 상태라면 배포된 코드가 보편적으로 느리게 동작할 것이라는 생각으로 코딩을 해야합니다.
    • 근데 이것도 케바케이긴 한것이 성능이 그렇게 중요하지 않은 서비스라면 상관이 없습니다.
    • requestAnimationFrame 같은 함수를 사용하면서, 16ms 안에 항상 모든 연산이 마쳐지도록 하고자 한다거나,
    • 애니메이션 등을 많이 필요로 한다거나,
    • canvas 등을 사용해서 역시 빠른 연산이 필요한 상황이라면, 트랜스파일된 코드를 쓰거나 '기본이 지연'인 라이브러리를 쓰는게 불안할 수 있습니다.
    • 그래서 저희는 IE 지원이 필요한 부분에 있어서는, partial.js를 기반으로 ES5 코드로 서비스를 운영하고, 함수형 프로그래밍을 하고 있습니다.
  2. ES6의 환경이라면

    • 제너레이터/이터러블을 적극 사용합니다.
    • 이것을 적극 사용한다고해서 애니메이션, requestAnimationFrame, canvas 등을 사용할 때 문제를 일으킬 것이라는 생각이 전혀 들지 않아 마음이 편합니다.
    • 트랜스파일링하지 않는다면 말이죠!
    • 그렇다면 아주 좋은 표현력을 가졌으면서도 보편적으로 전혀 느리지 않은 라이브러리와 서비스를 제공할 수 있습니다.

이 정도가 제가 그동안 고민해오고 적용해온 내용들입니다.

아래는 좀 연관된 푸념인데요. 집필했었던 책인 '함수형 자바스크립트 프로그래밍'에서도 위와 같은 생각들을 담아두었습니다.
그러다보니 책이 '함수형 프로그래밍'에 집중하기보다는 좀 장황한 현실 문제에 대한 이야기가 함께 많이 섞여있고, 개인적으로는 이점이 아쉬움이 많습니다.
저는 이런 내용이 어떤 이유에서든 독자들에게 필요할 것이라 기대했는데, 결론적으로 그렇지는 않았던 것 같습니다.

왜냐면 이미 독자의 선택이나 스타일이나 상황이, 이미 트랜스파일을 한 코드를 써야하거나, 쓰고 싶거나, 보편적으로 다소 느린 것을 이미 인정한 방법론을 택하고 있는 경우라면 위와 같은 생각들이 이미 전혀 필요가 없는 상태이니 장황하기만 한 것이죠. ㅠㅠ..

어쨌든 기회나 시간이 된다면 이러한 이야기를 영상으로도 언제 한 번 나눠볼까 합니다.

정답이 있는 이야기가 아니다보니 긴 상황들을 늘어놓고 마칩니다.

감사합니다.

  • Wonmin Jeon
    너무나도 상세하고 전문적인 답변을 해주셔서 진심으로 감사드립니다~남은 강의도 재밌게 듣겠습니다~^^ 장문의 답변을 읽으면서 하나 더 궁금한게 생겼는데요. 혹시 ES5를 지원하는 환경에서 모듈 관리는 어떻게 하시는지 궁금합니다. 예를들면 require.js같은 걸 사용하시는지 별도의 시스템을 만들어서 사용하시는지요...^^; 다시 한번 정성어린 답변 감사합니다~!!!! Wonmin Jeon 2019.01.07 14:20
  • 유인동
    역시 ES5에서도 가능한 방식을 사용하기위해 어떻게 보면 더 전통적인 방식인 '네임스페이스 방식'을 글로벌 환경에 네임스페이스 기반으로 등록해놓고 필요한거 꺼내서 사용하는식으로 사용하고 있습니다. 감사합니다. 유인동 2019.01.07 16:01
  • 유인동
    amd 를 써봤는데, 상호참조 문제나 이런거에서 조금 힘들더라고요 개인적으로는요. ES6 modules가 짱인거 같아요. :) 유인동 2019.01.07 16:02
답변 쓰기
이 입력폼은 마크다운 문법을 지원합니다. 마크다운 가이드 를 참고하세요.