V8 엔진은 C++로 만들어 졌으며, JS와 웹어셈블리를 실행할 때 사용된다.
V8 엔진은 브라우저 위에서 뿐 아니라 독립적으로도 사용될 수 있으며 예는 node.js가 있다.
V8 엔진에서의 동작 방식을 보면
JS 코드를 읽기 -> parsing -> bytecode -> 실행
순으로 이뤄지는데 최근 브라우저에서는 turbofen 엔진을 통해 최적화를 진행한다.
V8 엔진은 다음과 같은 일을 수행하게 되는데
1) JS 코드 실행
2) 객체를 메모리 힙 영역에 할당
3) 사용하지 않는 객체를 GC에서 해제
4) hidden class를 사용하여 객체 프로퍼티 참조하여 참조 속도 개선
5) turbofen 엔진을 통해 최적화 -> 속도, 메모리 최적화
hidden class
여기서 정적 타이핑 언어와 달리 동적 타이핑 언어인 JS는 동적으로 타입이 변경되거나, 생성, 삭제될 수 있다.
정적 타이핑 언어는 컴파일 시에 데이터와 타입 등이 정의되므로 일부러 동적으로 생성하지 않는다면 바뀌는 일이 거의 없다.
그렇기 때문에 컴파일 시에 해당 데이터에 관련한 정보, 오프셋 값을 다른 곳에 저장해 두었다가 필요시 이를 사용하여 바로 접근할 수 있게 된다.
이와 다르게 동적 타이핑 언어는 컴파일 시에 값이 저장되더라도 해당 파일이 실행될 때 해당 데이터가 변경되어 오프셋 값을 참조할 수 없을 수 있다.
그렇기 때문에 해당 프로퍼티에 접근할 때마다 프로퍼티에 직접 접근하여 값을 참조하게 되고 이를 동적 탐색(Dynamic Lookup) 이라고 한다.
V8 엔진은 이와 같은 동적 탐색을 회피하기 위해 hidden class 를 사용한다.
hidden class는 객체가 생성될 때 생성되며 객체는 hidden class를 참조한다.
hidden class는 객체의 프로퍼티가 변환되면 새로운 Hidden class가 생성되며 해당 hidden class를 참조하게 되고,
기존 참조하고 있던 hidden class에는 해당 프로퍼티 변환시 새로운 hidden class를 참조한다는 정보를 가지게 된다.
예를 들어
var obj = {} // hidden class C0
obj.x = 1; // hidden class C1
obj.y = 2; // hidden class C2
obj 를 생성할 때 새로운 hidden class 가 생성되고 이를 C0이라고 하자.
x프로퍼티를 생성하게 되면 새로운 hidden class가 생성되고 이 hidden class를 C1이라고하자.
C1에는 x 프로퍼티에 대한 오프셋 값을 저장하고 있다.
C0에는 x 프로퍼티 추가 시 C1 hidden class를 가리키게 된다는 정보를 저장한다.
C2에는 y 프로퍼티에 대한 오프셋 값을 저장하고 있다.
C1에는 y 프로퍼티 추가 시 C2 hidden class를 가리키게 된다는 정보를 저장한다.
이런식으로 hidden class를 체이닝하여 동적 탐색을 회피한다.
function Person(name) {
this.name = name;
}
var kim = new Person("Kim");
var lee = new Person("Lee");
// kim.job = "developer";
이러한 코드가 있다면 해당 코드를 브라우저에서 스냅샷을 찍어 메모리를 확인해본다면 kim과 lee는 같은 hidden class를 참조하고 있음을 알 수 있는데 메모리 탭에서 Person을 검색해 본다면 map 에서 같은 숫자를 참조하고 있음을 할 수 있다.
메모리 탭에서 map은 hidden class를 가리키며 두 객체는 같은 hidden class를 참조하고 있음을 나타낸다.
그러나 마지막 주석 부분을 풀어 다시 스냅샷을 찍어 메모리탭을 확인해보면 서로 다른 hidden class를 참조하고 있음을 알 수 있다.
kim 객체에서 job 을 추가하면 job 프로퍼티를 추가하면 새로운 hidden class가 생성되고 해당 hidden class를 참조하게 되기 때문이다.
그러나 map 항목 아래에 transition을 확인해보면 lee의 hidden class가 내부적으로 kim의 hidden class를 참조하고 있음을 알 수 있다.
동작 방식
JS 실행 동작 방식을 좀 더 자세히 보자면
JS 코드가 올라가면 해당 코드에서
- Javascript 에 미리 정의되어 있는 for, const, if, function 같은 키워드
- 공백 이나 탭
- 변수 나 함수 식별자
위와 같은 것들을 Token이라고 하여 스캐너에서 Token을 생성한다.
해당 토큰을 가지고 parsing을 하여 추상구문트리(AST: abstract syntex tree)를 만든다.
해당 AST로 bytecode로 변환하여 실행하게 된다.
모든 파일이 컴파일되고 실행되는 것은 아니고,
스트리밍에서 도착하는 순서대로 chunk로 관리되고 30kB가 넘으면 parsing을 하게 된다.
그리고 파일을 실행할 때 자주 사용되는 코드는 bytecode로 캐싱(인라인 캐싱)해 두었다가 재사용하게 되는데,
이러한 방법으로 인터프리터의 속도를 개선했다. 이를 JIT(Just In Time) compiler 라고 한다.
feat. 웹 어셈블리
최근 브라우저에서는 html, css, js 뿐아니라 웹 어셈블리를 지원하며, .wasm 확장자를 갖는다.
웹 어셈블리에서 wasm은 하나의 확장자이다.
웹 어셈블리는 C++, Rust, go 등의 언어를 사용할 수 있으며,
JS와 다르게 정적 타이핑 언어로 컴파일된 파일을 사용한다.
그렇기 때문에 JS 실행과정에서의 parsing을 따로 해줄 필요 없기에 더 빠른 속도를 기대할 수 있다.
더하여 실행과정에서 브라우저는 turbofen 엔진으로 최적화 과정을 진행하는데,
hot 코드 (자주 사용하거나 반복되는 과정)을 저장하고, 타입 등으로 simple graph를 만들어 불필요한 코드 등을 제거하여 최적화 과정을 거치게 된다.
그러나 JS는 동적 타이핑 언어이고, 타입이 동적으로 변환되거나 한다면 해당 최적화 과정이 중단될 수 있다.
이와 반대로 웹어셈블리는 정적 타이핑 언어로 컴파일되어 만들어지기 때문에 최적화 과정이 중단되는 일이 거의 없다.
위와 같이 parsing 작업을 거치지 않고, 최적화 과정이 JS보다 보장되기 때문에 웹어셈블리를 이용하여 보다 안정적으로 빠른 속도를 기대할 수 있게 된다.
참고)
https://www.youtube.com/watch?v=VJag_H2Cosc
https://engineering.linecorp.com/ko/blog/v8-hidden-class/
'개발👩💻 > 프론트엔드' 카테고리의 다른 글
npm yarn yarn berry (0) | 2022.07.04 |
---|---|
이미지 최적화 및 성능 개선 (0) | 2022.07.04 |
vertical percentage bar 바닥부터 채우기 (0) | 2022.04.21 |
next: link # anchor 사용하기 (0) | 2022.03.31 |
react: intersection observer 사용기 (0) | 2022.03.31 |