자바스크립트 엔진 ?

무제·2021년 8월 21일
4
post-thumbnail

왜 자바스크립트는 엔진이 필요할까?

컴퓨터를 컨트롤 하기 위해서는 소통이 필요하다. 그러기 위해서는 언어가 필요하다. 그렇기에 우리는 Computer Language라는 매개체를 사용해 컴퓨터와 소통한다. 그러나 인간이 써내려 놓은 코드를 컴퓨터는 이해할 수 있을까? 당연히 이해하지 못한다.

따라서 위와 같은 이유로 인해 Highlevel Language를 컴퓨터가 이해할 수 있게 컴파일이나 인터프리터 과정을 거친 후 머신 코드(기계어)로 바꿔주어야 한다.

그러므로 JS도 컴퓨터의 CPU가 알 수 있게 기계어로 변환되어야 하기에 엔진이 필요하다.

컴파일러 vs 인터프리터

📎 컴파일러(compiler, 순화 용어: 해석기, 번역기)는 특정 프로그래밍 언어로 쓰여 있는 문서를 다른 프로그래밍 언어로 옮기는 언어 번역 프로그램
📎 인터프리터(interpreter, 문화어: 해석기)는 프로그래밍 언어의 소스 코드를 바로 실행하는 컴퓨터 프로그램 또는 환경

인터프리터와 컴파일러는 하나의 언어를 다른 언어로 바꿔준다는 점에서 비슷한 맥락을 갖지만 몇 가지 차이점을 갖는다. 인터프리터는 전체의 소스 코드를 중간 코드로 변환하고 직접 실행지만 컴파일러는 전체의 소스 코드를 읽고 한 번에 머신 코드로 번역을 한다.

엔진의 종류

자바스크립트의 엔진은 보통 브라우저사가 개발하고 제공한다. 우리가 흔히 볼 수 있는 메이저 브라우저들은 저마다 각자의 독립적인 엔진을 갖고 있다. 그래서 브라우저마다 호환되는 ECMAScript의 차이가 나는 이유가 바로 이거다.

  • 구글의 크롬 - V8 Engine
  • 파이어폭스 - SpiderMonkey (처음으로 만들어진 엔진)
  • JavascriptCore - Safari

항상 구글의 크롬에서 개발을 진행해왔기에 크롬의 V8엔진 에 대해서 블로깅 할 것이다..🔥

V8 엔진은 어떻게 작동하나?

V8 엔진은 📎 JIT(Just-In-Time)컴파일 기법을 사용한다. JIT 컴파일 기법이란 소스코드를 컴파일링과 컴파일링 된 코드 실행을 따로 하지 않고 동시에 진행하는 기법이다. 코드를 실행하는 시점에서 프로그래밍 언어를 읽어가면서 해당 기능에 대응하는 기계어 코드를 실행하는 방법실행하기 전에 프로그램 코드를 기계어로 번역하는 방법혼합한 방식이다. 실행 시점에서 기계어 코드를 생성하면서 그 코드를 캐싱하여, 같은 함수가 여러 번 불릴 때매번 기계어 코드를 생성하는 것을 방지한다.

V8엔진은 내부적으로 TurboFan 이라고 불리는 컴파일러와 Ignition 이라고 불리는 인터프리터를 사용한다.

  1. JS 파일이 엔진에 들어오면 파서는 코드를 쪼갠 뒤 토큰화 시킨다.

  2. 토큰화된 코드들은 AST(Abstract Syntax Tree)를 만든다.

    여기서 만약 키워드의 오타나 괄호 기입에 오류가 나면 Unexpected Token 에러가 나게 된다. (📎 AST를 볼 수 있는 사이트)

  3. 인터프리터는 쪼개진 코드들을 바로 바이트코드로 변환한다.

  4. Profiler는 최적화할 수 있는 코드들이 있는지 없는지 확인한다. 만약 최적화 할 수 있는 코드들이 존재 할시 Turbo Fan 컴파일러로 코드를 최적화한 뒤 바이트코드를 수정한다.

V8엔진은 소스 코드를 컴파일 하면서 동시에 실행을 하는데, 실행을 하면서 코드의 정보를 수집해 코드를 재사용시 컴파일을 다시 할 필요가 없다.

데이터 저장 - 힙 VS 콜 스택

자바스크립트 엔진은 자바스크립트 코드를 컴파일하고 실행하는 프로그램이다.

자바스크립트 엔진은 코드를 실행하는데 있어 필요한 데이터를 저장하기 위해 메모리를 할당한다. 그렇다면 데이터를 어디다가 어떻게 저장할까 ? 엔진은 크게 콜 스택 두 개의 저장소가 있다.

  • 힙(Heap) : 참조형 자료들이 저장되는 곳
  • 콜 스택(Call Stack) : 원시 자료형, 참조형 자료들이 저장된 힙의 주소 값, 함수 호출의 실행 컨텍스트(Execution Context)를 저장하는 곳

힙에서는 배열, 객체, 함수와 같은 참조형 자료들이 실제로 저장되는 곳이다. 힙의 특징은 고정된 메모리의 양이 할당되지 않는 다는 점이다. 필요에 따라서 메모리의 양이 커질수도 있도 있다.

콜 스택

콜 스택에서는 Number, String, Boolean, undefined, null, Symbol과 같은 원시 자료형, 참조형 자료들이 저장되어 있는 힙의 주소 값이 저장된다. 또한 함수 호출로 인한 Function context 및 Global Context 들이 스택(LIFO)처럼 쌓인다.

  • 실행 컨택스트 ( Execution Context ) : 코드가 실행되는 환경을 뜻한다. 실행 컨텍스트글로벌 컨텍스트함수 컨텍스트Eval 컨텍스트로 나눠진다.
  • LIFO : First In First Out 자료구조. 가장 마지막에 들어온 것이 가장 처음 나가는(실행) 자료구조를 뜻한다.

한 번에 한 가지의 일만 - 싱글 쓰레드

working alone

자바스크립트 엔진은 하나의 콜 스택이 존재한다.콜 스택에는 브라우저에서 처음 파일을 로드할 때 기본적으로 글로벌 컨텍스트가 가장 처음 생성되며 그 위로 함수 호출로 생성된 함수 컨텍스트들이 LIFO의 구조로 쌓여나간다.

하나의 콜 스택.

쓰레드의 개수는 몇 개의 프로그램이 독립적으로 동시에 작동할 수 있는지를 나타내는 지표이다. 자바스크립트는 하나의 콜 스택을 갖고 있기에 동시에 두 개의 프로그램을 작동시킬 수 없다.

console.log("hi");

// 1 억번 Loop
for(let i = 0; i < 100000000; i++){
  console.log(i);
};

console.log("bye")

// 콘솔에는 bye 가 가장 늦게 찍힌다. 즉 for loop가 돌기 전까지 bye가 찍힐 수 없다.

즉, 어떤 함수가 실행되고 있을 때 어떠한 코드도 실행 될 수 없다. 예를 들면 HTTP 통신과 같은 것을 동기적으로 처리한다면, response를 받기까지 모든 동작이 멈추게 된다. 이는 브라우저의 멈춤을 의미하기도 한다.

그렇다면 어떻게 이러한 문제를 해결할 수 있을까 ?

♻️ 이벤트 루프(Event Loop)를 사용하는 것이다. 이벤트 루프를 사용해 비동기적으로 태스크들을 관리하면 우리는 브라우저가 멈추는 끔찍한 문제를 해결할 수 있다.

이벤트 루프

기본 구조

이벤트 루프는 자바스크립트는 코드 실행, 이벤트 수집과 처리, 큐에 놓인 하위 작업들을 담당하는 이벤트 루프에 기반한 동시성(concurrency) 모델을 가지고 있습니다. - MDN -

콜백 함수란 함수의 인자에 들어온 함수를 콜백 함수라 말한다. 함수 내부에서 어떤 작업이 끝나면 콜백 함수를 호출하고 실행시킨다.

setTimeout, setInterval, DOM(문서 객체 모델), ajax 와 같은 WebAPIs는 자바스크립트의 엔진이 제공하는 것이 아닌 브라우저에서 제공하는 api

✅ 지속적인 콜 스택과 콜백 큐 체크

이벤트 루프는 콜 스택이 비워져 있는지 없는지, 콜백 큐 안에 실행되기를 기다리고 있는 콜백 함수들이 존재하는지 확인한다. 그래서 정확한 콜백 함수의 실행 시간을 보장하지는 않는다. 예를 들면, setTimeOut의 두 번째 인자에 들어가는 number 값은 최소한 콜백 함수가 실행 되기까지 기다려야 하는 시간(ms)일 뿐이지 그 시간 후에 실행된다는 뜻은 아니다.

🗑 가비지 콜렉션

let obj = {
  name: "abc";
}

obj = null;

힙에 저장된 obj 객체를 참조하는 주소값을 obj 변수에 할당하였다. 그 뒤 null 값으로 재할당을 하였다. 변수 obj에 담겼던 주소값은 객체가 저장되어 있던 힙을 가리키고 있었는데, 더 이상 객체를 참조하지 않는다.

그러나 자바스크립트는 high-level language이기 때문에 메모리를 자동으로 관리한다. 이 과정을 가비지 콜렉션이라고 한다.

그리고 가비지 콜렉션을 가비지 콜렉터가 실행한다.

🧹 Mark-and-sweep 알고리즘

markAndSweep

가비지 콜렉션은 Mark-and-sweep 알고리즘을 사용해 참조된 객체들을 mark 한다. 그리고 접근할 수 없는 객체들은 sweep 한다.

가비지 콜렉터가 sweep할 수 없는 것들이 있다.

  • 현재 실행중인 함수의 지역 변수
  • 현재 실행중인 함수의 의해서 사용되고 있는 변수
  • 전역 변수

Mark and sweep 알고리즘은 장단점이 존재한다.

Mark-and-sweep 알고리즘의 장단점

장점

  • 한번 싸이클이 돌았던 객체에는 Marking을 하기 때문에 절대 무한루프에 빠질 수 없다.

단점

  • Mark and Sweep 알고리즘이 여러번 수행되면, 접근할 수 있는 객체들이 여럿으로 쪼개지게 된다. 그러면서 사용하지 않는 메모리 부분들이 작게 따로 떨어지게 된다. 사용할 수 있는 메모리의 양이 있음에도 불구하고 연속적인 메모리의 양이 부족하게 된다.

흰 부분이 사용할 수 있는 메모리이고 회색 부분이 사용중인 메모리이다.

📎 참조

Understanding V8’s Bytecode

JIT 컴파일 - 위키백과

How Does JavaScript Really Work? (Part 1)

Concurrency model and the event loop - MDN

Mark-and-Sweep: Garbage Collection Algorithm - GeeksforGeeks

profile
표현할 수 없는 무제공책

1개의 댓글

comment-user-thumbnail
2022년 6월 21일

콜스택 설명 하실때 틀린 부분이 있는것 같습니다.

LIFO : First In First Out 자료구조. 가장 마지막에 들어온 것이 가장 처음 나가는(실행) 자료구조를 뜻한다.

Last In First Out 자료구조. 마지막에 들어온 것이 가장 나중에 나가는(실행) 자료 구조를 뜻한다.

라고 바꿔주시면 좋을것 같습니다

답글 달기