[JavaScript] 자바스크립트의 작동 원리와 V8 엔진

배고픈메꾸리·2020년 12월 21일
4

Advanced JavaScript

목록 보기
1/10
post-custom-banner


컴퓨터는 0과 1밖에 이해하지 못한다는 것은 누구나 아는 사실이다. 하지만 컴퓨터는 High-level language인 자바스크립트 파일을 이해하고 명령을 수행할 수 있다. 그렇다면 어떻게 자바스크립트 파일을 컴퓨터가 해석하는지 궁금할 것이다. (궁금해야 한다.)

인간이 작성한 자바스크립트 파일을 컴퓨터가 읽을 수 있는 것은 바로 자바스크립트 엔진이 있기에 가능하다. 자바스크립트 엔진은 일련의 과정을 거쳐서 컴퓨터에게 최적화된 코드로 변환하여 전달하게 되고, 이러한 과정으로 인해서 컴퓨터가 자바스크립트 파일을 이해하고 수행할 수 있는 것이다. 이번 글에서는 자바스크립트 엔진에 대해서, 그 중에서도 가장 많이 쓰이는 V8 엔진에 대해서 알아보자.


컴파일러와 인터프리터

V8엔진이 어떻게 돌아가는지 알아보기 전에 프로그래밍에서 일반적으로 사용하는 두 가지 번역 방식에 대해서 알아보자.

// 인터프리터 vs 컴파일러
function someCalculation(x, y){
  return x+y;
}

for (let i = 0 ; i <1000 ;i++ {
  somecalculation(5,4);
}
     

인터프리터

인터프리터는 코드를 한 줄 한 줄 순차적으로 변환하는 방식이다. 코드를 실행하기 전 컴파일 단계가 없기 때문에 실행 속도가 빠르다는 장점이 있다

하지만 같은 코드를 두 번 이상 실행할 경우 문제가 발생한다. 위의 코드를 인터프리터 방식으로 실행할 경우 somecalculation(5,4) 가 같은 결과를 뱉어내는데도 불구하고 반복적으로 함수 호출과 반환이 수행되고, 결과적으로 속도에서 손해를 보게 된다. (최적화를 하지 않기 때문)

컴파일러

컴파일러는 시작하기 전에 컴파일이라는 과정을 거치기 때문에 시작하는데 시간이 조금 더 걸리지만 코드를 실행할때 마다 번역을 할 필요가 없어진다. 이로 인해서 얻을 수 있는 장점이 있는데, 위의 예제처럼 somecalculation(5,4)9로 치환하여 반복하도록 단순화 시켜줌으로써 불필요한 작업들을 거치지 않게 된다.

JIT 컴파일러

이렇듯 인터프리터와 컴파일러는 각각의 장단점들이 있다.
구글에서는 인터프리터와 컴파일러의 장점들을 섞어서 JIT 컴파일러(Just In Time compiler)를 만들었고 JIT컴파일러가 V8에서 핵심적인 역할을 하게 된다.


V8 엔진의 구동방식


자바스크립트 V8 엔진은 위 사진과 같은 구조로 되어있다.

  1. Parser : 낱말 분석(Lexical Analysis) 이라는 과정을 통해 코드를 토큰으로 분해한다.
    ex) 'var a = 5' -> ['var' , 'a' , '=' , '5']

  2. AST(Abstract Syntax Tree) : Parser에서 분해된 토큰을 바탕으로 tree를 생성한다.

  3. AST에서 나온 코드가 인터프리터(ignition)에게 전달되고 인터프리터는 코드를 빠르게 Bytecode 로 변환한다.

  4. 인터프리터가 코드를 실시간으로 변환하면서 브라우저에게 작업을 지시하는동안 프로파일러가 최적화 할 수 있는 부분을 찾아서 기록한다.

  5. 컴파일러는 프로파일러에게 전달받은 내용을 토대로 기계어로 변환하여 최적화를 진행한다.

  6. 최적화한 코드를 수행할 차례가 되면, Bytecode 대신 Optimized code가 실행된다.

기운빠지게도 이러한 방식도 완벽한 최적화를 이뤄내지는 못한다. 하지만 엔진의 구동 방식을 이해하고 어떤 방식으로 최적화가 되는지 이해한다면 더 좋은 코드를 짤 수 있는 개발자가 될 수 있을 것이다.


콜 스택 과 메모리 힙

자바스크립트 엔진이 우리를 위해서 여러가지 일을 한다는 것을 대략적으로 알았다. 그러나 가장 중요한 것은 어떻게 코드를 해석하냐는 것이다. 그리고 이 과정에서 가장 중요한 두 가지는
정보를 어디서 어떻게 저장하냐는 것현재 어디를 실행하고 있는지 아는 것이다. 여기서 정보를 저장하는 공간이 메모리 힙이고 실행 중인 코드를 트래킹하는 공간이 콜 스택이다.

메모리 힙

const number = 610;
const string = 'some text'
const human = {
  first : "Andrei",
  last  : "Neagoie"
}

위의 코드처럼 우리가 변수를 선언한다는 것은 자바스크립트 엔진에게 '이러한 값을 저장할건데 메모리에 저장해주세요' 라는 말과 같다. 메모리 힙은 자바스크립트 엔진이 우리에게 제공하는 메모리의 넓은 영역이다. 우리는 이 메모리 힙에 임의의 데이터를 정렬되지 않은 상태로 보관할 수 있다.

결론 : 메모리 힙이란 변수나 함수의 저장과 호출이 발생하는 메모리의 일정 영역이고 이는 자바스크립트 엔진이 제공한다.

콜 스택

콜 스택도 마찬가지로 메모리에 존재하는 공간 중 하나이다. 코드를 읽어내려가며 작업들을 스택 형태로 저장하고 이를 통해 어느 부분이 실행중인지 트래킹 하기 위해 설계된 공간이다. 예시를 들어서 알아보자.

먼저 다음과 같은 코드를 크롬 개발자 모드에서 실행시켜보자.

function subtractTwo(num){
  return num - 2;
}

function calculate(){
  const sumTotal = 4+5;
  return subtractTwo(sumTotal);
  
}
debugger;
calculate()
  1. F12 개발자 모드에 들어가서 Source -> New snippet에 위 코드를 작성하면 다음과 같이 Call Stack에 익명 함수가 들어가 있는 것을 확인할 수 있다. 이 익명함수는 현재 코드의 전체에 해당하는 자바스크립트 파일을 실행했기 때문에 콜 스택에 들어가 있는 것이다.
    (이 익명함수는 Global Execution Context 라고 한다.)

  1. 그 다음 F11을 눌러서 진행시키다 보면 calculate함수가 콜 스택에 들어가는 것을 확인 할 수 있다.

  2. return subtractTwo 구문을 만나면 콜 스택에 subtractTwo함수를 넣고 해당 함수로 이동하는 것을 확인할 수 있다. scope 에서 해당 스코프의 변수들도 확인할 수 있다.

  3. return num-2 구문을 만나 Return value에 7을 저장하였다.

  4. subtractTwo 함수가 콜 스택에서 제거되었고 지역 스코프에 맞게 num이라는 이름이었던 변수도 sumTotal로 변경되었다.

  5. calculate 함수도 콜 스택에서 제거되고 지역 스코프를 갖는 변수들도 전부 사라졌다. 한 번더 실행하면 익명함수 또한 콜 스택에서 제거되고, 모든 수행이 종료된다.

최종적으로 콜 스택과 메모리 힙이 어떻게 동작하는지 나타내면 다음과 같다. 참고로 콜 스택에도 변수가 저장될 수 있고 , 객체나 배열 , 함수 등과 같은 복잡한 데이터들은 메모리 힙에 저장된다.

결론 : 콜 스택을 사용하면 stack frame 으로 부터 현재 코드의 어느 부분을 실행중인지 알 수 있고, 메모리 힙을 참조하여 코드에 필요한 변수나 함수의 위치를 참조할 수 있다.

profile
FE 개발자가 되자
post-custom-banner

0개의 댓글