Javascript 엔진?
개발자가 자바스크립트에 대한 코드를 짜면 이 자바스크립트 코드를 해석하고 실행시켜줘야 하는데, 바로 이것이
자바스크립트 엔진의 역할을 담당한다. 자바스크립트 엔진은 자바스크립트 코드를 해석하고 실행하기 위해
안들어진 일종의 프로세스 가상머신인 것이다.
WebPack 에서는 자바스크립트 엔진을 다음과 같이 정의한다.
- 자바스크립트 엔진은 자바스크립트 코드를 실행하는 프로그램 또는 인터프리터이다.
- 자바스크립트 엔진은 전통적인 인터프리터일 수 있고, 특정한 방식으로 바이트코드로 JIT 컴파일을 할 수 있다.
- 여러 목적으로 자바스크립트 엔진을 사용하지만 대체적으로 웹 브라우저에서 사용된다.
자바스크립트 엔진의 역할
자바스크립트 엔진의 역할은 어떤 역할을 담당해주는 것일까?
자바스크립트 엔진은 개발자가 작성한 자바스크립트 코드를 브라우저의 의해 해석하거나 애플리케이션에 임베드 될 수 있는 빠르고 최적화된 코드로 변환해주는 일을 담당한다.
자바스크립트 엔진의 종류
대표적인 자바스크립트 엔진은 구글의 V8인데 이외에 많은 자바스크립트 엔진이 존재한다.
- Rhino - 모질라
- SpiderMoney - 파이어폭스
- V8 - 구글, 오페라
- JavaScriptCore - 사파리
- Chakra - 익스플로러, 마이크로소프트 엣지
구글의 V8 엔진
V8 엔진은 구글이 제작하고 구글 크롬에서도 사용 중이며 오픈소스로 구성되어있고 C++로 작성되었다. 그리고 Node.js의 런타임으로도 사용된다. V8은 웹 브라우저 내부에서 자바스크립트 수행 속도를 개선하기위해 처음 고안되었는데,
속도 향상을 위해서 인터프리터를 사용하는 대신 자바스크립트 코드를 더욱 효율적인 머신 코드로 번역해준다.
또한, 자바스크립트 코드를 컴파일하고 실행하며, 메모리를 할당하고, 가비지 컬렉팅(쓰레기 수집)을 한다.
V8은 자바스크립트 코드를 어떻게 컴파일할까?
V8 엔진은 소스코드를 직접 머신 코드로 컴파일하는 두 개의 컴파일러로 구성되어 있다.
- 플코드젠(Full-codegen): 최적화되지 않은 코드를 생성해주는 빠른 컴파일러
자바스크립트 코드를 처음 수행할때, 이를 통해서 머신 코드의 실행을 매우 빠르게 시작할 수 있게
되는 것이다. 이와 같이 V8에서는 중간 바이트코드를 이용하지 않기 때문에
인터프리터가 필요 없다. - 크랭크샤프트(Crankshaft): 빠르고 최적화된 코드를 생성하는 느린 컴파일러
풀코드젠(Full-codegen)이 생성한 코드를 크랭크샤프트(Crankshaft)가 검사하여, 최적화가 필요하다고
판단된다면 코드를 변경한다. 자바스크립트 추상구문트리를 고수준 정적단일할당(SSA)로 번역하는데
이를 하이드로젠이라고 부른다. 크랭크샤프트는 또한 하이드로젠 그래프를 최적화하고자
노력하기도 한다.
컴파일러와 인터프리터
V8엔진이 어떻게 작동하는지 알아보기 전, 프로그래밍에서 사용하는 두가지 번역 방식에 대해 알아보자.
인터프리터같은 경우 자바스크립트 파일을 입력받으면 한 줄 한 줄 해석하면서 중간 단계의 Bytecode로 변환한다.
반면 컴파일러는 자바스크립트 코드를 입력받으면 파일 전체를 읽은 뒤, 이를 컴파일하여 기계어로 변환한다.
그리고 이 기계여(Machine Code)는 CPU로 입력되어 코드를 실행한다.
인터프리터와 컴파일러, 둘 중 어떤 것이 더 나은 방식일까? 절대적으로 더 나은 것은 없다. 각각의 장단점을 알고,
언제 어떻게 사용하는 것이 효율적인지를 아는게 중요하다.
인터프리터 vs 컴파일러
1.인터프리터
- 장점: 코드 저체가 컴파일된 Compilation이 완성되는 것을 기다릴 필요 없이, 한 줄 한 줄 벼환하기 때문에
실행 속도가 빠르다.자바스크립트는 웹을 위해 개발한 언어이고, 유저에게 최상의 경험을 제공해야 하기에
빠르게 실행되는 것이 중요한 자바스크립트에 이상적인 코드 변환 방식이다. - 단점: 자바스크립트 코드가 복잡해질수록 점점 속도가 느려진다. 예를 들어, 같은 코드를 여러차례 반복하는
반복문의 경우, 같은 결과를 반복하는 것임에도 불구하고 코드를 한 줄 한 줄 읽는 방식 때문에 10억번을
반복해야 한다면 말그대로 10억번을 반복해 코드 변환을 진행하게 된다.
2.컴파일러
- 장점: 컴파일러는 작업을 단순화시킨다. 예를 들어 특정 함수를 10억번 반복해야 할 경우에는 컴파일 과정에서
함수를 반복하는 것이 아닌 함수의 결과물을 반복하도록 컴파일 한다. 이처럼 불필요한 동작을 제거하는
컴파일러의 방식을 최적화, optimization이라고 한다. (단 인터프리터는 optimization을 하지 않는다.) - 단점: 코드를 바로 실행하지 않고, 코드 실행 전 전체를 컴파일 하는 과정이 필요하기 때문에 초기에는
속도가 느릴 수 있다.
JIT 컴파일러 (Just In Time Compiler)
자바스크립트 V8 엔진은 위 사진과 같이 정의되어있다. 순차적으로 정리하자면
- Parser: 낱말 분석(Lexical Analysis)이라는 과정을 통해 코드를 토큰으로 분해
ex) let a = 5 = ['let', 'a', '5'] - AST(Abstract Syntax Tree): Parser에서 분해된 토큰을 바탕으로 tree를 생성한다.
- AST에서 나온 코드가 인터프리터(ignition)에서 전달되고 인터프리터는 코드를 빠르게 Bytecode로 변환한다.
바이트코드(Bytecode)는 고급언어로 작성된 소스코드를 가상머신이 한결 편하게 이해하도록 중간코드를
한번 컴파일 한것을 의미한다. V8에서는 ignition이 이 역할을 수행하고 있다. - ignition을 개발할 때는 모든 소스를 한번에 해석하는 컴파일 방식이 아닌 코드 하줄 한줄이 실행될 때마다
해석하는 인터프리트 방식을 채택해 세가지 이점을 가져가고자 하였다.
1. 메모리 사용량 감소: 자바스크립트코드에서 기계어로 컴파일 하는 것보단 바이트 코드로 컴파일하는 방식이 더 편하다.
2. 파싱 시 오버헤드 감소: 바이트 코드는 간결하기 때문에 다시 파싱하기도 편하다.
3. 컴파일 파이프 라인의 복잡성 감소: Optimizing이든 Deoptimizing이든 바이트 코드 하나만 생각하면 되기에 편하다. - 이후 이 바이트 코드를 실행함으로써 우리의 소스 코드가 실제로 작동하게 되며, 그 중 자주 사용되는 코드는TurboFan으로 보내져 Optimized Marchine Code, 즉 최적화된 코드로 다시 컴파일된다. 그러다 다시 사용이 덜 되었다고 판단되면 Deoptimizing하기도 한다.
- 인터프리터가 코드를 실시간으로 벼환하면서 브라우저에게 작업을 지시하는 동안 프로파일러가 최적화 할 수 있는 부분을 찾아 기록한다.
- 최적화가 가능한 부분을 찾으면 프로파일러는 이를 컴파일러에게 전달하고, 컴파일러는 인터프리터에 의해서 실시간으로 웹사이트가 구동되는 동안에 필요한 부분을 기계어로 변환하여 최적화를 진행한다.
- 최적화한 코드를 수행할 차례가 다가오면, ByteCode 대신 컴파일러가 변환한 최적화된 코드가 그 자리를 대체하여 실행된다.
V8은 런타임 중에 Profile이라는 친구에게 함수나 변수들의 호출 빈도와 같은 데이터를 모으라고 시킨다. 이렇게 모인 데이터를 들고 TurboFan에게 가져가면 TurboFan은 자기 기준에 맞는 코드를 가져와 최적화를 하는 것이다
최적화의 기법으로는 히든 클래스(Hidden Class)나 인라인 캐싱(inline Caching) 등 여러가지 기법을 사용한다.
간단하게 설명하자면 히든 클래스는 비슷한 놈들끼리만 분류해놓고 가져다 쓰는 것. 인라인 캐싱은 자주사용되는 코드가 만약 hello()와 같은 함수의 호출부라면 이것을 function hello () {..}과 같이 함수의 내용으로 바꿔버리는 것이다.
말 그대로 캐싱(Caching)이다.
어떤 조건으로 최적화 하는 것일까.
- 코드가 뜨겁고 안정적인 것, 쉽게 말해 자주 호출되고, 코드가 변하지 않는(안정적)이라는 뜻. 매번 같은 행동을
수행하는 반복문 내에 있는 코드 같은 경우가 여기에 해당된다. - 인터프리팅된 바이트 코드의 길이를 보고 특정 임계점을 넘기지 않으면 작은 함수로 판단해 최적화를 진행하는 것.
작고 단순한 함수는 크고 복잡한 함수보다 매우 추상적이고 제한적인 확률이 높기 때문에 안정적이라 볼 수 있다.
한편 컴파일러는 100%로 완벽하지 못하기 때문에 의도와 다르게 발적화(deoptimization)이 일어날 수 있다. 따라서
현재까지 이해한 자바스크립트 엔지의 구동 원리를 기반으로, 빠르고 효율적인, 최적화된 코드를 작성할 수 있다.
여러 스레드를 사용하는 V8
V8 엔진은 내부적으로 여러개의 스레드를 사용하고 있다.
- Main Thread(메인 스레드) - 코드를 가져와 컴파일하고 실행한다.
- Profiler Thread(프로파일러 스레드) - 어떤 메소드에서 사용자가 많은 시간을 보내는지 런타임에게 알려줘 크랭크 샤프트 컴파일러가 이들을 최적화하게 해준다.
- 그외 - 가비지컬렉터 스윕을 처리하기 위한 몇가지 스레드가 존재한다.
참고 자료
[JavaScript] 자바스크립트 엔진, V8
자바스크립트 엔진? 개발자가 자바스크립트 코드를 짜면 이 자바스크립트 코드를 해석하고 실행시켜줘야 하는데, 바로 이것이 자바스크립트 엔진의 역할이다. 자바스크립트 엔진은 자바스크립
jess2.tistory.com
V8 작동원리
우리가 작성한 자바스크립트 파일을 컴퓨터가 읽을 수 있는 것은 자바스크립트 엔진이 있기에 가능한 것이다. 자바스크립트 엔진은 일련의 과정을 거쳐 컴퓨터에 최적화된 코드로 변환해 전달
dkwjdi.tistory.com
'프론트 엔드 > Javascript' 카테고리의 다른 글
[Javascript] Promise.all 이란? (0) | 2022.02.02 |
---|---|
[Javascript] Event Loop (0) | 2022.01.24 |
[Javascript] OOP vs FP란 무엇인가? (0) | 2022.01.23 |