V8 엔진 7.4 버전 출시


원문: https://v8.dev/blog/v8-release-74

우리는 릴리스 절차에 따라 6주마다 V8 엔진에 대한 새 브랜치를 만든다. 각 버전은 크롬 베타 버전의 마일스톤이 만들어지기 직전에 V8 엔진의 깃헙 마스터 브랜치에서 만들어진다. 우리는 오늘 새 브랜치인 V8 엔진 7.4 버전을 발표하게 되어 기쁘다. 이 버전은 몇 주 후에 크롬 74 안정화 버전과 함께 출시될 때까지 베타 버전을 유지한다. V8 엔진 7.4 버전은 개발자 지향의 기능들로 가득 차 있다. 이 글에서는 릴리스에 앞서 몇 가지 특징들을 미리 살펴본다.

JIT 없는 V8 엔진

이제 V8 엔진은 런타임에 실행 가능한 메모리를 할당하지 않고 자바스크립트를 실행할 수 있다. 이 기능에 관한 자세한 내용은 별도의 글에서 확인할 수 있다.

웹 어셈블리 스레드/어토믹스 지원

이제 안드로이드가 아닌 운영 체제에서도 웹 어셈블리 스레드/어토믹스(WebAssembly Threads/Atomics)를 사용할 수 있다. 이것으로 V8 엔진 7.0 버전부터 사용 가능했던 오리진 트라이얼/미리보기를 끝낸다. (역자주: 오리진 트라이얼(Origin Trials)) Web Fundamentals 글에서는 Emscripten으로 웹 어셈블리 어토믹스를 사용하는 방법에 대해 설명한다.

이것은 웹 어셈블리를 통해 사용자의 컴퓨터에서 여러 코어 사용을 차단하고, 웹에서 새롭고 계산이 많은 사용 사례를 구현할 수 있게 한다.

성능

인자가 일치하지 않는 호출에 대한 성능 향상

자바스크립트는 함수를 호출할 때 너무 적거나 많은(함수 선언부에 정의된 매개변수보다 많거나 적은) 매개변수를 사용해도 전혀 문제가 없다. 전자는 언더 애플리케이션(under-application), 후자는 오버 애플리케이션(over-application) 이라고 한다. 언더 애플리케이션의 경우, 남는 매개변수에 undefined 값이 할당된다. 그리고 오버 애플리케이션의 경우, 불필요한 매개변수가 무시된다.

그러나 자바스크립트 함수는 arguments 객체, 나머지 매개변수(rest parameter) 또는 느슨한 모드(sloppy mode)에서 비표준 Function.prototype.arguments 속성을 사용하여 실제 매개변수를 가져올 수 있다. 따라서 자바스크립트 엔진은 실제 매개변수를 가져올 방법을 제공해야 한다. V8 엔진에서는 아규먼트 어댑션(arguments adaption) 이라는 기술을 통해서 이러한 일을 처리한다. 아규먼트 어댑션은 언더 애플리케이션 또는 오버 애플리케이션에서 실제 매개변수를 제공한다. 안타깝게도 아규먼트 어댑션은 성능 비용이 발생하고, 최신 프론트엔드 및 미들웨어 프레임워크(선택적 매개변수 또는 가변 인자 목록을 가지는 많은 API)에서 아규먼트 어댑션을 필요로 한다.

호출 수신자(callee)가 스트릭트 모드(strict mode) 함수이고 arguments 및 나머지 매개변수를 사용하지 않으면 실제 매개변수가 관찰될 수 없기 때문에 엔진은 아규먼트 어댑션이 필요하지 않다는 것을 알 수 있다. 이 경우 V8 엔진은 아규먼트 어댑션을 전부 건너뛰어서 호출 오버헤드를 60% 까지 줄인다.

마이크로 벤치마크로 측정한 아규먼트 어댑션을 건너뛰는 경에 대한 성능 영향 비교

위 그래프는 V8 엔진 7.4 버전에서 인자가 일치하지 않을 때 더 이상 오버헤드가 없다는 것을 보여준다. 자세한 내용은 디자인 문서를 참고한다.

네이티브 접근자 성능 개선

앵귤러 팀은 크롬에서 네이티브 접근자(예: DOM 속성 접근자)의 get 함수 사용이 모노모픽(monomorphic) 또는 메가모픽(megamorphic) 속성 접근보다 현저하게 더 느린 것을 발견했다.

이는 V8 엔진에서 속성 접근자를 위해 이미 존재했던 빠른 경로(fast-path) 대신 Function#call()를 통한 DOM 접근자 호출을 위해 느린 경로(slow-path)를 택했기 때문이다.

우리는 네이티브 접근자가 메가모픽 접근자보다 훨씬 더 빨라지도록 성능을 개선했다. 더 자세한 내용은 V8 엔진 이슈 #8820을 참고하길 바란다.

파서 성능

크롬에서 용량이 큰 스크립트는 "스트리밍"(다운로드되는 동안 워커 스레드에서 파싱)된다. 이번 릴리스 버전에서는 원본 스트림(source stream)에서 사용되는 커스텀 UTF-8 디코딩을 통해 성능 문제를 확인하고 수정하였다. 그 결과로 스트리밍 파싱 속도가 평균 8% 정도 빨라졌다.

그리고 보통 워커 스레드에서 실행되는 V8 엔진 프리파서(preparser)의 추가적인 문제를 확인했다. 속성명이 불필요하게 중복 제거되는 문제가 있었다. 이러한 중복 제거를 없앰으로써 스트리밍 파서 성능이 10.5% 더 향상되었다. 또한 적은 스크립트와 인라인 스크립트처럼 스트리밍되지 않은 스크립트의 메인 스레드 파싱 시간을 향상시켰다.

위 차트에서 그래프 수치 떨어지는 각 구간은 스트리밍 파서에서 성능 개선 중 하나를 보여준다.

메모리

바이트코드 플러싱

자바스크립트 소스로 컴파일 된 바이트코드(bytecode)는 관련된 메타 데이터를 포함해 15% 정도의 V8 엔진 힙 공간을 차지하는 중요한 덩어리다. 초기화를 하는 동안에만 실행되거나 컴파일 된 이후 사용되지 않는 많은 함수들이 있다.

V8 엔진의 메모리 오버헤드를 줄일 수 있도록, 가비지 컬렉션이 실행되는 동안 함수로부터 최근에 실행되지 않은 컴파일 된 바이트코드를 플러싱(flushing)하도록 구현하였다. 함수의 바이트코드 수명 시간을 추적하고 가비지 컬렉션 중에 이 수명 시간을 증가시킨다. 그리고 함수가 실행될 때 수명 시간을 0으로 재설정한다. 수명 임계치를 초과한 바이트코드는 다음 가비지 컬렉터에 의해 수집될 수 있으며, 나중에 다시 실행되면 함수가 이 바이트코드를 지연시켜 다시 컴파일하기 위해 재설정한다.

우리의 실험은 바이트코드 플러싱이 크롬 사용자에게 상당한 메모리 절약 효과를 제공했음을 보여준다. 성능을 떨어뜨리지 않으면서 V8 엔진의 힙 메모리 용량을 5-15% 사이로 줄이거나 자바스크립트 코드 컴파일에 소요되는 CPU 시간을 크게 늘려준다.

바이트코드 죽은 기본 블럭 제거

이그니션(Ignition) 바이트코드 컴파일러는 return 또는 break문 뒤에 오는 코드와 같이 실행되지 않는 코드(일명 "죽은 코드")를 생성하지 않으려고 한다.

return;
deadCall(); // skipped

하지만 지금까지는 연속된 명령문을 도중에 중단하는 경우에만 이런 최적화가 사용되었기 때문에, 다음과 같이 항상 참(true)으로 평가되는 조건문 등은 고려되지 않았다.

if (2.2) return;
deadCall(); // not skipped

V8 엔진 7.3 버전에서 이 문제를 해결하려 했지만 명령문 단위 수준에서만 작동했고, 다음과 같이 복잡한 제어문이 포함되면 잘 동작하지 않았다.

do {
  if (2.2) return;
  break;
} while (true);
deadCall(); // not skipped

위 예제에서 deadCall()은 새로운 기본 블록의 시작점이 되는데, 왜냐하면 명령문 단위 수준에서 보면 반복문 내부의 break문 다음으로 실행될 코드이기 때문이다.

V8 엔진 7.4 버전에서는 어떤 Jump 바이트코드(이그니션의 주 제어 흐름 기본 단위)도 특정 기본 블록을 참조하지 않는다면 그 블록 전체를 죽은 것으로 간주할 수 있다. 위 예제에서 break는 실행되지 않았고, 이는 루프에 break문이 없음을 의미한다. 즉, deadCall()로 시작하는 기본 블럭은 참조하고 있는 Jump 바이트코드가 없으므로 죽은 것으로 간주된다. 우리는 이것이 사용자 코드에 큰 영향을 미치지 않을 것으로 예상하지만, 특별히 제너레이터와 for-of, try-catch와 같은 다양한 desugarings을 단순화하는 데는 유용할 것이다. 그리고 기본 블럭의 구현이 복잡한 구문의 부분을 "재구축(resurrect)"하는 버그류를 제거한다.

자바스크립트 언어 기능들

프라이빗 클래스 필드

V8 엔진 7.2 버전에서는 퍼블릭 클래스 필드 구문에 대한 지원이 추가되었다. 인스턴스의 속성을 정의하기 위해 생성자 함수를 사용하는 대신 클래스 필드를 사용해 클래스 구문을 단순화한다. V8 엔진 7.4 버전부터 접두사 #를 붙여 프라이빗 필드를 표시할 수 있다.

class IncreasingCounter {
  #count = 0;
  get value() {
    console.log('Getting the current value!');
    return this.#count;
  }
  increment() {
    this.#count++;
  }
}

퍼블릭 필드와 다르게 프라이빗 필드는 클래스 몸체 바깥에서 접근이 불가하다. (역자주: 인스턴스 생성 후 속성명으로 접근할 수 없다)

const counter = new IncreasingCounter();
counter.#count;
// → SyntaxError
counter.#count = 42;
// → SyntaxError

자세한 내용은 퍼블릭 및 프라이빗 클래스 필드에 대한 WebFu 글을 참고한다.

Intl.Locale

자바스크립트 애플리케이션은 보통 'en-US' 또는 'de-CH'와 같은 문자열을 사용해 로케일을 식별한다. Intl.Locale은 로케일을 다루는 강력한 메커니즘을 제공하며, 언어, 달력, 지역 넘버링 시스템, 시간 체계 등 로케일 별 설정 값을 쉽게 추출할 수 있다.

const locale = new Intl.Locale('es-419-u-hc-h12', {
  calendar: 'gregory'
});
locale.language;
// → 'es'
locale.calendar;
// → 'gregory'
locale.hourCycle;
// → 'h12'
locale.region;
// → '419'
locale.toString();
// → 'es-419-u-ca-gregory-hc-h12'

V8 API

git log branch-heads/7.3..branch-heads/7.4 include/v8.h 커맨드를 사용하면 변경된 API 목록을 받을 수 있다.

이미 V8 엔진 브랜치를 체크아웃 한 개발자들은 git checkout -b 7.4 -t branch-heads/7.4 커맨드로 V8 7.4 버전의 새로운 기능을 실험해볼 수 있다. 또는 크롬 베타 채널을 구독하면 새로운 기능들을 곧 사용해 볼 수 있다.


류선임, FE Development Lab2019.04.01Back to list