홈으로

[번역] 자바스크립트 시각화: 자바스크립트 엔진

2020년 02월 03일

원저작자인 Lydia Hallie의 허락 하에 🚀⚙️ JavaScript Visualized: the JavaScript Engine 을 번역한 글입니다. 오역이나 의역이 있을 수 있습니다.


자바스크립트는 멋지다(순전히 내 의견이다.), 하지만 기계는 당신이 작성한 코드를 실제로 어떻게 이해할까? 자바스크립트 개발자로서, 우리는 보통 컴파일러 자체에 대해 다룰 필요는 없다. 그러나, 자바스크립트 엔진의 기본 을 알고, 인간 친화적인 자바스크립트 코드를 어떻게 처리하는지와 그것을 기계가 이해하는 어떤 것으로 바꾸는 것을 보는 것은 분명히 좋은 일이다! 🥳

주의: 이 포스트는 Node.js와 크로미움 기반 브라우저에서 사용하는 V8 엔진을 기준으로 설명한다.

HTML 파서는 소스가 있는 script 태그를 마주하게 된다. 소스코드는 네트워크 , 캐시 또는 설치된 서비스 워커 를 통해서 로드된다. 요청에 대한 응답은 바이트 스트림 디코더 가 관여하는 바이트 스트림으로서의 스크립트이다. 바이트 스트림 디코더 는 다운로드 될 때 바이트 스트림을 해독한다.



바이트 스트림 디코더는 해독된 바이트 스트림으로부터 토큰들 을 만들어낸다. 예를 들어, 0066f 로, 0075u 로, 006en 으로, 0063c 로, 0074t 로, 0069i 로, 006fo 로, 006en 으로 해독되고 공백으로 이어진다. 당신은 function 을 작성한 것처럼 보인다! 이것은 자바스크립트에서 예약된 키워드이기 때문에 토큰이 만들어지고 파서에게 보내진다 (그림에서 다루지는 않았지만 나중에 설명할 프리-파서(pre-parser)에게도 보내진다). 동일한 작업이 나머지 바이트 스트림에 대해 진행된다.



엔진은 2개의 파서를 사용한다: 프리-파서파서이다. 웹사이트 로딩 시간을 줄이기 위해서, 엔진은 당장 필요하지 않은 코드는 파싱하지 않으려 한다. 프리-파서는 나중에 사용될 코드를 담당하는 반면에, 파서는 즉각 필요한 코드를 담당한다! 만일 어떤 함수가 사용자가 버튼을 클릭할 때만 실행되는 것이라면, 웹사이트를 로딩하기 위해서 이 코드가 즉각 컴파일 될 필요는 없다. 사용자가 마침내 버튼을 클릭해서 해당 코드를 필요로 한다면, 파서에게 보내진다.


파서는 바이트 스트림 디코더로부터 받은 토큰들을 기반으로 노드를 만든다. 이 노드들로, 추상 구문 트리(Abstract Syntax Tree, AST)를 만든다. 🌳



다음으로, 인터프리터 의 시간이다! 인터프리터는 AST를 쭉 훑어서 AST가 포함하는 정보를 기반으로 바이트 코드 를 생성해낸다. 바이트 코드가 완전히 생성되면, 메모리 공간이 비워지면서 AST가 제거된다. 마침내, 우리는 기계가 사용할 수 있는 것을 가지게 되었다! 🎉



바이트 코드가 빠르긴 하지만, 더 빨라질 수 있다. 바이트 코드가 실행되면서, 정보가 생성된다. 이 정보를 통해서 특정 동작이 자주 발생하는지 여부와, 사용된 데이터의 타입을 감지할 수 있다. 아마도 당신은 많은 함수를 실행시켰을 수 있다: 이제 더 빠르게 하기 위해 최적화할 시간이다! 🏃🏽‍♀️


바이트 코드는, 생성된 타입 정보와 함께 최적화 컴파일러(optimizing compiler) 에게 보내진다. 최적화 컴파일러는 바이트 코드와 타입 정보를 받아서 고도로 최적화된 기계 코드를 생성해낸다. 🚀



자바스크립트는 동적 타이핑 언어이며, 이것은 데이터의 타입이 끊임없이 변화할 수 있다는 것을 뜻한다. 따라서, 자바스크립트 엔진이 특정 값의 데이터 타입을 매번 확인하는 것은 상당히 느릴 것이다.


코드를 해석하는 시간을 줄이기 위해서, 최적화된 기계 코드는 바이트 코드를 실행하면서 엔진이 이전에 보았던 코드만 처리된다. 만약 우리가 계속해서 동일한 데이터 타입을 반환하는 특정 코드를 반복해서 사용한다면, 최적화된 기계 코드는 속도를 빠르게 하기 위해서 재사용될 수 있다. 그러나, 자바스크립트는 동적으로 타이핑 되기 때문에, 동일한 코드 조각이 갑자기 다른 데이터 타입을 반환하는 일이 발생할 수 있다. 만약 그런일이 일어나면, 기계 코드는 최적화-해제(de-optimized) 되고, 엔진은 다시 바이트 코드를 해석하기위해 돌아간다.


어떤 함수가 100번 실행되고 지금까지 항상 동일한 값을 반환한다고 하자. 엔진은 101번째에도 동일한 값을 반환한다는 것을 가정한다.

우리에게 항상 (지금까지) 숫자 값들을 인자로 받아 호출되던 sum 함수가 있다고 해보자:

이 코드는 3 을 반환한다! 다음으로 호출할 때는, 우리가 다시 2개의 숫자 값과 함께 호출한다는 것을 가정한다.

만약 그것이 사실이라면, 동적인 조회는 필요하지 않고, 최적화된 기계 코드를 그대로 재사용할 수 있을 것이다. 그러나, 가정이 잘못되었다면, 최적화된 기계 코드 대신에 다시 바이트 코드로 되돌아갈 것이다.

예를 들어, 다음에 이 코드를 실행할 때, 숫자 대신에 문자열을 넘긴다고 해보자. 자바스크립트는 동적으로 타이핑되기 때문에, 에러 없이 이걸 할 수 있다!

이것은 숫자 2 가 문자열로 형변환 되고, 함수가 문자열 "12" 를 반환한다는 것을 의미한다. 따라서, 다시 해석된 바이트 코드를 실행하기 위해 돌아가고, 타입 정보를 업데이트한다.


이 포스트가 여러분에게 도움이 되었기를 바란다! 😄 물론, 내가 이 포스트에서 다루지 못했지만 나중에 다룰 많은 것들 (자바스크립트의 힙, 콜스택 등)이 있다. 나는 정말로 여러분들이 자바스크립트의 내부에 대해 관심있다면 스스로 찾아보길 권장한다, V8은 오픈소스이고 어떻게 동작하는지에 대한 훌륭한 문서가 있다! 🤖

V8 문서 | V8 깃헙 | 크롬 대학교 2018: 스크립트의 생애

Loading script...