본문 바로가기
  • soldonii's devlog
Javascript 공부/Advanced Javascript(-)

자바스크립트의 작동 원리는?(크롬 V8 엔진)

by soldonii 2019. 8. 27.

*Udemy의 "Advanced Javascript Concepts" 강의에서 학습한 내용을 정리한 포스팅입니다.

*https://soldonii.github.io에서 2019년 8월 15일(목)에 작성한 글을 티스토리로 옮겨온 포스팅입니다.

*자바스크립트를 배우는 단계라 오류가 있을 수 있습니다. 틀린 내용은 댓글로 말씀해주시면 수정하겠습니다. 감사합니다. :)


1. 자바스크립트 엔진

자바스크립트 파일을 작성해서 컴퓨터에게 전달할 때, 컴퓨터는 도대체 어떻게 우리가 작성한 자바스크립트 코드를 해석해서 의도한 결과물을 출력해줄까? 프로그래밍을 공부하는 사람이라면 누구나 한 번쯤은 궁금증을 가질만한 주제이다. 이 내용에 대해서 공부해보자.

 

사실 컴퓨터는 0과 1로 두개의 값만 이해할 수 있다. 그런데 우리가 작성하는 자바스크립트 코드는 0과 1로 이루어져 있기는 커녕, 꽤나 인간이 읽기 쉽게 이루어져 있다. 이처럼 인간이 읽고, 쓰고, 해석할 수 있도록 작성된 언어를 고차원 언어라고 하며, 인간에게서 멀어지고 기계에 가까워질수록, 저차원 언어가 된다.

JS file, JS Engine, Computer

 

이 때 인간이 자바스크립트 언어로 작성한 파일을 컴퓨터가 읽을 수 있도록 도와주는 역할을 하는 프로그램이 있는데, 그것이 바로 자바스크립트 엔진이다. 따라서 자바스크립트를 컴퓨터가 어떻게 해석해서 원하는 명령을 수행하는지를 깊게 이해하기 위해서는 자바스크립트 엔진에 대해서 깊게 파서 공부할 필요가 있다.

본 글에서는 가장 성능이 좋고, 가장 많이 쓰이는 대표적인 자바스크립트 엔진, 구글 크롬의 V8엔진을 기반으로 자바스크립트 엔진에 대해서 공부해보고자 한다. 참고로 크롬 브라우저에 내장된 V8 엔진 말고도 자바스크립트 엔진은 굉장히 많다.(ex. 파이어폭스의 Spider Monkey 등)

 

# 자바스크립트 엔진의 구조

JS 엔진

 

자바스크립트 엔진(이하 자바스크립트 엔진은 V8 엔진을 의미)은 위 사진처럼 구성되어 있다.

  • Parser : Lexical Analysis(코드의 의미를 이해하기 위해 token이라는 작은 단위들로 코드를 쪼개는 일)를 진행한다.
  • AST : Abstract Syntax Tree의 약자로, parser에서 분해된 token들을 기반으로 나무 구조를 만든다.(ex. DOM Tree, CSSOM Tree가 구성되는 것과 비슷한 개념이다.)
    •  AST Explorer : 코드가 어떻게 나무 구조로 변경되는지를 시각적으로 볼 수 있는 사이트.
  • AST에서 생성된 나무 구조는 인터프리터와 컴파일러를 거쳐서 컴퓨터가 이해할 수 있는 Bytecode Optimized Code로 변환되는데, 아래에서 훨씬 더 자세하게 다뤄본다.

자바스크립트 엔진의 구조와 구성 원리를 이해하면, 반대로 코드를 작성할 때 어떻게 Optimized Code, 효율적으로 최적화된 코드를 작성할 수 있는지를 알 수 있다.

 

2. 인터프리터와 컴파일러

고차원 언어를 기계가 이해할 수 있는 저차원 언어로 변환하는 방식은 크게 2가지 방식이 있다. ① 인터프리터를 이용한 변환과 ② 컴파일러를 이용한 변환이다.

 

 인터프리터 : 코드를 한 줄 한 줄 읽어내려가며 한 줄씩 Bytecode로 변환한다.

 컴파일러 : 한 줄 한 줄 번역하지 않고 파일 전체를 읽은 뒤, 코드의 의미를 해석하고 파일 전체를 기계어로 컴파일해서 변환한다.

 

저차원 언어, bytecode, 고차원 언어

 

위 사진을 통해 예를 들어보자면, 인터프리터의 경우 자바스크립트 파일을 입력받으면 한 줄 한 줄 해석하면서 중간 단계의 Bytecode로 변환한다. 반면 컴파일러는 자바스크립트 코드를 입력받으면 파일 전체를 읽은 뒤, 이를 컴파일하여 기계어로 변환한다. 그리고 이 기계어(Machine Code)는 CPU로 입력되어 코드가 실행된다.

 

인터프리터와 컴파일러, 둘 중 어떤 것이 더 나은 방식일까? 항상 강조하지만, 절대적으로 더 나은 것은 없다. 각각의 장단점을 알고, 언제 어떻게 사용하는 것이 효율적인지를 아는게 중요하다.

 

# 인터프리터 vs. 컴파일러

1. 인터프리터

  • 장점 : 코드 전체가 컴파일 된 Compilation이 완성되는 것을 기다릴 필요 없이, 한 줄 한 줄 변환하기 때문에 실행 속도가 빠르다. 자바스크립트는 웹을 위해 개발된 언어이고, 유저에게 최상의 경험을 제공해야 하기 때문에 빠르게 실행되는 것이 중요한 자바스크립트에 이상적인 코드 변환 방식이다.
  • 단점 : 자바스크립트 코드가 복잡해질수록 점점 속도가 느려진다. 예를 들어, 같은 코드를 여러차례 반복하는 반복문의 경우, 같은 결과를 반복하는 것임에도 불구하고 코드를 한 줄 한 줄 읽는 방식 때문에 10억번을 반복해야 한다면 말 그대로 10억번을 반복해서 코드 변환을 진행하게 된다.

2. 컴파일러

  • 장점 : 컴파일러는 작업을 단순화시킨다. 예를 들어 특정 함수를 10억번 반복해야 할 경우, 컴파일 과정에서 함수를 반복하는 것이 아니라 함수의 결과물을 반복하도록 컴파일 한다. 이처럼 불필요한 동작을 제거하는 컴파일러의 방식을 최적화, optimization이라고 한다. (인터프리터는 optimize하지 않는다.)
  • 단점 : 코드를 바로 실행하지 않고, 코드 실행 전 전체를 컴파일 하는 과정이 필요하기 때문에 초기에 속도가 느릴 수 밖에 없다.

자바스크립트 엔진의 내부를 공부하다가 잠시 인터프리터와 컴파일러의 간단한 개념과 장단점을 짚어봤다. 그러면 과연 V8 엔진은 어떤 방식을 취했길래 다른 자바스크립트 엔진보다 더 빠르고 효율적으로 자바스크립트 명령을 수행할 수 있을까? 바로 여기에서 V8 엔진의 독특한 언어 변환 도구인 JIT Compiler가 들어온다. JIT은 Just In Time의 약자이다.

 

# JIT Compiler의 구동 방식

JS Engine

 

  1. AST를 통해 나무 구조로 변환된 코드는 최초에 인터프리터에게 전달된다.(참고로 V8엔진에서 인터프리터의 명칭은 Ignition이다.) 인터프리터는 빠르게 코드를 Bytecode로 변환한다.
  2. 인터프리터가 코드를 실시간으로 변환하면서 브라우저에게 특정 작업을 지시하는 동안, 프로파일러(Profiler)는 입력받은 코드에서 최적화할 수 있는 부분을 찾아서 기록해놓는다.
  3. 최적화가 가능한 부분을 찾으면 프로파일러는 이를 컴파일러에게 전달하고, 컴파일러는 인터프리터에 의해 실시간으로 웹사이트가 구동되는 동안 필요한 부분을 기계어로 변환하여 최적화를 진행한다.
  4. 최적화한 코드를 수행할 차례가 다가오면, Bytecode 대신 컴파일러가 변환한 최적화된 코드가 그 자리를 대체하여 실행된다.

이 방식을 통해 코드가 읽으면 읽을수록 구동 속도는 빨라지게 된다. 인터프리터가 최적화를 진행하는 코드가 더 많아지기 때문이다.

한편, 컴파일러는 100% 완벽하지 못하기 때문에 의도와 다르게 발적화(deoptimization)가 일어날 수도 있다. 따라서 현재까지 이해한 자바스크립트 엔진의 구동 원리를 기반으로, 빠르고 효율적인, 최적화된 코드를 작성할 수 있는 방법을 다음 글에서 알아보자.

댓글