참고로 말하자면, 나는 이글을 쓰는 사람이나 읽는 사람 모두 바보라고 생각하지 않는다. 하지만 가끔은 어떤 주제가 당신을 바보로 느끼도록 만들 때가 있다. 자바스크립트 코드를 구동하는 엔진이 그런 주제 중의 하나이다 (최소한 나에게는).
웹에서 코드를 작성할 때, 단지 문자들을 나열했을 뿐인데, 브라우저에서 뭔가가 나타나는 걸 보면 가끔은 약간의 마법같다는 생각이 든다. 하지만 기술 뒤에 숨겨진 마법을 이해한다면 당신의 프로그래밍 솜씨를 향상시키는 데에 도움이 될 것이다. 최소한 자바스크립트 기반의 웹이나 모바일 기술의 내부에서 일어나는 일들에 대해 설명을 할 때 바보같다는 느낌이 덜 들게 될 수는 있을 것이다.
… 중략 …
몇가지 용어 정리
‘자바스크립트 엔진’은 주로 가상머신이라 불린다. ‘가상 머신’은 특정 컴퓨터 시스템을 소프트웨어 기반으로 에뮬레이션 한 것을 뜻한다. 많은 종류의 가상 머신이 있으며, 얼마나 정확하게 실제의 물리적 머신을 에뮬레이션 할 수 있느냐에 따라 분류된다.
예를 들어, ‘시스템 가상 머신’은 운영체제가 실행될 수 있는 플랫폼의 완전한 에뮬레이션을 제공한다. 맥 사용자들은 Parallels를 잘 알텐데, 이는 당신의 맥에서 윈도우를 구동할 수 있게 해주는 시스템 가상 머신이다.
반면에 ‘프로세스 가상 머신’은 완전한 기능을 제공하지는 않지만, 하나의 프로그램이나 프로세스를 구동할 수 있다. Wine은 프로세스 가상 머신으로서 리눅스 머신에서 윈도우 어플리케이션을 실행할 수 있게 해주지만, 전체 윈도우 운영체제를 제공하지는 않는다.
자바스크립트 엔진은 자바스크립트 코드를 해석하고 실행하기 위해 만들어진 일종의 프로세스 가상 머신이다.
주의: 레이아웃 엔진과 자바스크립트 엔진은 다르다. 레이아웃 엔진은 브라우저가 웹페이지의 레이아웃을 배치할 때 사용되는 반면, 더 하위레벨인 자바스크립트 엔진은 코드를 해석하고 실행한다. 여기에 좋은 설명이 있다.
그래서 자바스크립트 엔진은 정확히 어떤 일을 하는걸까?
자바스크립트 엔진의 기본적인 역할은, 개발자가 작성한 자바스크립트 코드를 브라우저에 의해 해석되거나 어플리케이션에 임베드 될 수 있는 빠르고 최적화된 코드로 변환하는 일이다. 실제로 JavaScriptCore는 스스로를 ‘최적화 가상 머신’ 이라 부른다.
더 자세하게 말하면, 각각의 자바스크립트 엔진은 특정 버전의 ECMAScript를 구현한다. ECMAScript가 발전하는 만큼, 자바스크립트 엔진도 발전한다. 수많은 자바스크립트 엔진이 존재하는 이유는 각각의 엔진이 서로 다른 웹브라우저, 헤드리스(headless) 브라우저, Node.js와 같은 런타임 등에서 동작하도록 만들어졌기 때문이다.
아마 웹브라우저라는 단어는 익숙하겠지만, 헤드리스 브라우저는 생소할 것이다. 헤드리스 브라우저는 그래픽 유저 인터페이스(GUI)가 없는 웹브라우저이다. 이는 웹 제품에 대한 자동화된 테스트를 구동할 때 유용하다. PhantomJS가 좋은 예이다. 그러면 Node.js는 어디에 속하는 걸까? Node.js는 자바스크립트를 서버사이드에서 사용할 수 있도록 해주는 비동기, 이벤트주도 방식의 프레임워크이다. 이들은 모두 자바스크립트-주도(Javascript-driven)의 도구이기 때문에, 자바스크립트 엔진에 의해 구동된다.
위에서 말한 가상머신의 정의에 따르면, 자바스크립트 엔진을 ‘프로세스 가상 머신’이라고 부르는 게 맞을 것이다. 왜냐하면 자바스크립트 엔진의 유일한 목적은 자바스크립트 코드를 읽고 컴파일 하는 것이기 때문이다. 그렇다고 간단한 엔진이라는 뜻은 아니다. 예를 들면 JavaScriptCore는 자바스크립트 코드를 분석하고, 해석하고, 최적화하고, 가비지 콜렉팅을 하는 여섯개의 ‘빌딩 블록’을 갖고 있다.
어떤 식으로 작동할까?
당연히, 엔진에 따라 다르다. 우리가 관심을 갖고 있는 두개의 중요한 엔진은 Webkit’s의 NativeScript와 Google의 V8 엔진이다. 이들 두 엔진은 코드를 다른 방식으로 처리한다.
JavaScriptCore는 스크립트를 해석하고 최적화하기 위해 다음의 순차적인 단계를 진행한다.
- 어휘 분석(lexical analysis). 소스코드를 일련의 토큰 혹은 식별가능한 문자열로 분해한다.
- 토큰들은 Parser에 의해 분석되어, Syntax Tree로 만들어진다.
- 네 개의 JIT(just in time) 프로세스가, Parser에 의해 만들어진 바이트코드들을 분석하고 실행한다.
간단하게 말하면, 자바스크립트 엔진은 당신의 소스코드를 가져와서, 문자열 단위로 분해하고(어휘단위로 정리), 이들 문자열을 가져다가 컴파일러가 이해할 수 있도록 바이트 코드로 변환한 후, 이를 실행한다.
구글의 V8 엔진은 C++로 작성되었다. 이 엔진 역시 자바스크립트 코드를 컴파일하고 실행하며, 메모리를 할당하고, 가비지 컬렉팅을 한다. 이 엔진은 소스코드를 직접 머신 코드로 컴파일하는 두 개의 컴파일러로 구성되어 있다.
- Full-codegen: 최적화되지 않은 코드를 생성하는 빠른 컴파일러
- Crankshaft: 빠르고 최적화된 코드를 생성하는 느린 컴파일러
Full-codegen이 생성한 코드를 Crankshaft가 검사하여, 최적화가 필요하다고 판단되면 코드를 변경한다. 이를 ‘crankshafting’이라고 한다.
재미있는 사실: crankshaft는 자동차 공업에서 사용되는 내연기관 엔진의 필수적인 부품이다. 고성능의 자동차에 사용되는 이런 타입의 유명한 엔진이 V8이다.
컴파일 과정에서 머신 코드가 생성이 되면, 엔진은 ECMA 표준에 명시된 모든 데이터 타입, 연산자, 객체, 함수들을 추출하여, 브라우저나 런타임(NativeScript와 같은)이 사용할 수 있도록 한다.
이외에는 어떤 자바스크립트 엔진이 있을까?
당신의 클라이어트 코드를 분석하고 실행할 수 있는 자바스크립트 엔진은 어지러울 만큼 많이 있다. 각각의 브라우저 버전이 릴리즈 될때마다 자바스크립트 엔진은 최신 코드를 실행할 수 있는 상태를 유지하기 위해 변경되고 최적화 될 것이다.
이들 엔진에 붙여진 이름들로 의해 혼란을 겪기 전에, 그 밑바탕에는 많은 마케팅 압력이 있다는 사실을 알아두면 좋을 것이다. 자바스크립트 컴파일러에 대한 이 유익한 분석글에서, 저자는 빈정대듯이 이야기한다. “참고로 얘기하자면, 컴파일러는 대략 37% 정도의 마케팅으로 구성되며, 이를 위해 할 수 있는 몇 안되는 방법 중 하나는 브랜딩을 다시 하는 것이다. 그래서 이런 이름들이 줄줄이 생겨난다. SquirrelFish, Nitro, SFX, Nitro Extreme…”
네이밍에 마케팅이 강한 영향을 미친다는 것을 염두에 둔다면, 자바스크립트 엔진의 역사상 중요한 몇가지 이벤트들에 대해 주목하는 것은 유용할 것이다. 내가 간단한 표를 만들어 보았다.
Browser, Headless Browser, or Runtime | JavaScript Engine |
---|---|
Mozilla | Spidermonkey |
Chrome | V8 |
Safari** | JavaScriptCore* |
IE and Edge | Chakra |
PhantomJS | JavaScriptCore |
HTMLUnit | Rhino |
TrifleJS | V8 |
Node.js*** | V8 |
Io.js*** | V8 |
*JavaScriptCore는 SquirrelFish로 재작성되었고, Nitro라고도 불리는 SquirrelFish Extreme으로 다시 브랜딩되었다. 하지만 JavaScriptCore를 WebKit의 구현(Safari와 같은)에 내장된 엔진이라고 부르는 건 여전히 타당하다.
**iOS 개발자들은 Mobile Safari가 Nitro를 기반으로 하지만 UIWebView는 JIT 컴파일을 포함하고 있지 않기 때문에 더 느리다는 것을 꼭 알아야 한다. 하지만 iOS8에서는 개발자들이 Nitro에 접근할 수 있는 WKWebView를 사용할 수 있으며, 이를 통해 상당한 속도향상을 경험할 수 있다. 하이브리드 모바일 앱 개발자들은 약간 더 숨통이 트일 수 있을 것이다.
***io.js가 Nods.js로부터 갈라져 나오기로 결정한 이유중의 하나는 프로젝트에 의해 지원될 V8버전과 관계가 있다. 여기서 정리된 것 처럼, 이 문제는 진행중이다.
왜 관심을 가져야 할까?
자바스크립트 엔진의 코드 해석 및 실행 프로세스의 목표는 가능한한 짧은 시간내에 가장 최적화된 코드를 생성하는 것이다.
요점은, 이들 엔진의 발전이 웹과 모바일 분야를 최대한 고성능으로 발전시키려는 우리의 목표와 부합한다는 것이다. 이 발전을 따라가기 위해, arewefastyet.com의 벤치마킹 그래프 등에서 엔진별 성능이 얼마나 다양한 지를 확인할 수 있다. 예를 들면, V8로 구동될 때와 Crankshafted 없는 엔진으로 구동될 때의 Chrome의 성능차이는 흥미롭다.
어떤 개발자든, 우리가 열심히 노력해서 생성하고, 디버그하고, 관리하는 코드를 표현해주는 브라우저의 내부에 있는 차이에 대해 알고 있어야 한다. 왜 특정한 스크립트는 어떤 브라우저에서는 느리고, 다른 브라우저에서는 더 빠를까?
마찬가지로 모바일 개발자들, 특히 웹뷰를 사용해 컨텐츠를 표시하거나 NativeScript등의 런타임을 사용하는 하이브리드 모바일 앱을 만드는 개발자들은, 그들의 자바스크립트 코드를 어떤 엔진이 해석하고 있는지 알고 싶을 것이다. 모바일 웹 개발자들은 그들의 작은 디바이스에 있는 다양한 브라우저들의 내재된 한계와 가능성에 대해 알아야만 한다. 자바스크립트 엔진의 변화를 놓지지 않고 따라간다면 당신이 웹, 모바일, 앱 개발자로서 발전하는 데에 큰 도움이 될 것이다.