들어가기전에...

  • 이 글은 Thibault Laurens의 글 How the V8 engine works? 를 번역한 글입니다. 글에서 오역이나 의역이 있을 수 있습니다. 틀린 부분이 있다면 지적해주시면 확인 후 바로 정정하겠습니다.

How the V8 engine works?

V8은 독일의 Google development center 에서 만들어진 자바스크립트 엔진 입니다. 이 엔진은 C++로 만들어진 Open source 이며, 클라이언트 측 (Chrome)과 서버 측 (node.js)의 JavaScript 애플리케이션 모두에 사용됩니다.

V8은 웹 브라우저 내에서 JavaScript 실행 성능을 높이기 위해 처음 개발되었습니다. V8은 속도를 높이기 위해 인터프리터 대신 JavaScript 코드를 보다 효율적인 기계어로 변환합니다. SpiderMonkey 나 Rhino (Mozilla)와 같은 많은 최신 자바 스크립트 엔진과 같이 JIT (Just-In-Time) 컴파일러 를 구현하여 실행 시 JavaScript 코드를 기계어로 컴파일합니다 . V8과의 주요 차이점은 바이트 코드 또는 중간 코드를 생성하지 않는다는 점입니다.

이 글의 목적은 클라이언트 측 또는 서버 측 애플리케이션 모두에 최적화 된 코드를 생성하기 위한 V8의 작동 방식 을 보여주고 이해하기 위함 입니다. "JavaScript 성능에 관심을 가져야 할까요?"라고 묻는다면, Daniel Clifford (V8 팀 기술 책임자 겸 관리자)의 말을 인용하여 대답 할 것입니다. "현재의 애플리케이션을 더 빨리 실행할 수있는 것은 아닙니다. 과거에는 할 수 없었던 일들을 가능하게 합니다."

image.png

Hidden class

JavaScript는 프로토 타입 기반 언어입니다. 복제 프로세스를 사용하기 때문에 클래스와 객체가 생성 되지 않습니다. JavaScript는 또한 동적으로 type이 지정됩니다. type 및 type 정보는 명시적이지 않으며 property 는 직접적으로 객체에 추가 및 삭제 할 수 있습니다. type 및 property 에 효과적으로 액세스하는 것이 V8의 첫 번째 큰 도전 과제입니다. 객체 property 를 저장하기 위해 사전과 같은 데이터 구조를 사용하고 (대부분의 자바 스크립트 엔진과 마찬가지로) property 위치를 확인하기 위해 동적으로 검색을 수행하는 대신 V8은 runtime 에 hidden class를 생성 하여 type 시스템의 내부 표현을 갖고, 속성 액세스 시간을 향상시키고 있습니다.

예를 들어, Point함수와 두 Point객체를 생성을 보겠습니다 .

image.png

만약 위 사진과 같다면, 이 경우 pq 는 V8에 의해 생성 된 같은 hidden class에 속합니다. 이는 hidden class 를 사용하는 또 다른 장점입니다. V8에서는 속성이 동일한 객체를 그룹화 할 수 있습니다. 다음 pq최적화 된 코드를 같이 사용합니다.

이제, 선언 직후의 q 객체에 z속성 을 추가하려 한다고 가정 해 봅시다

V8은 이 시나리오를 어떻게 다룰까요? 사실 V8은 생성자 함수가 속성을 선언 하고 hidden class 의 변경 사항을 추적 할 때마다 새로운 hidden class를 만듭니다. 왜냐하면, 두 개의 객체가 생성되고( p그리고 q), 생성 후 두 번째 객체 ( q )에 멤버가 추가 되면, V8은 최근에 생성된 hidden class를 추적하여(첫 객체 p ) 새 멤버를 위한 새 hidden class를(객체 q) 만듭니다.

image.png

새로운 hidden class가 생성 될 때마다 이전의 hidden class 는 자기 대신 사용되어야하는 hidden class로 업데이트됩니다.

코드 최적화

V8은 각 속성에 대해 새로운 hidden class 를 생성하기 때문에 hidden class 생성을 최소한으로 유지해야 합니다. 이렇게 하려면, 객체를 만든 후에 속성을 추가하지 말고 같은 순서로 객체 멤버를 항상 초기화 해야합니다 (다른 hidden class 트리를 만들지 않기 위해).


V8에 최적화 된 JavaScript 코드의 예

Tagged values

숫자와 JavaScript 객체를 효율적으로 표현하기 위해 V8은 둘 다 32 비트 값으로 나타냅니다 . 1비트는 Object(flag = 1) 인지 integer(flag = 0, SMall Integer 또는 SMI라고 불리는) 인지를 구별하기 위해 사용됩니다. 이이 구별 때문에 32비트 중 31비트를 값을 표현하는데 사용합니다. 그런 다음 integer 값이 31 비트보다 큰 경우 V8은 숫자를 감싸서 double 로 바꾸고 새 객체를 만들어 내부에 숫자를 넣습니다.

코드 최적화 : 자바 스크립트 객체에 값 비싼 boxing 작업을 피하기 위해 가능한 한 31 비트의 부호 있는 숫자를 사용하십시오.

배열

V8은 배열을 처리하는 두 가지 다른 방법을 사용합니다.

  • Fast elements : 키 집합이 매우 컴팩트 한 배열로 설계되었습니다. 이것은 매우 효율적으로 액세스 할 수 있는 linear storage buffer를 가집니다.
  • Dictionary elements: 내부에 모든 요소가 없는 희소 배열 용으로 설계되었습니다. 실제로는 hash table 입니다. "Fast Elements" 보다 접근하는데 비용이 더 비쌉니다.

코드 최적화 : V8이 배열을 처리하기 위해 "빠른 요소"를 사용하는지 확인하십시오. 즉, 키가 증가 하지 않는 경우 희소 배열을 피하십시오. 또한 큰 배열을 미리 할당하지 않도록 하십시오. 당신이 필요할 때마다 크기를 증가 시키는게 낫습니다. 마지막으로 배열의 요소를 삭제하지 마십시오.


V8은 JavaScript 코드를 어떻게 컴파일합니까?

V8에는 두 개의 컴파일러가 있습니다!

  • 자바 스크립트에 좋은 코드를 생성 할 수 있는 "Full" Compiler: 훌륭하지만 훌륭한 JIT 코드는 아닙니다. 이 컴파일러의 목표는 코드를 신속하게 생성하는 것입니다. 목표를 달성하기 위해 type 분석을 하지 않으며 type에 대해 알지 못합니다. 대신 인라인 캐시 또는 "IC"전략을 사용하여 프로그램이 실행되는 동안 type에 대한 지식을 구체화합니다. IC는 매우 효율적이며 약 20 배의 속도 향상을 가져옵니다.
  • 대부분의 자바 스크립트 언어에 대해 훌륭한 코드를 생성 하는 Optimizing Compiler: 나중에 제공 되며 hot function 을 다시 컴파일합니다. 최적화 컴파일러는 인라인 캐시에서 type을 가져 와서 코드를 더 잘 최적화 하는 방법을 결정합니다. 그러나 일부 언어 기능은 try / catch 블록와 같이 아직 지원되지 않습니다.

코드 최적화 : V8은 최적화 해제 를 지원합니다 . 최적화 컴파일러는 인라인 캐시에서 다양한 유형에 대한 최적화된 가정을 합니다. 이러한 가정이 유효하지 않은 경우 최적화가 해제됩니다. 예를 들어 생성 된 hidden class 가 예상 한 클래스가 아닌 경우 V8은 최적화 된 코드를 버리고 Full Compiler로 돌아와 인라인 캐시에서 다시 type을 가져옵니다. 이 프로세스는 느리고 최적화 된 후에는 함수를 변경하려 시도하면 안됩니다.