2. Javascript Engine
● 자바스크립트 코드를 실행하는 Program 혹은 Interpreter
● 표준적인 Interpreter로 구현될 수도 있고, 혹은 Javascript Code를 Byte
Code로 컴파일하는 Just-In-Time 컴파일러로 구현할 수도 있습니다.
3. Javascript Engine
● 구현체
○ V8 Engine: 오픈소스, 구글에서 개발, C++로 작성됨. Chrome과 NodeJS에서 사용
○ Rhino: 모질라파운데이션에서 관리, 오픈소스, 전체가 자바로 개발됨
○ SpiderMonkey : 최초의 자바스크립트 엔진. 예전에는 넷스케이프 네비게이터에 사용됐고
지금은 파이어폭스에 사용됨
○ JavaScriptCore: 오픈소스, 니트로라는 이름으로도 알려져 있으며 애플이 사파리를 위해
개발함
○ KJS: KDE의 엔진으로서 KDE 프로젝트의 컨커러 웹 브라우저를 위해 해리 포텐이 개발함
○ Chakra (JScript9): IE
○ Chakra (JavaScript): Edge
○ Nashorn: 오픈JDK의 일환으로 오픈소스이며 오라클 자바언어와 툴그롭으로 작성됨
○ JerryScript: 사물인터넷을 위한 경량 엔진
4. Javascript Engine (Revisit)
● 단일 쓰레드(single-threaded)
● Call Stack
● Event Loop
● 콜백 큐(callback queue)
● Runtime
○ setTimeout와 같은 특별한
라이브러리를 참조하지 않아도
사용할 수 있는 것
5. 싱글 쓰레드(single-threaded) 프로그래밍 언어
● 콜스택이 하나 = 한 번에 하나의 일만 할 수 있음
● 콜스택 = 기본적으로 우리가 프로그램의 어디에 있는지를 기록하는 자료 구조
● 스택 프레임(Stack Frame) = 콜 스택에 쌓이는 단위
6. function multiply(x, y) {
return x * y;
}
function printSquare(x) {
var s = multiply(x, x);
console.log(s);
}
printSquare(5);
7. ● 장점
○ 데드락(deadlocks)과 같이 멀티쓰레드 기반 환경에서 대처해야 하는 상황을 신경쓰지 않아도
됨
● 단점/제한점
○ 콜스택에 수행할 함수가 있으면 브라우저는 사실 아무것도 할 수 없다는 게 문제입니다. 바로
‘블록킹'되는 것입니다.
○ 코드 작성이 어려워짐
■ non-blocking
■ Callback hell, Promise, …
■ async/await 로 인해 조금은 쉬워짐
8. 왜 만들어졌는가?
● 웹 브라우저 내부에서 Javascript 수행 속도의 개선을 목표로 처음 만들어짐
● Compiler 개념 도입
○ 인터프리터를 사용하는 대신 Javascript Code를 더 효율적인 Machine Code로 만듦
○ Just-In-Time 컴파일러를 구현함으로써 코드를 실행 시에 Javascript Code를 Machinde
Code로 컴파일함
■ 이는 SpiderMonkey나 Rhino와 같은 현대적인 다른 Javascript Engine에서도 마찬가지임
■ 주된 차이는 Byte Code와 같은 Intermediate Code를 생산하지 않음
9. 두 개의 Engine
● 5.9 버전 이전 기준
● 두 개의 Engine을 사용함
○ full-codegen - 간단하고 매우 빠른 Compiler로서 단순하고 상대적으로 느린 Machine Code를
생산함
○ Crankshaft - 좀 더 복잡한 (JIT) 최적화 Compiler로서 고도로 최적화된 코드를 생산함
● 내부적으로 여러 개의 Thread를 사용합니다
○ Main Thread - Code를 가져와서 컴파일하고 실행함
○ Compile Thread - Code를 최적화함. Main Thread와 분리되어서 쉬지 않고 코드를 수행할 수
있음
○ Profiler Thread - 어떤 method에서 사용자가 많은 시간을 보내는지 정보를 수집해서
Crankshaft가 이를 최적화할 수 있게 해줌
○ 그 외 Garbage Collector Sweep을 처리하기 위한 몇 개의 Thread가 있음
10. Behavior Overview
● 처음 수행하는 코드
○ full-codegen을 이용해 파싱된 Javascript Code를 변형 없이 직접 Machine Code로 번역함
○ 이를 통해 머신 코드의 실행을 매우 빠르게 시작할 수 있음
● 자주 사용하는 코드는 최적화
○ Crankshaft이 수행함
○ SSA(static single-assignment)로 번역함
■ 모든 변수를 한 번만 assign할 수 있게 함
■ Compiler Optimization에 이점이 있음
○ 결과물 = Hydrogen
11. 최적화 기법 1 - Inlining
● 함수 호출 지점(함수가 호출된 곳의 코드 위치)을 호출된 함수의 내용으로
바꾸는 과정
● 함수 호출은 큰 비용이 드는 동작이기에 이를 줄임
12. ● 자바스크립트 = Prototype-based Language / Dynamic Programming
Language
○ 클래스라는 것은 없으며 객체는 복제 과정을 통해 생성됨
○ 객체가 생성된 이후에도 속성을 쉽게 추가하거나 삭제할 수 있음
● 객체를 다루는 방법
○ 보통 Dictionary와 유사한 구조(hash function 기반)를 이용해 객체 속성 값의 위치를 메모리에
저장함
○ Dictionary를 이용해서 메모리 상에서 객체 속성의 위치를 찾아내는 것은 매우 비효율적이고
그래서 속성 값을 가져오는 것은 Java나 C#에서 보다 계산적으로 더 비싼 행동이 됨
■ Java 등에서는 모든 객체 속성이 컴파일 전에 고정된 layout에 의해 결정되고 런타임에
동적으로 추가되거나 제거될 수 없음
■ 속성값(혹은 이들 속성을 가리키는 pointer)은 메모리에 고정된 offset을 가진 연속적인
buffer로 저장될 수 있고 offset의 길이는 속성 타입에 따라 쉽게 결정될 수 있음
■ 속성 타입이 동적으로 변할 수 있는 자바스크립트에서는 불가능함
최적화 기법 2 - Hidden Class
13. 최적화 기법 2 - Hidden Class
● 히든클래스(hidden classes)
○ 고정 객체 layout과 유사하게 작동하는데 다만 실행 중에 생성된다는 차이점이 있음
○ 속성이 객체에 추가되는 순서에 의존적으로 생성됨
function Point(x, y) {
this.x = x;
this.y = y;
}
var p1 = new Point(1, 2);
14. 최적화 기법 3 - Inline Caching
● PIC(Polymorphic Inline Cache) - 다양한 객체가 Parameter로 넘어올 수
있는데 이를 최적화시키는 기법
● 최적화 포인트
○ 같은 Method에 대한 반복되는 호출은 같은 타입의 객체에 이뤄진다는 관찰 결과를 이용함
○ 최근 Method 호출에 Parameter로 전달된 객체 타입의 Cache를 유지하고 이 정보를 이용해
앞으로 Parameter로 넘어올 객체의 타입에 대한 가정을 함
○ Method에 전달될 객체 타입에 대한 가정을 잘 할 수 있으면 객체의 속성에 접근할 방법을
알아내는 과정을 수행하지 않아도 됨
○ 그 대신 객체의 히든 클래스에 대해 이전에 찾아서 저장했던 정보를 사용할 수 있음
● 방법 - 해당 함수의 recompling을 통해 inlining을 수행함
15. 최적화 기법 4 - Machine Code Compilation
● Crankshaft 가 수행함
● Lithum
○ Hydrogen 그래프가 최적화되면 이를 machine code 까지 내림
○ Lithum 구현은 Architecture에 따라 다름
○ 레지스터 할당이 이 수준에서 이뤄짐
● OSR(On Stack Replacement)을 통해서 코드 치환
● 반최적화(deoptimization)
○ 가정이 더 이상 유효하지 않는 경우에 대비한 보호장치
○ 반대로의 변형을 수행해 최적화되지 않은 코드로 되돌려 놓음
16. 최적화 기법 4 - Machine Code Compilation
● OSR(on-stack replacement)
○ 모든 맥락(스택, 레지스터 등)을 전환하여 코드의 수행 중간에 최적화된 버전으로 옮겨탈 수
있도록 해줌
○ V8이 이러한 작업을 수행하는 유일한 엔진은 아님 (Java 등도 수행)
17. 최적화 기법 5 - Garbage Collection
● Mark-and-Sweep 사용
● Incremental Marking
○ Javascript의 수행을 중단함
○ GC 비용을 통제하고 그 수행을 좀 더 안정적으로 하기 위해 점진적으로 Marking을 수행함
■ 힙 전체를 훑어서 가능한 모든 객체를 마킹하는 대신 힙의 일부만을 확인함 => 정상적인
코드 실행 => 바로 이전에 멈춘 곳에서부터 다음 GC 수행 => 실행 => …
● Sweep은 별도의 Thread에서 수행됨
18. V8 ver 5.9
● 2017년 초 출시
● Interpreter - Ignition
● 최적화 Compiler - TurboFan
● 성능 향상 / 메모리 절약 / 단순함으로 인한 유지보수성 향상
19. ● 객체 속성의 순서: 객체 속성을 항상 같은 순서로 초기화해서 히든클래스 및
이후에 생성되는 최적화 코드가 공유될 수 있도록 합니다
● 동적 속성: 객체 생성 이후에 속성을 추가하는 것은 히든 클래스가 변하도록
강제하고 이전의 히든클래스를 대상으로 최적화되었던 모든 메소드를 느리게
만듭니다. 대신에 모든 객체의 속성을 생성자에서 할당합니다
● 메소드: 동일한 메소드를 반복적으로 수행하는 코드가 서로 다른 메소드를 한
번씩만 수행하는 코드보다 더 빠르게 동작합니다(인라인 캐싱 때문)
최적화된 자바스크립트 코드 작성
20. ● 배열: 값이 띄엄띄엄 있어서 키가 계속해서 증가하는 숫자가 되지 않는 배열은
피하는게 좋습니다. 모든 요소를 가지지는 않는 배열은 해시테이블입니다.
이와 같은 배열의 요소들은 접근하기에 많은 비용이 듭니다. 또한 커다란
배열을 미리 할당하지 않도록 하십시오. 사용하면서 크기가 커지도록 하는 게
낫습니다. 마지막으로 배열의 요소를 삭제하지 마십시오. 그 배열의 키가
띄엄띄엄 배치됩니다.
● 태깅된 값: V8은 객체와 숫자를 32비트로 표현합니다. 어떤 값이 오브젝트(flag
= 1)인지 혹은 정수(flag = 0)인지는 SMI(Small Integer)라는 하나의 비트에
저장하고 이 때문에 31비트가 남습니다. 따라서 어떤 숫자가 31비트 보다 크면
V8은 이 숫자를 분리해서 더블 타입으로 전환한 다음 이 숫자를 넣을 새로운
객체를 생성합니다. 이러한 동작은 비용이 높으므로 가능한한 31비트의
숫자를 사용하도록 하십시오.
최적화된 자바스크립트 코드 작성
21. 참고자료
● 자바스크립트는 어떻게 작동하는가: 엔진, 런타임, 콜스택 개관
● How JavaScript works: an overview of the engine, the runtime, and the call
stack
● 자바스크립트는 어떻게 작동하는가: V8 엔진의 내부 + 최적화된 코드를
작성을 위한 다섯 가지 팁
● How JavaScript works: inside the V8 engine + 5 tips on how to write
optimized code
● Static Single Assignment Form
● Optimizing dynamic JavaScript with inline caches