개요[편집 / 원본 편집]
JavaScript Engine
자바스크립트 코드를 해석하고 실행하는 소프트웨어 컴포넌트이다. 브라우저들의 성능 경쟁의 핵심이자 개발자들의 최적화 고민거리
자바스크립트가 처음 등장했을 때만 해도 단순한 인터프리터 방식으로 작동했지만, 현재는 JIT 컴파일러부터 시작해서 온갖 최적화 기법들이 적용된 복잡한 시스템이 되었다. 옛날엔 간단했는데
역사[편집 / 원본 편집]
초창기[편집 / 원본 편집]
1995년 넷스케이프의 브렌던 아이크가 10일 만에 만든 자바스크립트[1]를 실행하기 위해 최초의 자바스크립트 엔진이 개발되었다. 당시에는 SpiderMonkey라는 이름도 없었고, 그냥 넷스케이프 내비게이터에 내장된 인터프리터였다.
브라우저 전쟁 시대[편집 / 원본 편집]
- 1997년: 마이크로소프트가 인터넷 익스플로러 4.0에 JScript 엔진을 탑재
- 1998년: 넷스케이프가 SpiderMonkey를 오픈소스로 공개
- 2001년: 맥OS용 사파리 브라우저에 JavaScriptCore 엔진 탑재
이 시기의 자바스크립트 엔진들은 솔직히 말해서 성능이 그리 좋지 않았다. 웹페이지에서 간단한 상호작용 정도만 처리하면 충분했기 때문이다.
성능 혁명 시대[편집 / 원본 편집]
V8의 등장[편집 / 원본 편집]
2008년, 구글이 크롬 브라우저와 함께 V8 엔진을 출시하면서 자바스크립트 엔진의 패러다임이 완전히 바뀌었다. 구글이 또 뭔가 해버렸다
V8의 혁신적인 특징:
- 히든 클래스(Hidden Classes): 동적 언어인 자바스크립트를 정적 언어처럼 최적화
- 인라인 캐싱(Inline Caching): 프로퍼티 접근을 빠르게 처리
- 가비지 컬렉션 최적화: 세대별 가비지 컬렉션으로 메모리 관리 효율화
경쟁 가속화[편집 / 원본 편집]
V8의 성공 이후 다른 브라우저 업체들도 가만히 있지 않았다.
- 2009년: 모질라가 SpiderMonkey에 TraceMonkey JIT 컴파일러 추가
- 2010년: 애플이 JavaScriptCore에 SquirrelFish Extreme 엔진 도입
- 2011년: 마이크로소프트가 Chakra 엔진으로 대응
주요 자바스크립트 엔진들[편집 / 원본 편집]
V8[편집 / 원본 편집]
구글에서 개발한 오픈소스 자바스크립트 엔진. 구글의 모든 것을 집어삼키려는 야욕의 산물
특징[편집 / 원본 편집]
- C++로 작성됨
- 크롬, Node.js, Electron 등에서 사용
- 하이브리드 컴파일 방식: 인터프리터 + JIT 컴파일러
- WebAssembly 지원
내부 구조[편집 / 원본 편집]
- Ignition: 바이트코드 인터프리터
- TurboFan: 최적화 컴파일러
- Liftoff: WebAssembly 컴파일러
- Orinoco: 병렬/동시 가비지 컬렉터
이름들이 왜 이렇게 멋있는지 모르겠다
SpiderMonkey[편집 / 원본 편집]
모질라에서 개발하는 자바스크립트 엔진. 최초의 자바스크립트 엔진이라는 역사적 의미가 있다.
특징[편집 / 원본 편집]
JavaScriptCore (WebKit)[편집 / 원본 편집]
애플에서 개발한 자바스크립트 엔진. 애플답게 클로즈드 소스인 줄 알았는데 의외로 오픈소스다
특징[편집 / 원본 편집]
- 사파리에서 사용
- Nitro라고도 불림
- 4단계 JIT 컴파일 파이프라인:
- LLInt (Low Level Interpreter)
- Baseline JIT
- DFG (Data Flow Graph)
- FTL (Faster Than Light)
이름 센스
Chakra[편집 / 원본 편집]
마이크로소프트에서 개발한 자바스크립트 엔진들.
Chakra (Legacy)[편집 / 원본 편집]
- 인터넷 익스플로러 9+와 Microsoft Edge Legacy에서 사용
- 현재는 개발 중단
인터넷 익스플로러와 함께 역사의 뒤안길로
ChakraCore[편집 / 원본 편집]
- Chakra의 오픈소스 버전
- Node.js 대안 런타임인 Node-ChakraCore에서 사용되었으나 현재는 중단
기타 엔진들[편집 / 원본 편집]
- Hermes: Facebook에서 개발한 모바일 최적화 엔진
- QuickJS: 경량화된 임베디드용 엔진
- Duktape: IoT 기기용 초경량 엔진
- Rhino: 자바 기반 자바스크립트 엔진
코뿔소
작동 원리[편집 / 원본 편집]
기본 실행 과정[편집 / 원본 편집]
- 소스 코드 파싱 → AST (Abstract Syntax Tree) 생성
- 바이트코드 생성 (옵션)
- 인터프리터 실행 또는 컴파일러 실행
- 최적화 및 재컴파일
복잡해 보이지만 실제로는 더 복잡하다
파싱 단계[편집 / 원본 편집]
어휘 분석 (Lexical Analysis)[편집 / 원본 편집]
소스 코드를 토큰들로 분해하는 과정이다.
var x = 42;위 코드는 다음과 같은 토큰들로 분해된다:
- `var` (키워드)
- `x` (식별자)
- `=` (연산자)
- `42` (숫자 리터럴)
- `;` (구분자)
구문 분석 (Syntax Analysis)[편집 / 원본 편집]
토큰들을 이용해 AST를 생성한다. 이 과정에서 문법 오류가 발견되면 SyntaxError가 발생한다.
`Unexpected token` 에러의 원흉
실행 단계[편집 / 원본 편집]
인터프리터 방식[편집 / 원본 편집]
AST를 직접 순회하면서 실행하는 방식이다. 초기 자바스크립트 엔진들이 사용했던 방식.
장점:
- 구현이 간단
- 메모리 사용량이 적음
- 빠른 시작 시간
단점:
- 실행 속도가 느림
- 반복 실행 시 매번 파싱해야 함
바이트코드 방식[편집 / 원본 편집]
AST를 중간 언어인 바이트코드로 컴파일한 후 실행하는 방식.
장점:
- 인터프리터보다 빠름
- 메모리 효율적
- 포터빌리티 좋음
단점:
- 네이티브 코드보다는 느림
- 추가 컴파일 단계 필요
JIT 컴파일 방식[편집 / 원본 편집]
실행 중에 네이티브 머신 코드로 컴파일하는 방식. 현대 자바스크립트 엔진의 핵심 기술.
Just-In-Time 컴파일의 특징:
- 핫스팟 (자주 실행되는 코드) 감지
- 프로파일링 정보 수집
- 추측 최적화 (Speculative Optimization)
- 탈최적화 (Deoptimization)
최적화했다가 다시 되돌리기
최적화 기법들[편집 / 원본 편집]
히든 클래스 (Hidden Classes)[편집 / 원본 편집]
자바스크립트는 동적 타입 언어이지만, 실제로는 비슷한 구조의 객체들이 많이 생성된다. 엔진은 이를 활용해 히든 클래스를 생성하여 프로퍼티 접근을 빠르게 만든다.
function Point(x, y) {
this.x = x; // 히든 클래스 변경
this.y = y; // 히든 클래스 변경
}
var p1 = new Point(1, 2);
var p2 = new Point(3, 4); // p1과 같은 히든 클래스 재사용똑같은 구조면 굳이 매번 새로 만들 필요가 있나
인라인 캐싱 (Inline Caching)[편집 / 원본 편집]
프로퍼티나 메서드에 접근할 때의 결과를 캐시해두는 기법이다.
처음 `obj.property` 접근:
- 객체의 히든 클래스 확인
- 프로퍼티 위치 검색
- 결과를 캐시에 저장
두 번째 접근부터:
- 히든 클래스가 같은지 확인
- 캐시된 위치에서 바로 접근
한 번 찾은 건 기억해두자
함수 인라이닝 (Function Inlining)[편집 / 원본 편집]
작은 함수들을 호출하는 대신 코드를 직접 삽입하는 최적화.
function add(a, b) {
return a + b;
}
function calculate() {
return add(1, 2) + add(3, 4); // 인라이닝 후: return (1 + 2) + (3 + 4);
}루프 최적화[편집 / 원본 편집]
루프 불변 코드 이동[편집 / 원본 편집]
루프 내부의 변하지 않는 계산을 루프 밖으로 이동시킨다.
// 최적화 전
for (let i = 0; i < arr.length; i++) {
process(arr[i] * Math.PI); // Math.PI는 상수
}
// 최적화 후
const pi = Math.PI;
for (let i = 0; i < arr.length; i++) {
process(arr[i] * pi);
}루프 언롤링[편집 / 원본 편집]
루프의 반복 횟수를 줄이고 대신 한 번에 여러 작업을 수행한다.
컴파일러들의 오래된 트릭
타입 추론[편집 / 원본 편집]
자바스크립트는 동적 타입이지만, 실행 중에 변수의 타입을 추론해서 최적화한다.
function add(a, b) {
return a + b; // 숫자로 추론되면 정수 덧셈으로 최적화
}
add(1, 2); // 정수 덧셈
add(1.5, 2.3); // 부동소수점 덧셈
add("a", "b"); // 문자열 연결 (탈최적화!)타입이 바뀌면 최적화가 물거품이 된다
메모리 관리[편집 / 원본 편집]
가비지 컬렉션[편집 / 원본 편집]
자바스크립트는 자동 메모리 관리 언어이다. 개발자가 직접 메모리를 해제할 필요가 없다. C/C++ 개발자들이 부러워하는 부분
참조 카운팅 (Reference Counting)[편집 / 원본 편집]
초기 자바스크립트 엔진들이 사용했던 방식. 객체를 참조하는 변수의 개수를 세어서 0이 되면 메모리를 해제한다.
문제점: 순환 참조 상황에서 메모리 누수 발생
function leak() {
var a = {};
var b = {};
a.ref = b;
b.ref = a; // 순환 참조! 해제되지 않음
}마크 앤 스위프 (Mark and Sweep)[편집 / 원본 편집]
현재 대부분의 엔진이 사용하는 방식.
- 마크 단계: 루트에서 접근 가능한 모든 객체에 마크
- 스위프 단계: 마크되지 않은 객체들을 메모리에서 해제
좀비 게임처럼 표시하고 쓸어버리기
세대별 가비지 컬렉션[편집 / 원본 편집]
대부분의 객체는 생성된 후 금방 사용되지 않게 된다는 약한 세대 가설을 기반으로 한 최적화.
- Young Generation: 새로 생성된 객체들
- Old Generation: 오래 살아남은 객체들
Young Generation을 더 자주 검사하여 효율성을 높인다.
메모리 누수 방지[편집 / 원본 편집]
자동 메모리 관리라고 해서 메모리 누수가 아예 없는 것은 아니다.
전역 변수[편집 / 원본 편집]
// 나쁜 예
var globalLeak = new Array(1000000); // 전역 변수는 해제되지 않음
// 좋은 예
function useLocalArray() {
var localArray = new Array(1000000); // 함수 종료 후 해제 가능
// ... 사용 ...
}이벤트 리스너[편집 / 원본 편집]
// 나쁜 예
element.addEventListener('click', handler);
// element가 DOM에서 제거되어도 handler가 메모리에 남아있을 수 있음
// 좋은 예
element.addEventListener('click', handler);
element.removeEventListener('click', handler); // 명시적 해제클로저[편집 / 원본 편집]
function createLeak() {
var largeData = new Array(1000000);
return function() {
// largeData를 직접 사용하지 않아도 클로저로 인해 메모리에 남아있음
console.log('Hello');
};
}클로저는 양날의 검
성능 측정 및 프로파일링[편집 / 원본 편집]
벤치마킹 도구들[편집 / 원본 편집]
V8 Benchmark Suite[편집 / 원본 편집]
구글에서 만든 자바스크립트 성능 측정 도구. V8이 가장 잘 나올 수밖에 없는 구조
Octane[편집 / 원본 편집]
복잡한 웹 애플리케이션의 성능을 측정하는 벤치마크.
JetStream[편집 / 원본 편집]
WebKit 팀에서 개발한 종합적인 자바스크립트 벤치마크.
Speedometer[편집 / 원본 편집]
실제 웹 애플리케이션과 유사한 환경에서의 성능을 측정.
브라우저 개발자 도구[편집 / 원본 편집]
Performance 탭[편집 / 원본 편집]
- CPU 프로파일링
- 메모리 사용량 추적
- 가비지 컬렉션 이벤트 표시
개발자 도구 없으면 디버깅이 불가능하다
Memory 탭[편집 / 원본 편집]
- 힙 스냅샷 촬영
- 메모리 누수 탐지
- 객체 참조 관계 분석
Node.js 프로파일링[편집 / 원본 편집]
# CPU 프로파일링
node --prof app.js
# 메모리 사용량 추적
node --trace-gc app.js
# V8 옵션 확인
node --v8-options임베디드 시스템에서의 활용[편집 / 원본 편집]
IoT 기기[편집 / 원본 편집]
최근에는 IoT 기기에서도 자바스크립트가 사용되고 있다.
제약사항[편집 / 원본 편집]
- 메모리 제한 (보통 수십 KB ~ 수 MB)
- CPU 성능 제한
- 전력 소비 제한
최적화 전략[편집 / 원본 편집]
- AOT (Ahead-of-Time) 컴파일
- 코드 크기 최소화
- JIT 컴파일러 제거
게임 엔진[편집 / 원본 편집]
- Unity - UnityScript (현재는 C# 전환)
- Unreal Engine - 블루프린트와 함께 자바스크립트 지원
- Godot - GDScript (자바스크립트 유사 문법)
게임에서 자바스크립트를 쓴다니 격세지감