V8 엔진은 구글이 개발한 고성능 자바스크립트 엔진으로, Google Chrome과 Node.js에서 사용됩니다.
V8은 ECMAScript 및 Web Assembly를 표준에 맞게 구현하였으며, JavaScript 코드를 컴파일하여 매우 빠른 실행 속도를 보장합니다.
Just-In-Time(JIT)이라는 컴파일링 기술을 사용하여, JavaScript 코드를 실행할 때 실시간으로 컴파일합니다. 그리고 안정적이고 안전한 구조를 가지고 있어, 코드가 비정상적으로 동작하거나 메모리 누수가 발생하는 경우를 막습니다.
이렇게 가볍고, 빠르고, 강력한 자바스크립트 엔진이기 때문에, 현재도 여러 응용 프로그램에서 사용되고 있죠.
V8엔진의 구조
V8은 다양한 구성 모듈을 포함하고 있습니다.
- Ignition: 자바스크립트 언어를 한줄씩 코드를 바이트코드(Bytecode)로 변환해 주는 인터프리터입니다.
- TurboFan: ignition을 통해 만들어진 바이트코드 중, 프로파일러를 통해 자주 사용하는 코드를 찾아 최적화해주는 최적화 컴파일러입니다.
- Liftoff: V8이 WebAssembly를 사용할 수 있도록. WASM을 컴파일해주는 One-pass compiler입니다. 이렇게 V8엔진은 Liftoff 덕분에 javascript 이외의 C나 C++과 같은 언어를 WebAssembly를 통해서 실행할 수 있습니다.
- Orinoco: Orinoco는 가비지 컬렉션에 병렬(parallel), 인크리멘탈(incremental) 및 동시(concurrent) 기술을 사용하여 메인 스레드를 방해하지 않도록 하는 V8 GC 프로젝트의 코드명입니다.
V8엔진의 동작방식
V8엔진의 동작 방식은 여러 가지가 있지만 그중 가장 대표적인 동작방식을 설명하려 합니다. 기본적으로 javascript가 V8엔진을 통해 어떻게 실행이 되는지 알아보겠습니다.
Parser와 AST
V8엔진은 javascript 소스코드를 받으면 먼저 Lexical Analysis (낱말 분석) 과정을 통해 코드를 토큰으로 분해합니다.
이렇게 분해된 토큰들은 인터프리터가 읽기 쉽도록 AST(Abstract Syntax Tree)라는 추상 구문 트리로 변환됩니다.
Ignition
위에서 만들어진 AST를 Ignition이라는 인터프리터에 넘겨지고 바이트 코드(Bytecode)로 변환됩니다.
이후 가상머신이 이 바이트 코드를 실행함으로써 우리가 작성한 코드가 동작하게 되는 것입니다.
TurboFan
Ignition으로 만들어진 코드가 실행되면서 프로파일러가 자주 사용되는 코드를 찾아 TurboFan에 전달합니다. TurboFan은 이런 자주 사용되는 바이트 코드를 최적화하여 머신 코드로 컴파일합니다.
그 이후 바이트 코드를 사용하지 않고 TurboFan이 만든 머신 코드를 사용하게 되는 것입니다.
그러나 이후 최적화된 코드가 다시 자주 사용하지 않거나 중간에 코드가 변경될 경우, 디옵티마이징(Deoptimizing)이 되어 다시 바이트코드로 변환할 수 있습니다.
옛날과 현재의 V8엔진
V8엔진은 5.9 버전 이전과 이후로 파이프라인의 변화가 크게 바뀌었습니다. 위에서 설명한 Ignition과 TurboFan은 5.9버전 이후로 등장했습니다.
그렇다면 5.9버전 이전의 V8엔진은 어떻게 생겼고, 왜 이렇게 바뀌었을까요?
과거의 V8엔진 Full-codegen과 Crankshaft
V8은 독일 구글 개발 센터에서 만들어진 JavaScript 엔진으로, 웹 브라우저 안에서 실행되는 JavaScript의 성능을 높이기 위해 만들어졌습니다.
JavaScript는 인터프리터 언어입니다. 인터프리터 언어는 코드를 실행할 때에 인터프리터가 머신 코드로 번역하고 실행하기 때문에 컴파일 언어에 비해 비교적 느립니다.
구글에선 속도가 빠른 JavaScript 엔진을 만들기 위해 인터프리터를 이용하는 대신, JavaScript 코드를 좀 더 효율적인 머신 코드로 번역하는 방법을 선택했습니다. 즉, 바이트코드 또는 다른 중간 코드를 생성하지 않는다는 것이죠.
그렇게 V8과 탄생한 것이 Full-codegen과 Crankshaft입니다.
V8 엔진의 Full-codegen 컴파일러
V8 엔진의 Full-codegen 컴파일러는 명령어를 실행하기 위해 자바스크립트 코드를 머신 코드로 번역하는 과정에서 사용됩니다.
이 단계에서, 자바스크립트 코드는 초기화되고 분석됩니다. 그리고, 이를 위해 각각 수행해야 할 각 명령어들이 계산됩니다. 명령어들의 세부 사항은 레지스터들과 메모리 주소들에 따라 결정됩니다.
이 과정을 통해 생성된 머신 코드는 실행 방법과 메모리 관리 방법에 따라 변경됩니다.
V8 엔진의 Crankshaft 옵티마이저
V8 엔진의 Crankshaft는 두 번째 단계인 옵티마이저입니다.
옵티마이저는 이전 단계에서 만들어진 머신 코드를 수정하고 최적화하기 위해 사용됩니다.
Crankshaft 옵티마이저는 Javascript 코드의 성능을 향상시키기 위해 사용됩니다. Crankshaft은 변수의 값이 정해져 있거나 반복되는 경우에 사용되는 기계 코드의 재사용을 통해 명령어를 최적화합니다. 이는 코드의 실행 속도를 높이며, 메모리 관리를 더욱 효율적으로 할 수 있게 합니다.
왜 Full-codegen과 Crankshaft는 대체되었는가?
예전에 크롬이 메모리를 어마어마하게 많이 먹어서 생겨난 밈입니다. 크롬은 왜 저런 밈이 생겼을까요?
Full-codegen에서 Ignition으로!
크롬은 V8을 사용합니다. 옛날의 Full-codegen을 사용하던 V8은 전체 javascript 코드를 한꺼번에 머신 코드로 컴파일을 했습니다. 그렇기 때문에 메모리 사용량이 많았습니다.
그런데 문제는 스마트폰이 탄생하면서 크롬도 모바일을 지원해야 하게 되었습니다. 과거의 메모리 사양이 부족했던 시절, Full-codegen은 모바일에 문제가 크게 부각되었습니다.
결국 전체 코드를 컴파일하는 것이 아닌 다시 인터프리터 방식으로 돌아와 메모리를 아끼고 바이트 코드의 장점을 더욱 살려 현재의 ignition이 만들어지게 되었습니다.
Crankshaft는 TurboFan으로!
Crankshaft는 javascript의 지속적인 발전과 다양한 아키텍처의 등장으로 계속 새로운 사양에 맞춰 최적화를 해나가야 했습니다.
그렇게 Crankshaft는 몸집이 불어나고 확장되었습니다. 구글은 계속 Crankshaft를 계속 확장하는 것은 무리라고 판단했다 합니다.
전체적인 파이프라인을 개선하면서 여러 레이어로 계층화되고, 좀 더 유연하게 확장에 용이하도록 설계하여 현재의 TurboFan이 만들어졌습니다.
그래서 현재의 TurboFan은 확장에 용이해졌기 때문에, Ignition을 통한 바이트코드를 최적화하는 것뿐만 아니라, LiftOff로 만들어진 코드를 최적화하는 등 다양한 방면으로 사용되고 있습니다.
다음 이야기
다음은 TurboFan의 최적화 이야기에 대해 알려드리고자 합니다.
TurboFan은 다양한 최적화 방법을 가지고 있습니다.
특히 V8은 웹 개발이나 Nodejs를 이용한 서버 개발, electron 개발 등 다양한 방면에서 사용되고 있는 만큼 최적화 방식에 대한 중요성을 높다고 생각됩니다.
잘못된 내용이 있는 경우 댓글로 지적해 주시면 검토 후 반영하겠습니다.
'개발 아카이브 > 개발 관련 지식' 카테고리의 다른 글
V8 Liftoff와 웹 어셈블리: 웹 성능을 향상시키는 강력한 조합 (0) | 2023.04.20 |
---|---|
[노션 API] 노션 API 연동으로 데이터베이스 사용하기 (3) | 2023.04.09 |
정규식을 이용한 공격 - ReDos (0) | 2022.12.24 |
Postman 대신 사용하는 VS Code API Test Extention - Thunder Client (1) | 2022.11.13 |
배워보자 정규표현식! (Regular Expression) (0) | 2022.09.18 |