V8 자바스크립트 엔진

Sonny·2024년 11월 18일
36

Article

목록 보기
27/27
post-thumbnail

원문 : https://medium.com/@manikmudholkar831995/the-v8-javascript-engine-d1434ca77c96

By Manik Mudholkar (시니어 소프트웨어 엔지니어)

이 글은 시니어 엔지니어를 위한 고급 Node.js 시리즈의 첫 번째 글입니다. 이 글에서는 V8 엔진이 무엇인지, 왜 그리고 어떻게 동작 하는지 자세히 설명하겠습니다. 시니어 엔지니어를 위한 고급 Node.js 시리즈의 다른 글은 아래에서 확인할 수 있습니다.

시리즈 로드맵

목차

Node.js의 가장 낮은 수준의 다다르면, 모든 것이 복잡하고 다소 혼란스러워집니다. 자바스크립트는 동적 타입의 인터프리터 언어이며 자바스크립트에서 실행되는 모든 것들은 엔진에 의해 처리됩니다. 이 엔진은 주변 환경과 상호작용하면서 컴퓨터가 프로그램을 실행하는 데 필요한 바이트코드를 생성합니다. 이를 담당하는 엔진은 구글에서 개발한 오픈소스 고성능 자바스크립트 및 웹어셈블리 엔진인 V8입니다. V8은 C++로 작성되었으며 크롬 또는 유사한 환경과 Node.js 모두에서 활용됩니다. V8은 ECMAScript와 웹어셈블리를 완벽하게 지원하며, 브라우저에만 국한되지 않고 독립적으로 실행하거나 어떠한 C++ 애플리케이션에도 임베드할 수 있습니다.

왜 V8일까요?

why v8.jpeg

더 낮은 수준으로 갈수록 더 많은 책임이 따릅니다. C/C++을 어셈블리어로 변환하려면 컴파일러가 필요하고 어셈블리어를 기계어로 변환하려면 어셈블러가 필요합니다.
따라서 자바스크립트를 실행 가능한 코드로 변환하려면 자바스크립트 엔진이 필요합니다. 과거에는 파이어폭스에서 스파이더몽키가 사용되었고, 이후 V8이 만들어져 구글 크롬과 Node.js에서 사용되고 있습니다.
스파이더몽키와 V8의 주목할 만한 차이점은 스파이더몽키가 자바스크립트 코드를 중간 언어인 바이트코드(기계어의 추상화)로 변환한 후 기계어로 변환하는 반면, V8은 자바스크립트를 직접 기계어로 변환한다는 점입니다.

Node.js에서는 어떻게 사용되나요?

V8의 소스 코드를 살펴보면 객체, JSON, 날짜 등의 구현은 찾을 수 있지만 크롬에서 사용되는 document 객체나 Node.js에서 사용되는 require() 같은 기능들은 찾을 수 없습니다. 이는 Node.js와 크롬이 이러한 새로운 기능들을 C++로 구현하고, V8을 사용해 자바스크립트 함수로 바인딩하기 때문입니다. 왜 이런 방식을 택했을까요? 앞서 말씀드렸듯이, 더 낮은 수준의 언어일수록 시스템에 대한 더 많은 접근 권한과 제어 능력을 가지기 때문입니다. C/C++은 네트워크 카드와 같은 저수준 리소스에 직접 접근할 수 있고, 자바스크립트는 이러한 C++의 기능을 활용해 원하는 작업을 수행합니다. 따라서 이러한 필수적인 기능들은 V8이 아닌 Node.js 소스 코드에서 찾을 수 있습니다.

이것이 바로 자바스크립트가 이렇게 많은 곳에서 사용되는 이유입니다. 다른 사람들도 자신의 사용 사례에 맞춰 동일한 방식을 적용할 수 있기 때문이죠. 예를 들어, 로봇 공학 분야에서도 자바스크립트가 사용되는데, 이는 오른쪽으로 이동, 왼쪽으로 이동과 같은 C++ 기능을 구현하고 이를 자바스크립트 함수에 바인딩함으로써 가능해졌습니다. 다만, V8은 사용 가능한 메모리가 매우 제한적이기 때문에 이런 특수한 사용 사례에서는 V8이 적합하지 않을 수 있습니다. 이런 경우에는 Duktape 또는 Jerryscript 같은 자바스크립트 엔진이 더 나은 선택이 될 수 있습니다.

결국 V8은 Node.js의 필수적인 의존성으로 자리잡았으며, 이는 Node.js 공식 웹사이트에서도 확인할 수 있습니다.

Node.js is just an application that extends v8.gif

V8은 어떻게 작동하나요?

how v8 work.jpeg

V8 엔진은 코드를 두 단계로 컴파일합니다. 첫 번째 단계에서는 코드를 기계 코드로 빠르게 컴파일하지만, 최적화는 이루어지지 않습니다. 이것만으로도 일단 시작하기에는 충분합니다. 이 작업이 백그라운드에서 진행되는 동안 또 다른 컴파일이 수행되는데, 이 단계의 컴파일은 느리지만 훨씬 최적화된 코드를 제공합니다.

느린 컴파일 작업이 완료되면 자바스크립트는 최적화된 컴파일 코드로 전환됩니다. 이 두 단계는 이그니션(Ignition, 빠른 저수준 레지스터 기반 인터프리터)과 터보팬(Turbofan, 최적화 컴파일러)으로 알려져 있습니다. 여기에 새로운 접근 방식인 JIT(Just-in-Time) 컴파일레이션이 만들어졌는데, 이는 인터프리테이션과 컴파일레이션의 장점을 결합한 방식입니다.

V8은 이그니션이라는 인터프리터를 사용합니다. 처음에는 추상 구문 트리(AST)를 가져와서 바이트 코드를 생성합니다. 하지만 이그니션만으로는 한계가 있습니다. 만약 어떤 함수가 자주 사용된다면, 이는 터보팬이라는 컴파일러에서 최적화되어 더 빠르게 실행됩니다.

단계별로 살펴보겠습니다.

  1. V8은 자바스크립트 코드를 직접 이해하지 못하므로 소스 코드를 파싱하여 추상 구문 트리로 변환하고 V8이 더 쉽게 처리할 수 있는 형태로 만듭니다. 이 과정에서 스코프도 함께 생성됩니다.
  2. 해당 AST와 스코프를 기반으로 이그니션 인터프리터는 바이트코드를 생성할 수 있습니다.
  3. 이 시점에서 엔진은 코드를 실행하고 타입 피드백을 수집하기 시작합니다. (실행 단계에서는 코드에 대한 타입 피드백을 제공합니다.)
  4. 실행 속도를 높이기 위해, 바이트 코드가 피드백 데이터와 함께 최적화 컴파일러로 전달될 수 있습니다. 최적화 컴파일러는 이를 기반으로 특수한 경우를 가정하고 고도로 최적화된 기계어를 생성합니다. 이 과정은 병렬로 처리되며 V8은 자주 사용되는 바이트코드를 "핫" 코드로 표시하여 더 효율적인 기계어로 변환합니다. 하지만 바이트코드가 아닌 기계어를 직접 사용하지 않는 이유는 다음과 같습니다.
  5. 기계어에는 많은 양의 메모리가 필요합니다.
  6. 기계어가 항상 바이트코드보다 빠른 것은 아닙니다. 기계어는 실행 속도는 매우 빠르지만 컴파일하는 데 더 오랜 시간이 걸립니다. 반면 바이트코드는 컴파일 시간은 짧지만, 실행 단계가 더 느리다는 단점이 있습니다.
  7. 만약 어느 시점에서 최적화 컴파일러의 가정 중 하나가 잘못된 것으로 판명되면, 최적화를 취소하고 다시 인터프리터로 돌아갑니다.

나만의 자바스크립트 런타임 만들기

나만의 자바스크립트 런타임을 만드는 데 관심이 있거나 그것이 가능한지 궁금하다면, 제가 만든 최신 프로젝트를 확인해보세요! 저는 강력한 V8 자바스크립트 엔진과 Libuv를 사용하여 저만의 자바스크립트 런타임을 만들었습니다. V8, Libuv, 그리고 C++를 기반으로 하여, Node.js를 처음부터 다시 만들어보는 것을 목표로 했습니다. 자바스크립트 런타임 개발의 세계로 깊이 빠져드는 이 흥미진진한 여정에 함께하세요.

profile
FrontEnd Developer

3개의 댓글

comment-user-thumbnail
2024년 11월 21일

감사히 잘 읽었습니다!

답글 달기
comment-user-thumbnail
2024년 11월 21일

이 내용을 포스팅하시다니 딱 시기적절하네요🥰👋👍👍👍

답글 달기
comment-user-thumbnail
2024년 11월 29일

잘 읽었습니다!

답글 달기