[Javascript Deep Dive] javascript History - V8 Engine, Node.js

강민혁·2022년 11월 1일
1

Javascript Deep Dive

목록 보기
5/6

V8 javascript Engine

V8은 웹 브라우저를 만드는 데 기반을 제공하는 오픈 소스 자바스크립트 엔진이다. 현재 구글 크롬 브라우저와 안드로이드 브라우저에 탑재되어 있다. C++로 작성되었고, 독립적으로 실행이 가능하며 C++로 작성된 application에 넣어서 작동시킬 수도 있다.

간단히 말하면, V8은 자바스크립트bytecode로 컴파일하고 최적화하여 실행하는 엔진이다. Bytecode로 컴파일하고 최적화하는 과정이 없다면, 매번 javascript 코드 전체를 Compile 해야 하고, 최적화가 없으니, 화면 전환과 새로고침이 잦은 웹 브라우저에서 이는 비효율적인 실행 방식을 갖게 된다. 하지만 V8 엔진을 이용하면, 이 문제를 해결할 수 있다.

V8 실행 환경

V8은 IA-32, ARM, MIPS 프로세서를 사용하는 Window 7 이상, macOS 10.12이상, Linux x64환경에서 실행이 가능하다. V8은 Chrome 뿐 아니라, 독립적으로도 실행이 가능하고, 그 예시로 V8로 빌드된 Node.js가 있다.

javascript -> bytecode 컴파일 과정


위 그림은 JSConf EU 2017에서 발표한 Franziska Hinkelmann님의 자료이다.

V8은 javascript 소스 코드parser를 통해 AST(Abstract Syntax Tree, 추상 구문 트리)로 변환한다. 이때 생성하는 Object를 메모리에 할당하고, garbage collection을 이용해, 사용되지 않는 Object의 메모리를 해제한다. 이후에는 이 ASTInterpreter Ignition로 넘겨, Bytecode로 변환한다.

그리고 Compile 속도를 높이기 위해, 이 Bytecode를 캐싱해두고, 자주 쓰이는 코드를 inline caching이라는 최적화 기법 등을 이용해 Complie시 참조하여 Compile 속도를 높인다. 일반적으로 브라우저의 javascript compile은 Interpreter로 처리하는데, Bytecode 캐싱을 통해 Interpreter의 실행속도를 개선할 수 있다.

위와 같은 최적화는 Bytecode를 실행하면서, Profiling을 통해 최적화 해야 하는 데이터를 수집하고, 이 데이터를 기반으로 TurboFan을 통해 자주 사용되는 함수나 데이터를 기반으로 최적화를 진행하여 Optimized Machine Code를 생성한다. 이 Optimized Machine Code를 실행하며, 메모리 사용량을 줄이고, 기계어에 최적화되어, 성능을 향상시킨다.

TurboFan은 자주 사용되는 Native code와 중복된 코드를 재사용하여 코드 크기를 줄이고, 메모리 오버 헤드를 크게 줄인다.

Binarycode

binarycode는 컴퓨터가 인식하고 이해할 수 있는 이진 숫자 체계의 0과 1로 구성된 코드이다.

Bytecode

Bytecode는 Virtual Machine이 이해할 수 있는 코드이다. CPU가 아닌, VM에서 이해할 수 있는 이진 코드이다. 플랫폼에 종속되지 않고 실행될 수 있는 VM 전용 기계어 코드이다.

Bytecode로 변환의 이점

javascript를 low level에 가까운 Bytecode로 변환하면, 원본 소스 코드를 컴퓨터가 해석하기 쉽다. 그리고 원본 코드를 다시 Parsing 해야하는 수고를 덜고 코드의 양도 줄여, 코드 실행을 위한 메모리 공간도 아낄 수 있다. V8에서는 TurboFan을 이용해서 Compile된 bytecode를 최적화하여, 최적화된 bytecode를 만든다.

AST(Abstract Syntax Tree, 추상 구문 트리)

소스코드를 트리로 만든 구조체, 컴파일러에서 사용되는 자료구조이다. AST는 일반적으로 컴파일러의 구문 분석 단계의 결과물이다. 컴파일러가 요구하는 여러 단계를 통해 프로그램의 중간 표현의 역할을 한다.

Overhead

Overhead는 어떤 처리를 위해 들어가는 '간접적인' 처리 시간이나 메모리를 의미한다. 즉, 컴퓨터가 user 프로그램을 실행할 때, 직접 user 프로그램 처리를 하지 않는 부분을 말하며, OS가 시스템을 관리하는데 필요로 하는 CPU 타임이나 메모리용량을 말한다.

V8 Engine github

Node.js와 V8

2009년 Ryan Dahl이 발표한 Node.js는 V8 엔진으로 빌드된 javascript runtime environment(자바스크립트 런타임 환경)이다. Node.js는 브라우저의 자바스크립트 엔진에서만 동작하던 자바스크립트를 브라우저 이외의 환경에서도 동작할 수 있도록 만든 실행 환경이다. Node.js는 주로 server side application 개발에 사용되며, 이에 필요한 모듈, 파일 시스템, HTTP등의 built-in API를 제공한다.

Node.js가 자바스크립트 엔진을 기반으로 하기 때문에, Node.js 환경에서 동작하는 application은 자바스크립트를 사용해서 개발한다. 여기서 front-end와 back-end 모두에서 javascript를 사용할 수 있다는 동형성(isomorphic)은 별도의 언어를 학습하기 위한 러닝 커브를 줄여줄 수 있다. Node.js 덕분에 자바스크립트는 브라우저라는 틀에서 벗어나 server side application 개발도 수행가능한 범용 프로그래밍 언어가 되었다.

Node.js는 비동기 I/O를 지원하며, single thread event loop 기반으로 동작한다. 그래서 요청 처리 성능이 좋다. Node.js는 single thread non-blocking 모델로 구성되어 있다. 하나의 Thread로 동작하지만, 비동기 I/O 작업을 통해 요청들을 서로 blocking하지 않는다. 즉, single thread이지만, 동시에 많은 요청들을 비동기로 수행한다.

Node.js는 Event-driven 모델을 사용한다. 즉, event listener에 등록해둔 callback 함수를 실행하는 방식으로 동작한다. 그리고 이 event에 따라 호출 되는 callback 함수를 관리하는 것이 event loop이다.


Node.js는 크게 내장 라이브러리(Node.js Core Library, Node.js Bindings), libuv와 V8 엔진으로 구성되어있다. Node.js의 특성인 event-driven, non-blocking I/O 모델들은 모두 libuv 라이브러리에서 구현된다.

Process (프로세스)

Process는 메모리에 올라와 실행되고 있는 프로그램의 인스턴스이다. 이는 독립적인 개체로 각각의 프로세스는 별도의 메모리 공간을 갖는다.

Thread (쓰레드)

Thread는 프로세스 내에서 할당받은 실행의 단위이다. Thread는 Process 당 CPU의 코어 개수만큼 생성될 수 있다. 각각의 Thread는 Process 내의 메모리 공간을 공유한다.

Asynchronous I/O (비동기 I/O)

기본적으로 입출력 동기화에는 동기 I/O와 비동기 I/O가 있다. 동기 I/O 처리에서는 thread가 사용되면, I/O 요청이 완료될 때까지 대기한다. 반면 비동기 I/O에서는 thread는 kernel의 도움을 받아 I/O 작업을 처리한다. 즉, kernel을 통해 작업의 완료 사항을 파악하고, thread는 I/O 작업을 필요에 따라 중지하고 다른 작업을 하기도 한다. 그래서 매우 많은 시간이 걸리는 작업의 I/O는 비동기 I/O를 사용해서 최적화할 수 있다.

Event Loop

브라우저 런타임에서의 event loop는 event 발생시 호출되는 callback 함수들을 관리하여 task queue에 전달하고, task queue에 담겨있는 callback 함수들을 callstack에 넘겨준다.
반면, Node.js에서의 event loop는 libuv 내에서 구현된다. event loop는 여러 개의 Phase들을 갖고 있고, 해당 Phase들은 각자 독립적인 queue를 갖는다. 그리고 event loop에 의해 각 Phase들은 Round-Robin 방식으로 노드 프로세스가 종료될때까지 여러 Phase들을 순회한다. 각 Phase들은 각각의 Queue를 관리하고, 해당 Queue들은 FIFO 순으로 callback 함수들을 처리한다.

Task Queue

web api에서 비동기 작업들이 실행된 후 호출되는 callback 함수들이 기다리는 공간이다. event loop가 정해준 순서대로 줄을 서있고, queue이기 때문에, FIFO(First In First Out) 방식을 따른다.

Round-Robin

그룹 내에 있는 모든 요소를 합리적인 순서에 입각하여 뽑는 방식, 컴퓨터 운영에서 컴퓨터 자원을 사용할 수 있는 기회를 프로그램 프로세스들에게 공정하게 부여하기 위한 방법이다. 각 프로세스에 일정시간을 할당하고, 할당된 시간이 지나면 그 프로세스는 잠시 보류한 뒤 다른 프로세스에게 기회를 주며 돌아가며 기회를 부여하는 방식이다. 흔히 라운드 로빈 프로세스 스케줄링이라 부른다.

Non-Blocking I/O

Node.js에서의 Non-Bloking I/O 모델은 Input과 Output이 관련된 작업(ex. http, database CRUD, third party api, filesystem) 등의 블로킹 작업들을 백그라운드(OS kernel이나 libuv의 thread pool)에서 수행하고, 이를 비동기 callback 함수로 event loop에 전달하는 것을 의미한다.

Reference

[javascript deep dive] 도서
https://helloinyong.tistory.com/290

https://ko.wikipedia.org/wiki/V8_(%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8_%EC%97%94%EC%A7%84)

https://evan-moon.github.io/2019/06/28/v8-analysis/

https://usefultoknow.tistory.com/entry/%EB%B0%94%EC%9D%B4%EB%84%88%EB%A6%AC%EC%99%80-%EB%B0%94%EC%9D%B4%ED%8A%B8-%EC%BD%94%EB%93%9C%EB%9E%80-%EA%B8%B0%EA%B3%84%EC%96%B4%EB%9E%80

https://pks2974.medium.com/v8-%EC%97%90%EC%84%9C-javascript-%EC%BD%94%EB%93%9C%EB%A5%BC-%EC%8B%A4%ED%96%89%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95-%EC%A0%95%EB%A6%AC%ED%95%B4%EB%B3%B4%EA%B8%B0-25837f61f551

https://ko.wikipedia.org/wiki/%EC%B6%94%EC%83%81_%EA%B5%AC%EB%AC%B8_%ED%8A%B8%EB%A6%AC

https://medium.com/@vdongbin/node-js-%EB%8F%99%EC%9E%91%EC%9B%90%EB%A6%AC-single-thread-event-driven-non-blocking-i-o-event-loop-ce97e58a8e21

https://medium.com/@vdongbin/javascript-%EC%9E%91%EB%8F%99%EC%9B%90%EB%A6%AC-single-thread-event-loop-asynchronous-e47e07b24d1c

profile
with programming

1개의 댓글

comment-user-thumbnail
2022년 11월 4일

보기 좋게 잘 설명되어 있어요!

답글 달기