IE10 및 Windows 8에서 개선된 JavaScript 성능
2012년 5월 31일 우리는 Windows 8 Release Preview와 6번째 IE10 플랫폼 프리뷰를 발표했습니다. Windows 8에는 두 가지 브라우징 경험(Metro 스타일 및 데스크톱)을 모두 지원하는 HTML5 브라우징 엔진뿐 아니라, HTML5와 JavaScript 기반의 Metro 스타일 응용 프로그램도 포함되어 있습니다. Release Preview는 IE9에서 첫 선을 보였던 최신 JavaScript 엔진인 Chakra와 동일한 주 수정 버전을 지원합니다. 웹에서 뛰어난 성능을 제공하면서 호환성, 상호 운용성 및 보안 수준이 높은 엔진을 만들겠다는 우리의 목표는 매번 플랫폼 프리뷰를 거치면서 점점 가까워지고 있습니다. 이 글에서는 새로운 웹 응용 프로그램 시나리오에서 뛰어난 성능을 제공하기 위해 JavaScript 엔진이 어떻게 향상되었는지 살펴보겠습니다.
실제 웹 응용 프로그램의 성능
웹 응용 프로그램은 최근 몇 년간 급속도로 진화하고 있습니다. 10년 전에 웹은 블로그, 소규모 기업 방문 페이지 또는 Wikipedia와 같이 정적인 콘텐츠로 구성된 웹 사이트가 대부분을 차지했습니다. AJAX의 출현으로 Facebook 또는 JetSetter와 같은 좀 더 복잡한 대화형 사이트가 대거 등장하게 되었습니다. 그 후 Office 365, Bing 지도 등과 같은 크고 복잡한 응용 프로그램을 만들기 위해 성능이 차츰 개선되었습니다. 가장 최근에는 W3C 표준 API 확장, JavaScript 성능 향상, 하드웨어 가속 그래픽을 바탕으로 웹에서 Angry Birds, Pirates Love Daisies, Cut The Rope 등과 같은 정교한 게임도 제작할 수 있게 되었습니다.
응용 프로그램이 진화함에 따라 사용자 환경에 영향을 주는 성능 요인도 변화하게 되었습니다. 기존 웹 사이트의 경우 초기 페이지 로드에 따라 사용자가 콘텐츠를 얼마나 빨리 볼 수 있는지가 결정됩니다. 대화형 웹 사이트와 대규모 웹 응용 프로그램은 DOM 연산, CSS 처리 및 메모리 내 대규모 내부 상태 조작 효율성의 영향을 받을 수 있습니다. HTML5 게임은 빠른 Canvas 렌더링, JavaScript 시행 및 효율적인 가비지 수집에 의존하는 경우가 많습니다. 간단히 말해, 브라우저 성능은 다양한 응용 프로그램의 요구 사항을 고려해야 하는 복잡한 문제입니다.
이 글에서는 브라우저 하위 시스템인 JavaScript의 성능에 대해 집중적으로 살펴보겠습니다. 최근 JavaScript 성능이 향상된 덕분에 많은 웹 응용 프로그램에서 JavaScript를 실행하는 데 별다른 문제가 없습니다. 한편, 성능이 향상됨에 따라 JavaScript 엔진에 추가적인 요구 사항을 부여하는 새로운 시나리오가 출현하고 있습니다. 우리는 JavaScript를 많이 사용하는 실제 응용 프로그램의 성능 요구 사항에 맞게 Chakra를 발전시킬 수 있는 기회를 지속적으로 모색하고 있습니다.
웹 응용 프로그램의 성능 수준
Chakra의 내부
Chakra JavaScript 엔진은 IE9에 처음 도입되었을 때부터 두 가지 기본 원칙에 따라 디자인되었고, 이 원칙은 IE10에서도 똑같이 중요합니다.
- 첫 번째 원칙은 사용자 환경의 중요한 경로에 대한 작업량을 최소화하는 것입니다. 이 원칙은 가능한 한 많은 작업을 정말 필요할 때까지 연기하고, 작업이 모두 함께 작동하지 않도록 하며, 비활성 기간을 활용하고, 작업을 병렬 처리하여 응용 프로그램의 응답성에 미치는 영향을 최소화하는 것과 관련이 있습니다.
- 두 번째 원칙은 사용 가능한 모든 하드웨어를 활용하는 것입니다. 즉, 사용 가능한 모든 CPU 코어를 활용하고 특화된 고급 CPU 명령을 생성하는 것을 의미합니다(예: 사용 가능한 경우 Intel의 SSE2).
Chakra의 병렬 구조
Chakra는 브라우저 하위 시스템 중 하나일 뿐이지만 자체적으로 JavaScript 코드를 처리하고 실행하기 위해 함께 작동하는 여러 구성 요소를 갖추고 있습니다. 브라우저는 JavaScript 파일을 다운로드한 후 구문의 정확성을 확인하기 위해 Chakra의 파서에 파일의 내용을 전달합니다. 이는 전체 파일에 적용되는 유일한 작업입니다. 이후 단계는 전역 함수를 비롯한 각 함수에 대해 개별적으로 수행됩니다. 함수가 실행될 때(전역 함수는 구문 분석 후 즉시 실행됨) Chakra의 파서가 코드의 AST(추상적 구문 트리) 표현을 만들어 바이트코드 생성기로 전달하면 인터프리터가 실행하기에 적합한 중간 형태(바이트코드)가 생성됩니다(CPU가 직접 생성하지 않음). AST와 함수 바이트코드는 유지되므로 이후 실행 과정에서 다시 만들지 않아도 됩니다. 그런 다음 인터프리터가 호출되어 함수를 실행합니다. 인터프리터는 개별 작업을 실행하면서 발견한 입력 유형에 대한 정보(프로필)를 수집하고 함수가 호출된 횟수를 추적합니다.
호출 수가 특정 임계값에 도달하면 인터프리터가 컴파일할 함수를 대기열에 보관합니다. 다른 브라우저와 달리 Chakra의 JIT(Just-In-Time) 컴파일러는 별도의 전용 스레드에서 실행되므로 스크립트 실행을 방해하지 않습니다. 컴파일러의 단독 작업은 컴파일 대기열의 각 함수에 최적화된 기계어 명령을 생성하는 것입니다. 함수가 컴파일되면 기계어 코드의 사용 가능 여부가 주 스크립트 스레드에 전달됩니다. 다음에 호출할 때는 함수의 시작점이 새로 컴파일된 기계어 코드로 리디렉션되고 CPU에서 직접 실행이 진행됩니다. 한두 번만 호출된 함수는 실제로 컴파일되지 않으므로 시간과 리소스가 절약된다는 것이 중요합니다.
JavaScript는 관리되는 런타임으로, 메모리 관리를 개발자가 볼 수 없으며 자동 가비지 수집기에서 수행합니다. 자동 가비지 수집기는 더 이상 사용하지 않는 모든 개체를 정기적으로 정리하기 위해 실행됩니다. Chakra는 보수적인 준세대적 마크 및 정리 가비지 수집기를 사용합니다. 가비지 수집기는 대부분의 작업을 전용 스레드에서 동시에 수행하여 스크립트 실행 일시 정지로 인해 사용자 환경이 중지되는 횟수를 최소화합니다.
이러한 아키텍처는 Chakra가 페이지 로드 중에 거의 즉시 JavaScript 코드 실행을 시작할 수 있게 합니다. 한편 JavaScript를 많이 사용하는 작업 중에 Chakra는 작업을 병렬 처리하고 스크립트 실행, 컴파일 및 가비지 수집을 동시에 수행하여 최대 3개의 CPU 코어를 포화 상태로 만들 수 있습니다.
빠른 페이지 로드 시간
상대적으로 정적인 웹 사이트도 대화형 작업, 광고 또는 소셜 공유를 위해 JavaScript를 사용하는 경향이 있습니다. 실제로 Steve Souders의 HTTP Archive 보고서에 따르면 Alexa의 상위 백만 개 페이지에서 JavaScript의 규모가 꾸준히 증가하고 있는 것으로 나타났습니다.
Alexa의 상위 백만 개 페이지에서 JavaScript의 규모
이러한 웹 사이트에 포함된 JavaScript 코드는 브라우저의 JavaScript 엔진에 의해 처리되어야 하며, 각 스크립트 파일의 전역 함수가 실행되어야 콘텐츠가 완전히 렌더링될 수 있습니다. 따라서 중요한 경로에서 수행되는 작업량을 최소화하는 것이 중요합니다. Chakra의 파서와 바이트코드 인터프리터는 이러한 목표를 염두에 두고 디자인되었습니다.
바이트코드 인터프리터. 페이지 로드 중에 실행되는 JavaScript 코드는 초기화 및 설정을 수행하는 경우가 많으며, 한 번만 실행됩니다. 전체 페이지 로드 시간을 최소화하기 위해서는 Just-In-Time 컴파일러가 코드를 처리하고 기계어 명령을 생성할 때까지 기다리는 대신 이 코드를 즉시 실행해야 합니다. 인터프리터는 바이트코드로 변환되는 즉시 JavaScript 코드를 실행합니다. 처음으로 실행되는 명령의 시간을 더 줄이기 위해 Chakra는 지연형 구문 분석이라는 메커니즘을 사용하여 실행할 함수에 대해서만 바이트코드를 처리하고 생성합니다.
지연형 구문 분석. Microsoft Research의 JSMeter 프로젝트에 따르면 일반 웹 페이지에서는 다운로드한 코드에서 일반적으로 약 40-50% 정도만 사용합니다(오른쪽의 차트 참조). 다시 말해, 개발자는 jQuery 또는 dojo와 같이 많이 사용하는 JavaScript 라이브러리나 Office 365에서 사용된 것과 같은 사용자 지정 라이브러리를 자주 사용하지만 라이브러리가 지원하는 기능의 일부만 활용한다고 볼 수 있습니다.
이러한 시나리오를 최적화하기 위해 Chakra는 소스 코드에 대해 가장 기본적인 구문 전용 구문 분석을 수행합니다. 나머지 작업(추상 구문 트리 작성 및 바이트코드 생성)은 호출할 함수에 대해서만 한 번에 하나씩 수행됩니다. 이러한 전략은 웹 페이지를 로드할 때 브라우저의 응답성 향상에 도움이 될 뿐만 아니라 메모리 공간도 덜 차지합니다.
IE9에서는 Chakra의 지연형 구문 분석에 대한 한 가지 제한이 있었습니다. 다른 함수에 중첩된 함수는 바깥쪽 함수를 사용하여 즉시 구문을 분석해야 했습니다. 대부분의 라이브러리 코드가 즉시 실행되는 큰 함수로 묶이는 소위 "모듈 패턴"을 사용하기 때문에 이러한 제한은 중요했습니다. IE10에서는 이러한 제한 사항이 사라졌기 때문에 Chakra는 이제 즉시 실행되지 않는 함수의 구문 분석 및 바이트코드 생성을 연기합니다.
JavaScript를 많이 사용하는 응용 프로그램의 성능 강화
이전의 IE9와 마찬가지로 IE10에서도 실제 웹 응용 프로그램의 성능을 개선하기 위해 노력하고 있습니다. 그러나 웹 응용 프로그램은 정도에 차이는 있지만 JavaScript 성능에 의존합니다. IE10에서 향상된 기능에 대해 논의하려면 JavaScript를 많이 사용하는 이러한 응용 프로그램에 초점을 맞추는 것이 가장 효과적입니다. 응용 프로그램은 Chakra의 향상된 기능으로 상당한 성능상의 이점을 볼 수 있기 때문입니다. JavaScript를 많이 사용하는 중요한 응용 프로그램 부류로는 HTML5 게임 및 시뮬레이션이 있습니다.
우리는 IE10 개발을 시작하면서 사용자 환경에 가장 큰 영향을 주는 성능 개선 사항이 무엇인지 이해하기 위해 유명한 JavaScript 게임(예: Angry Birds, Cut the Rope 또는 Tankworld) 및 시뮬레이션(예: FishIE Tank, HTML5 Fish Bowl, Ball Pool, Particle System)의 샘플을 분석했습니다. 분석 결과 공통적인 특성과 코딩 패턴이 여러 개 발견되었습니다. 이러한 응용 프로그램은 모두 고빈도 타이머 콜백에 의해 구동됩니다. 대부분 렌더링에 Canvas를 사용하지만 일부는 움직이는 DOM 요소를 사용하고, 일부는 둘을 함께 사용합니다. 대부분의 응용 프로그램에서 코드는 최소한 일부분이라도 응용 프로그램 코드나 포함된 라이브러리와 같은 개체 지향 스타일로 작성되어 있습니다(예: Box2d.js). 빈번한 속성 읽기 및 쓰기, 다형성과 마찬가지로 짧은 함수가 일반적입니다. 모든 응용 프로그램이 부동 소수점 산술을 수행하고, 대다수는 상당한 양의 메모리를 할당하여 가비지 수집기에 부담을 줍니다. 이러한 공통적인 패턴은 IE10에 대한 성능 작업의 기준이 되었습니다. 다음 섹션에서는 이에 대한 응답으로 변경한 사항을 설명합니다.
Just-in-Time 컴파일러 - 재고 후 개선
IE10에서는 Chakra의 JIT 컴파일러의 기능이 현저하게 향상되었습니다. 추가 프로세서 아키텍처 x64 및 ARM에 대한 지원도 추가되었습니다. 따라서 사용자가 64비트 PC나 ARM 기반 태블릿에서 JavaScript 응용 프로그램을 사용하든 관계없이 JavaScript 응용 프로그램이 CPU에서 직접 실행되는 이점을 얻을 수 있습니다.
또한 기계어 코드 생성에 대한 기본적인 접근 방식도 변경되었습니다. JavaScript는 매우 동적인 언어이므로 코드를 생성할 때 컴파일러가 알 수 있는 정보가 제한적입니다. 예를 들어 아래 함수를 컴파일할 때 컴파일러는 관련 개체의 모양(속성 레이아웃)이나 속성의 형식을 모릅니다.
function compute(v, w) {
return v.x + w.y;
}
IE9에서 Chakra의 컴파일러는 런타임에 각 속성을 찾아 관련된 모든 연산을 처리했습니다(위의 예의 경우: 정수 덧셈, 부동 소수점 덧셈 또는 문자열 연결). 이러한 연산 중 일부는 기계어 코드에서 직접 처리되었지만 다른 연산은 Chakra 런타임의 도움이 필요했습니다.
IE10에서 JIT 컴파일러는 형식에 특화된 프로필 기반 기계어 코드를 생성합니다. 즉, 특정 모양의 개체와 특정 형식의 값에 맞는 기계어 코드를 생성합니다. 컴파일러는 적절한 코드를 생성하기 위해 예상되는 입력 값 형식을 알아야 합니다. JavaScript는 동적인 언어이므로 소스 코드에 이러한 정보가 없습니다. 우리는 런타임 중에 이러한 정보를 수집하기 위해 Chakra의 인터프리터를 개선했습니다. 이 기술을 동적 프로파일링이라고 합니다. 함수가 JIT 컴파일에 대해 예약되어 있는 경우 컴파일러는 인터프리터에 의해 수집된 런타임 프로필을 살펴보고 예상되는 입력에 맞는 코드를 생성합니다.
인터프리터는 관찰한 실행에 대한 정보를 수집하지만 프로그램 실행으로 인해 생성된 최적화 코드의 가정을 위반하는 런타임 값이 생성될 가능성이 있습니다. 컴파일러는 모든 가정에 대해 런타임 검사를 생성합니다. 이후 실행에서 예상치 못한 값이 생성될 경우 검사가 실패하고, 실행이 특화된 기계어 코드의 재귀 한도를 초과하고, 인터프리터에서 계속됩니다. 재귀 한도 초과(실패한 검사)의 이유가 기록되고, 인터프리터에 의해 추가 프로필 정보가 수집되고, 다른 가정을 사용하여 함수가 다시 컴파일됩니다. 재귀 한도 초과 및 재컴파일은 IE10의 완전히 새로운 기능입니다.
이로 인해 Chakra의 IE10 컴파일러가 코드에 대해 생성하는 기계어 명령이 감소하여 전체 메모리 공간이 축소되고 실행 속도가 빨라집니다. 이전에 설명한 HTML5 게임 및 시뮬레이션과 같이 부동 소수점 산술 및 개체 속성 액세스를 사용하는 앱에 특히 효과적입니다.
개체 지향 스타일의 JavaScript 코드를 작성하는 경우 Chakra의 함수 인라인 지원으로 이점을 얻을 수도 있습니다. 개체 지향 코드에는 일반적으로 비교적 작은 메서드가 많은 부분을 차지하고 있으므로 함수 호출 오버헤드가 함수 실행 시간에 비해 상당히 높습니다. 함수 인라인은 Chakra가 이러한 오버헤드를 줄일 수 있게 하지만 더 중요한 것은 루프 고정 코드 동작 또는 복사 전파와 같은 기존의 다른 컴파일러 최적화 범위를 크게 확장한다는 점입니다.
더욱 빠른 부동 소수점 산술
대부분의 JavaScript 프로그램은 어느 정도의 정수 산술을 수행합니다. 아래 예에서 볼 수 있듯이 산술에 많은 중점을 두지 않는 프로그램에서도 정수 값은 일반적으로 루프의 반복 변수 또는 배열에 대한 인덱스로 사용됩니다.
function findString(s, a) {
for (var i = 0, al = a.length; i < al; i++) {
if (a[i] == s) return i;
}
return -1;
}
한편 부동 소수점 산술은 일반적으로 게임, 시뮬레이션, 사운드, 이미지 또는 비디오 처리와 같은 특정 부류의 응용 프로그램으로 제한됩니다. 이전에는 JavaScript로 작성된 응용 프로그램이 극히 일부에 불과했지만 최근 브라우저 성능이 강화되면서 JavaScript 구현이 가능하게 되었습니다. IE9에서는 Chakra를 보다 일반적인 정수 연산에 최적화하였고, IE10에서는 부동 소수점 산술을 획기적으로 개선했습니다.
function compute(a, b, c, d) {
return (a + b) * (c − d);
}
위의 단순한 함수로 보았을 때 JavaScript 컴파일러는 소스 코드의 인수 a, b, c, d의 형식을 확인할 수 없습니다. IE9 컴파일러는 인수가 정수일 것이라고 가정하고 빠른 정수 기계어 명령을 생성했습니다. 실행할 때 인수가 실제로 정수인 경우에는 이 방식이 매우 효과적이었습니다. 대신 부동 소수점 숫자가 사용될 경우 코드는 Chakra의 런타임에서 상당히 느린 도우미 함수에 의존해야 했습니다. Chakra를 포함한 대부분의 32비트 JavaScript 엔진에서는 개별 부동 소수점 값이 힙에 할당되어야 하므로 힙에서 중간 값의 박싱 및 박싱 해제에 의해 함수 호출의 오버헤드는 한층 더 악화되었습니다. 위의 식에서는 각 연산의 결과를 얻기 위해 힙을 할당하고, 힙에 값을 저장하고, 다음 연산을 위해 힙에서 값을 검색해야 했습니다.
IE10에서는 컴파일러가 인터프리터에 의해 수집된 프로필 정보를 활용하여 부동 소수점 코드를 매우 빨리 생성합니다. 위의 예에서 프로필에 모든 인수가 부동 소수점 숫자일 가능성이 높다고 나와 있을 경우 컴파일러가 부동 소수점 기계어 명령을 생성합니다. 모든 인수가 이미 레지스트리에 있다고 가정할 때 전체 식은 단 3개의 기계어 명령으로 계산되고, 모든 중간 값이 레지스터에 저장되고, 최종 결과를 반환하기 위해 단 하나의 힙 할당만 필요합니다.
부동 소수점을 많이 사용하는 응용 프로그램의 경우 이로 인해 성능이 크게 향상됩니다. 실험 결과에 따르면 IE10 부동 소수점 연산의 실행 속도는 IE9보다 약 50% 빠른 것으로 나타났습니다. 또한 메모리 할당 비율이 감소했다는 것은 곧 가비지 수집이 줄어들었다는 것을 의미합니다.
더욱 빠른 개체 및 속성 액세스
JavaScript 개체는 논리적으로 관련된 값 집합을 그룹화할 수 있는 간편하고 널리 사용되는 메커니즘입니다. JavaScript 개체를 구조화된 개체 지향 프로그램 스타일로 사용하든, 값의 유연한 패키징으로만 사용하든 관계없이 IE10에 추가된 향상된 개체 할당 및 속성 액세스 성능은 코드에 큰 이점으로 작용합니다.
앞서 언급한 것처럼 JavaScript에서는 컴파일 과정에서 개체의 모양을 알 수 없으므로 효율적인 속성 액세스가 복잡합니다. JavaScript 개체는 사전 정의된 형식이나 클래스 없이 임시로 만들 수 있습니다. 새 속성을 원하는 순서대로 빠르게 개체에 추가하거나 개체에서 제거할 수 있습니다. 따라서 다음 메서드를 컴파일할 때 컴파일러는 Vector 개체에서 속성 x, y, z 속성 값을 어디에서 찾아야 할지 모릅니다.
Vector.prototype.magnitude = function() {
return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
}
IE9에서는 속성 액세스 속도를 현저하게 높여 주는 인라인 캐시가 도입되었습니다. 인라인 캐시는 특정 속성을 찾을 수 있는 개체의 메모리에 개체의 모양과 위치를 저장합니다. 인라인 캐시는 하나의 개체 모양만 저장할 수 있고 함수와 함께 작동하는 모든 개체의 모양이 동일한 경우에 잘 작동합니다. IE10에서는 여러 모양(다형성)의 개체에서 작동하는 코드의 성능을 향상시켜 주는 보조 캐시 메커니즘이 추가되었습니다.
속성 값을 읽으려면 컴파일러가 개체의 모양이 인라인 캐시에 저장된 것과 일치하는지 확인해야 합니다. IE9에서는 이를 위해 컴파일러가 모든 속성이 액세스하기 전에 런타임 모양 검사를 생성합니다. 프로그램은 아래 예와 같이 동일한 개체의 연속된 여러 속성을 읽거나 쓰는 경우가 많으므로 이러한 모든 검사는 오버헤드를 추가합니다.
function collide(b1, b2) {
var dx = b1.x - b2.x;
var dy = b1.y - b2.y;
var dvx = b1.vx - b2.vx;
var dvy = b1.vy - b2.vy;
var distanceSquare = (dx * dx + dy * dy) || 1.0;
//...
}
IE10은 Chakra에서 예상한 개체 모양에 맞는 코드를 생성합니다. 새 컴파일러는 재귀 한도 초과 및 재컴파일 기능과 결합된 정확한 기호 추적을 통해 런타임 모양에 대한 검사 수를 획기적으로 줄입니다. 위의 예에서는 개별 모양 검사가 8번 수행되지 않고 b1과 b2에 각각 한 번씩 두 번 수행되었습니다. 또한 개체의 모양이 설정되면 모든 속성 위치가 알려지고 읽기 또는 쓰기 작업이 C++와 같이 효율적으로 수행됩니다.
ECMAScript 5에서는 개체에 접근자 속성이라는 새로운 유형의 속성이 포함되어 있을 수 있습니다. 접근자 속성은 사용자 지정 get 및 set 함수가 호출되어 쓰기 및 읽기 작업을 처리한다는 점에서 기존의 데이터 속성과 다릅니다. 접근자 속성은 데이터 캡슐화, 계산된 속성, 데이터 유효성 검사 또는 변경 알림을 추가하기 위한 간편한 메커니즘입니다. Chakra의 내부 형식 시스템 및 인라인 캐시는 접근자 속성을 지원하고 속성 값의 효율적인 읽기 및 쓰기를 촉진하도록 디자인되었습니다.
HTML5 게임 또는 애니메이션을 작성하는 경우 중력이 적용된 상태에서 개체의 실감나는 이동을 생성하고, 충돌을 시뮬레이션하는 등의 용도로 필요한 계산을 수행하는 물리적 엔진이 필요한 경우가 많습니다. 이러한 물리적 엔진이 매우 단순한 경우 사용자가 직접 만들 수도 있지만 요구 사항이 좀 더 복잡한 경우 일반적으로 Box2d.js( Box2d에서 포팅됨)와 같이 JavaScript에서 사용할 수 있는 일반적인 물리적 라이브러리 중 하나를 사용합니다. 이러한 라이브러리는 Point, Vector 또는 Color와 같은 작은 개체를 사용하는 경우가 많습니다. 각 애니메이션 프레임에서 이러한 개체가 다수 만들어졌다가 즉시 버려집니다. 따라서 JavaScript 런타임 중에 개체를 효율적으로 만드는 것이 중요합니다.
var Vector = function(x, y, z) {
this.x = x;
this.y = y;
this.z = z;
}
Vector.prototype = {
//...
normalize : function() {
var m = Math.sqrt((this.x * this.x) + (this.y * this.y) + (this.z * this.z));
return new Vector(this.x / m, this.y / m, this.z / m);
},
add : function(v, w) {
return new Vector(w.x + v.x, w.y + v.y, w.z + v.z);
},
cross : function(v, w) {
return new Vector(-v.z * w.y + v.y * w.z, v.z * w.x - v.x * w.z, -v.y * w.x + v.x * w.y);
},
//...
}
IE10에서 JavaScript 개체의 내부 레이아웃은 개체 생성을 간소화하기 위해 최적화되어 있습니다. IE9에서는 모든 개체가 고정 크기 헤더와 확장 가능한 속성 배열로 구성되어 있었습니다. 확장 가능한 속성 배열은 개체가 생성된 후 추가될 수 있는 다른 속성을 지원하기 위해 필요합니다. 모든 JavaScript 응용 프로그램이 이러한 유연성을 활용하는 것은 아니며 개체는 구성 단계에서 대부분의 속성을 수신하는 경우가 많습니다. 이러한 특성으로 인해 Chakra는 헤더를 사용하여 직접 이러한 개체의 속성을 대부분 할당하므로 새로 생성된 각 개체당 메모리 할당이 한 번만 발생합니다(두 번 아님). 이러한 변화로 인해 개체의 속성을 읽거나 쓰는 데 필요한 메모리 역참조 수가 줄어들고 레지스터 사용률이 개선됩니다. 향상된 개체 레이아웃과 감소한 런타임 모양 검사를 통해 속성 액세스 속도가 최대 50%까지 증가하게 됩니다.
가비지 수집 기능 향상
앞서 설명한 것처럼 HTML5 게임과 애니메이션은 개체를 만들었다가 버리는 비율이 굉장히 높습니다. JavaScript 프로그램은 취소된 개체를 명시적으로 제거하여 메모리를 확보하지 않습니다. 대신 엔진의 가비지 수집기를 사용하여 사용하지 않는 개체가 차지하는 메모리를 정기적으로 확보하여 새로운 개체를 위한 공간을 만듭니다. 자동 가비지 수집 기능으로 인해 훨씬 편리하게 프로그래밍 작업을 수행할 수 있지만 일반적으로 수집기가 작업을 수행할 때 JavaScript 실행을 가끔 일시 중지해야 합니다. 수집기가 오랫동안 실행되면 전체 브라우저가 응답하지 않을 수 있습니다. HTML5 게임에서는 애니메이션의 작은 문제도 사용자가 인지할 수 있으므로 짧은 일시 중지(10000분의 1초)도 지장을 초래합니다.
IE10에서는 메모리 할당자와 가비지 수집기의 여러 부분이 개선되었습니다. 메모리 할당을 낮추는 개체 레이아웃 변경 사항과 부동 소수점 산술에 특화된 기계어 코드의 생성에 대해서는 이미 설명했습니다. 이외에도 Chakra는 이제 별도의 메모리 공간에서 리프 개체(예: 숫자 및 문자열)를 할당합니다. 리프 개체에는 다른 개체에 대한 포인터가 없으므로 가비지 수집 시 일반 개체처럼 주의할 필요가 없습니다. 별도의 공간에서 리프 개체를 할당할 경우 두 가지 이점이 있습니다. 첫째, 마크 단계에서 전체 공간을 건너뛸 수 있어 기간이 단축됩니다. 둘째, 동시 수집 시 리프 개체 공간에서 새로 할당할 때 영향을 받는 페이지를 다시 검사할 필요가 없습니다. Chakra의 수집기는 주 스크립트 스레드와 동시에 작동하므로 실행 중인 스크립트가 이미 처리된 페이지의 새 개체를 수정하거나 만들 수 있습니다. Chakra는 이러한 개체가 너무 일찍 수집되지 않도록 마크 단계가 시작되기 전에 페이지에 쓰기 금지를 설정합니다. 마크 단계에서 데이터가 기록된 페이지는 나중에 주 스크립트 스레드에서 다시 검사해야 합니다. 리프 개체를 사용할 경우 이러한 처리가 필요하지 않으므로 리프 개체 공간의 페이지는 쓰기 금지를 설정하거나 나중에 다시 검사할 필요가 없습니다. 따라서 주 스크립트 스레드의 소중한 시간이 절약되고 일시 중지가 줄어듭니다. HTML5 게임과 애니메이션은 부동 소수점 숫자와 관련된 작업량이 많고 할당됨 메모리의 대부분을 힙 박싱 숫자에 할애하므로 이러한 변경 사항으로 인해 큰 이점을 얻을 수 있습니다.
사용자가 웹 응용 프로그램과 직접 상호 작용하는 경우 응용 프로그램의 코드가 가능한 한 빨리 실행되는 것이 중요합니다. 가비지 수집이 중단되지 않는다면 더할 나위 없이 좋을 것입니다. 그러나 사용자가 브라우저에서 전환하거나 탭을 변경하는 경우에도 현재 비활성 상태의 사이트나 응용 프로그램의 메모리 공간을 축소하는 것이 중요합니다. 이런 이유에서 충분한 메모리가 할당된 경우 IE9 Chakra에서 기존의 JavaScript 코드에 대한 수집을 트리거했습니다. 이는 대부분의 응용 프로그램에서 매우 효과적이지만 HTML5 게임 및 애니메이션과 같이 고빈도 타이머에 의해 구동되는 응용 프로그램에서는 문제가 발생하는 것으로 나타났습니다. 이러한 응용 프로그램의 경우 수집이 너무 자주 트리거되어 프레임 손실과 사용자 환경의 전반적인 수준 저하를 유발했습니다. 아마도 이러한 문제가 가장 분명히 나타난 것은 Tankworld 게임이었을 것입니다. 하지만 다른 HTML5 시뮬레이션에서도 빈번한 가비지 수집으로 인해 애니메이션 일시 중지가 나타났습니다.
IE10에서는 브라우저의 나머지 부분과 가비지 수집을 조정하여 이러한 문제를 해결했습니다. Chakra는 이제 스크립트 실행이 끝나면 가비지 수집을 연기하고 스크립트가 일정 시간 동안 비활성화되면 브라우저에서 콜백을 요청합니다. 스크립트가 실행되기 전에 이 시간이 경과되면 Chakra가 수집을 시작하고, 그렇지 않을 경우 수집이 더 연기됩니다. 이러한 기술을 사용하면 브라우저(또는 브라우저 탭 중 하나)가 비활성화되었을 때 메모리 공간을 축소함과 동시에 애니메이션 기반 응용 프로그램의 수집 빈도를 크게 낮출 수 있습니다.
HTML5 시뮬레이션에서 측정한 바에 따르면 위와 같은 변경 사항으로 주 스레드에서 가비지 수집에 소요되는 시간이 평균 4배 감소했습니다. JavaScript 실행 시간에 대한 비율로 계산할 경우 가비지 수집 시간은 약 27%에서 약 6%로 감소했습니다.
요약
IE10은 JavaScript를 많이 사용하는 응용 프로그램, 그 중에서도 특히 HTML5 게임 및 시뮬레이션에서 성능이 크게 향상되었습니다. JIT 컴파일러의 새로운 기본 기능부터 가비지 수집기의 변경 사항까지 Chakra에서 개선된 다양한 주요 기능 때문입니다.
IE10 개발을 마무리하면서 우리가 이루어낸 성과를 자축했지만, 성능이라는 것은 영원한 숙제임을 잘 알고 있습니다. 거의 매일 새로운 응용 프로그램이 출현하여 최신 브라우저와 JavaScript 엔진의 한계를 시험하고 있습니다. 틀림없이 다음 릴리스에서 개선할 부분이 많을 것 같습니다.
우리는 JavaScript 개발자의 의견을 듣고 싶습니다. IE10의 새로운 기능과 향상된 성능이 사용자 경험을 완전히 새로운 차원으로 끌어올렸거나 기존의 응용 프로그램을 개선하는 데 도움이 되었다면 우리에게 알려주시기 바랍니다. IE의 성능 한계를 경험한 경우에도 알려주시기 바랍니다. 우리는 이 블로그에 올라오는 모든 댓글을 꼼꼼히 살펴보고 있으며 IE10과 Windows 8을 가장 포괄적이고 성능이 뛰어난 응용 프로그램 플랫폼으로 만들기 위해 노력하고 있습니다.
- JavaScript 프로그램 관리자, Andrew Miadowicz