본문으로 건너뛰기

개선된 코드 캐싱

· 약 4분
미스리 알레, 최고 코드 캐셔

V8은 코드 캐싱을 사용하여 자주 사용되는 스크립트의 생성된 코드를 캐싱합니다. Chrome 66부터 우리는 상위 레벨 실행 후 캐시를 생성하여 더 많은 코드를 캐싱하고 있습니다. 이는 초기 로드 시 파싱 및 컴파일 시간의 20–40% 감소로 이어집니다.

배경

V8은 나중에 재사용될 수 있도록 생성된 코드를 캐싱하기 위해 두 가지 종류의 코드 캐싱을 사용합니다. 첫 번째는 V8의 각 인스턴스 내에서 사용 가능한 메모리 내 캐시입니다. 초기 컴파일 후 생성된 코드는 소스 문자열을 키로 사용하여 이 캐시에 저장됩니다. 이는 동일한 V8 인스턴스 내에서 재사용 가능합니다. 두 번째는 생성된 코드를 직렬화하여 디스크에 저장하는 코드 캐싱입니다. 이 캐시는 특정 V8 인스턴스에 국한되지 않으며 서로 다른 V8 인스턴스 간에 사용할 수 있습니다. 이 블로그 글은 Chrome에서 사용되는 두 번째 코드 캐싱 종류에 초점을 맞춥니다. (다른 임베더들도 이러한 유형의 코드 캐싱을 사용하지만 Chrome에 제한되지는 않습니다. 그러나 이 블로그 글은 Chrome에서의 사용에만 초점을 둡니다.)

Chrome은 직렬화된 생성 코드를 디스크 캐시에 저장하며 스크립트 리소스의 URL을 키로 사용합니다. 스크립트를 로드할 때 Chrome은 디스크 캐시를 확인합니다. 스크립트가 이미 캐싱된 경우 Chrome은 컴파일 요청의 일부로 직렬화된 데이터를 V8에 전달합니다. V8은 스크립트를 파싱하고 컴파일하는 대신 이 데이터를 역직렬화합니다. 또한 코드가 여전히 사용 가능함을 확인하기 위해 추가적인 검사가 이루어집니다 (예: 버전 불일치로 인해 캐싱된 데이터가 사용 불가능해질 수 있음).

실제 데이터는 캐싱 가능한 스크립트의 코드 캐시 적중률이 높음(~86%)을 보여줍니다. 이러한 스크립트의 캐시 적중률은 높지만, 스크립트당 캐싱하는 코드의 양은 크지 않습니다. 우리의 분석에 따르면 캐싱되는 코드 양을 늘리면 JavaScript 코드를 파싱하고 컴파일하는 데 걸리는 시간을 약 40% 줄일 수 있습니다.

캐싱되는 코드 양 증가

이전 접근법에서는 코드 캐싱이 스크립트 컴파일 요청과 결합되어 있었습니다.

임베더는 V8이 새로운 JavaScript 소스 파일을 상위 레벨로 컴파일할 때 생성한 코드를 직렬화하도록 요청할 수 있었습니다. V8은 스크립트를 컴파일한 후 직렬화된 코드를 반환했습니다. Chrome이 동일한 스크립트를 다시 요청할 때, V8은 캐시에서 직렬화된 코드를 가져와 이를 역직렬화합니다. V8은 캐시에 이미 있는 함수를 다시 컴파일하지 않습니다. 이러한 시나리오는 다음 그림에서 보여집니다:

V8은 상위 레벨 컴파일 동안 즉시 실행될 것으로 예상되는 함수(IIFE)만 컴파일하고 다른 함수는 지연 컴파일로 표시합니다. 이는 필요하지 않은 함수를 컴파일하지 않아 페이지 로드 시간을 개선하는 데 도움이 되지만, 직렬화된 데이터에는 열심히 컴파일된 함수의 코드만 포함됩니다.

Chrome 59 이전에는 실행이 시작되기 전에 코드 캐시를 생성해야 했습니다. 이전의 V8 베이스라인 컴파일러(Full-codegen)는 실행 컨텍스트에 대해 맞춤화된 코드를 생성했습니다. Full-codegen은 특정 실행 컨텍스트에 대한 빠른 경로를 제공하기 위해 코드 패칭을 사용했습니다. 이러한 코드는 다른 실행 컨텍스트에서 사용하기 위해 컨텍스트 특정 데이터를 제거하여 쉽게 직렬화할 수 없었습니다.

Ignition의 출시로 인해 Chrome 59에서 이러한 제한이 더 이상 필요하지 않게 되었습니다. Ignition은 현재 실행 컨텍스트에서 작업을 빠르게 처리하기 위해 데이터 기반 인라인 캐시를 사용합니다. 컨텍스트 의존 데이터는 피드백 벡터에 저장되며 생성된 코드와는 별개로 저장됩니다. 이는 스크립트 실행 후에도 코드 캐시를 생성할 가능성을 열었습니다. 스크립트를 실행하면서 지연 컴파일로 표시된 더 많은 함수가 컴파일되어 더 많은 코드를 캐싱할 수 있게 됩니다.

V8는 새 API, ScriptCompiler::CreateCodeCache를 노출하여 컴파일 요청과 독립적으로 코드 캐시를 요청할 수 있도록 합니다. 컴파일 요청과 함께 코드 캐시를 요청하는 것은 더 이상 사용되지 않으며 V8 v6.6 이상에서 작동하지 않습니다. 버전 66 이후 크롬은 최상위 실행 후 이 API를 사용하여 코드 캐시를 요청합니다. 아래 그림은 코드 캐시 요청의 새로운 시나리오를 보여줍니다. 최상위 실행 이후 코드 캐시가 요청되며, 따라서 이후 스크립트 실행 중에 컴파일된 함수의 코드를 포함합니다. 이후 실행(아래 그림에서 더운 실행으로 표시됨)에서는 최상위 실행 중 함수의 컴파일을 피할 수 있습니다.

결과

이 기능의 성능은 내부 실제 환경 벤치마크를 사용하여 측정됩니다. 아래 그래프는 이전 캐싱 방식 대비 구문 분석 및 컴파일 시간의 감소를 보여줍니다. 대부분의 페이지에서 구문 분석 및 컴파일 시간 모두 약 20–40% 감소했습니다.

실제 환경 데이터는 데스크톱과 모바일에서 모두 JavaScript 코드 컴파일에 소요되는 시간이 20–40% 감소하는 유사한 결과를 보여줍니다. Android에서는 이 최적화가 상호작용 가능한 상태가 될 때까지 웹 페이지가 소요되는 시간과 같은 최상위 페이지 로드 메트릭에서 1–2% 감소로 이어지기도 합니다. 또한 크롬의 메모리 및 디스크 사용량을 모니터링한 결과 눈에 띄는 회귀는 발견되지 않았습니다.