V8 자바스크립트 엔진의 동작 방식(Memory Heap & Call Stack)

Jiwon Joung·2023년 3월 11일
0

Web

목록 보기
2/2

V8 자바스크립트 엔진의 동작 방식(Memory Heap & Call Stack)

자바스크립트를 사용해서 웹개발을 하는 유저라면 한번쯤 내 자바스크립트 코드가 어떤 과정을 거쳐 웹에 렌더링되는지 궁금증을 느낀 적이 있을 것이다. (나)

사실 자바스크립트는 프로그래밍 언어중에서도 굉장히 high-level 한 언어에 속해서, 굳이 컴파일러나 엔진의 동작 방식을 몰라도 사용하는데 전혀 문제가 없고, 코드의 효율성에도 크게 영향을 미치지는 않는다. 하지만 알아둬서 나쁠건 없겠지

웹 브라우저에서 자바스크립트 코드가 어떻게 실행되는지, 자바스크립트 엔진 중 가장 유명한 V8 엔진의 동작 방식을 살펴보자

V8 엔진은 무엇인가

V8 엔진은 구글이 오픈소스로 관리하는 자바스크립트와WebAssembly 엔진으로, C++로 작성되었다. 크롬과 Node.js 를 포함한 여러 곳(오페라, 마이크로소프트 엣지 등..) 에서 사용되고 있으며, ECMAScript 문법 표준을 따른다.

참고로 파이어폭스는 V8 대신 SpiderMonkey라는 엔진을 사용한다.

https://github.com/v8/v8

코드는 엄청난 규모로 깃허브에서 관리되고 있으니 궁금하면 참고하도록 .. 하지만 c++로 작성되어 읽기 쉽지않을것같다.. 궁금한 사람은 열어보도록

JIT 컴파일러

우선 V8에 대해 알아보기전에 .. V8엔진은 JIT 컴파일러라고 하니 JIT 컴파일러가 무엇인지부터 알아보자

JIT 컴파일(Just In Time Compile) 은 프로그램을 실제 실행하는 시점에 기계어로 번역하는 컴파일 기법이다.

잘 이해가 안되니 일반적으로 C를 컴파일하는 과정과 비교해보면, C 프로그램은 실행 전에 컴파일을 하여 object 파일로 만든뒤(= 기계어로 변환한 뒤) 이를 실행파일로 만들어서 실행한다. 이과정에서 문법적인 오류가 있다거나 Segmentation Fault같이 코드를 잘못짜면 컴파일에러가 나면서 기계어로 변환 자체가 안된다. 이를 Ahead-of-time 컴파일러라고 한다.

반면, JIT 컴파일은 우선 프로그램을 시작한뒤, 그때그때 시간에 맞추어(런타임 동안에) 필요한 코드들을 컴파일한다는 것이다. 그리고, 이 컴파일 과정은 프로그램을 시작할때마다 반복된다.

사실 실행 성능 자체를 따지면 컴파일러가 성능은 최고라고 한다. 당연한 말인게, 이미 컴파일이 완료되어서 런타임중에 따로 해석할게 없으니.. 하지만 웹애플리케이션의 경우에는 다르다.

JIT는 정적 컴파일러만큼 실행이 빠르면서도 인터프리터 언어처럼 응답속도가 빠르도록 고안되어서 먼저 Parsing 만 해놓고 그때그때 인터프리터로 바이트코드로 바꾸는 방식을 사용한다.. 라고 하는데 … 사실 컴파일러에 대해 깊은 내용을 다루려는 포스팅이 아니었고 어려운 내용이니 컴파일러에 대한 내용은 이정도로만,,,

출처: DigitalOcean

아무튼 전반적인 절차는 위의 사진과 같다.

이것에 대한 내용은 .. 아래 블로그들에 잘 정리되어있으니 궁금한 사람은 참고하도록

[2020.10.16] Google Chrome V8 엔진을 파헤쳐보자

JIT Compiler & Chrome V8 Engine


V8 엔진의 내부 동작

자 그래서 V8 엔진의 내부 구조는 어떻게 생겼느냐, 어떻게 Stack 만으로 내 비동기작업을 처리하는가에 대한 궁금증을 풀어보자

데이터 저장

자바스크립트 엔진 구조

우선 V8 엔진은 위와 같이 Memory Heap 과 Call Stack 으로 구성되어있다.

메모리힙(Memory Heap)

메모리 할당이 일어나는 곳으로, 정확한 주소를 정해놓은 것이 아니라 광범위한 영역을 잡아놓고 사용한다.

콜 스택(Call Stack)

코드 실행에 따라 함수 호출 스택이 쌓이는 곳이다. 자바스크립트가 싱글 스레드 언어라고 말하는 것은, 이 Call Stack을 하나만 사용한다라는 의미와 동일하다.

하나의 콜 스택은 프로그램 상에서 코드가 어디에 위치하고 있는지 기록하는 카운터(Program Counter)를 각 하나씩 가지는데, Call Stack 이 하나면 당연히 하나의 흐름으로만 코드가 동작할 것이다.

위의 메모리힙과 콜스택이 기본적으로 V8 엔진의 구조인데, 자바스크립트는 사실 이 엔진으로만 구동되지않는다. 예를들어, 우리는 모두 setTimeout 이나 Ajax(XMLHttpRequest) 같은 API들을 자연스레 사용하는데 이는 자바스크립트 엔진이 제공하는 문법이 아니다. 이 함수들은 브라우저의 Web API로서 제공되는데, 이렇게 자바스크립트가 실행되게 하는 환경을 통틀어서 자바스크립트 런타임이라고 한다.

자바스크립트 런타임

자바스크립트 런타임에는 자바스크립트 엔진의 데이터 구조인 메모리힙과 콜스택 외에도 Web API들, 이벤트 루프(Event Loop), 콜백 큐(Callback Queue)가 포함된다. 그래서 구조는 아래 그림처럼 확장된다.

콜스택에 실행해야할 함수가 있다고 해보자. 그러면 브라우저는 이 함수의 실행이 끝날때까지 아무것도 못하고, 컴포넌트를 렌더링하지도, 다른 코드를 실행하지도 못한다.

생각해보면 .. 자바스크립트의 콜스택은 하나이고, 한번에 한가지밖에 처리하지 못하는데 우리는 자바스크립트에서 비동기적으로 작업을 처리한다. 그래서 마치 싱글스레드임에도 멀티스레드처럼 보이는데, 이 작업을 해주는것이 이벤트 루프이다.

사실 이벤트루프에 대한 내용은 생각보다 찾아보기 어렵다. 자바스크립트의 바이블이라고 하는 모던 자바스크립트 Deep Dive 책을 봐도 이벤트 루프에 대한 내용은 아예 찾아볼 수 없으며 ECMAScript 명세서에 들어가도 아예 없다. 아마 이것이 자바스크립트 자체의 개념이 아니라서 그렇겠지 싶다. 실제로 Node.js도 비동기 IO를 지원하기 위해 libuv 라는 서드파티 라이브러리를 사용한다고 한다.

**Philip Roberts: Help, I’m stuck in an event-loop 나는 이 동영상을 많이 참고해서 내용을 익혔다. 이벤트루프 뿐만 아니라 자바스크립트의 전반적인 동작방식을 잘 설명해주는 것같아서 한번 보는것 추천 !

일단 이벤트 루프에 대해 알아보기 전에 WebAPI가 무엇인지부터 알아야한다.

WebAPIs

WebAPI는 쉽게 말하면 브라우저에서 제공해주는 함수들이다. 자바스크립트 자체 API는 생각보다 많지않고 우리가 일반적으로 사용하는 setTimeout, ajax 기반 함수들(JSON.parse, XMLHttpRequest 등..), 이벤트 리스너(onClick, onHover ..) 등등이 모두 WebAPI에 속한다. 이렇게 WebAPI 에 속하는 함수들은 실행됐을때 콜스택에 남아있지 않고 WebAPI가 처리하게 된다. 엄밀히 말하면 백그라운드에서 돌아가는 다른 스레드한테 작업을 일임하는 것

아래처럼 만약 setTimeout 함수가 있는 코드가 있다고 하면 자바스크립트 엔진은 이 함수와 이 함수에 대한 콜백까지 WebAPI에게 전달하고, setTimeout 은 콜스택에서 일단 지워진다. 그리고 setTimeout 은 WebAPI에서 실행된다.

  1. Call Stack → WebAPIs

태스크 큐(Task Queue)

WebAPI가 작업 처리를 다 하게되면 callback 함수를 task queue에 쌓아놓는다. 이 태스크 큐는 이름에서 알 수 있듯이 FIFO(First In First Out) 형태의 배열이다.

  1. WebAPIs → Task Queue

이벤트 루프(Event Loop)

그리고 비동기처리의 마지막 작업으로 이벤트 루프가 동작하는데, 이 작업은 생각보다 단순하다. 자바스크립트의 콜스택을 주시하고 있다가 스택에 더이상 처리할게 없으면 태스크 큐에 있는 콜백함수를 스택으로 넘겨서 자바스크립트가 처리하게 한다.

  1. Task Queue → (Event Loop) → Call Stack
while(queue.waitForMessage()){
  queue.processNextMessage();
}

MDN의 이벤트 루프 설명을 보면 왜 ‘루프’ 라는 이름이 붙었는지를 간단한 수도 코드로 설명한다. 내부 동작도 말그대로 loop를 돌며 queue에 메시지가 오기를 기다리다가, 메시지가 도착하면 핸들링하는 프로세스로 진행된다.

그래서 총 동작 시퀀스를 보면 아래와 같다.

자바스크립트 런타임에서의 동작 총정리

그렇게 해서 최종적으로 아래와 같은 코드가 있을때

console.log('Hi');

setTimeout(function cb(){
	console.log("I'm");
}, 5000);

console.log('Jiwon');

이렇게 HiJiwonI'm 의 순서대로 출력될 수 있는 것이다.

(Bonus Quiz) Zero Waiting

보너스 퀴즈로 한번 다음과 같이 0초를 기다리는 상황에서는 어떻게 출력될지 예측해보자 !!

console.log('Hi');

setTimeout(function cb(){
	console.log("I'm");
}, 0);

console.log('Jiwon');

마치며 ..

그동안 자바스크립트가 어떻게 동작하는지 신경도 안쓰면서 개발했는데 신기하기도 하고 재밌었다. 위에서 언급했던 JIT 컴파일러에 대한 내용은 생각보다 더 어려워서 깊게 파지는 못했는데, V8 시리즈로 한번 V8의 컴파일 과정과 코드 최적화 방법까지 한번 공부해서 데려와보겠습니다 …


참고자료

V8 JavaScript engine
When is JIT Faster Than A Compiler?
자바스크립트는 어떻게 동작하나요?
https://vimeo.com/96425312

0개의 댓글