맨위로가기

JIT 컴파일

"오늘의AI위키"는 AI 기술로 일관성 있고 체계적인 최신 지식을 제공하는 혁신 플랫폼입니다.
"오늘의AI위키"의 AI를 통해 더욱 풍부하고 폭넓은 지식 경험을 누리세요.

1. 개요

JIT 컴파일(Just-In-Time compilation)은 프로그램 실행 중에 바이트코드를 기계어로 변환하는 기술이다. 1960년대 리스프 연구에서 처음 개념이 등장했으며, 이후 스몰토크, 자바 등 다양한 프로그래밍 언어와 환경에서 활용되었다. JIT 컴파일은 실행 환경에 맞춰 코드를 최적화하고, 런타임 시점의 정보를 활용하여 성능을 향상시킬 수 있지만, 초기 실행 시 지연(웜업 시간)이 발생할 수 있으며, 보안 취약점을 유발할 수 있다는 단점도 존재한다. 현재 자바 가상 머신, .NET, PHP 등에서 사용되며, 정규 표현식, 에뮬레이터 등 다양한 분야에서 활용된다.

더 읽어볼만한 페이지

  • 에뮬레이션 소프트웨어 - 에뮬레이터
    에뮬레이터는 다른 프로그램이나 장치를 모방하는 컴퓨터 프로그램 또는 전자기기의 능력으로, 하드웨어 및 소프트웨어 모방을 포함하며 디지털 아카이빙과 뉴 미디어 아트 분야에서 중요한 역할을 한다.
  • 에뮬레이션 소프트웨어 - 시그윈
    Cygwin은 윈도우에서 유닉스 시스템과 유사한 환경을 제공하는 소프트웨어로, POSIX API 구현 및 다양한 개발 도구를 통해 유닉스 기반 소프트웨어의 개발, 빌드, 실행을 지원한다.
  • 컴파일러 구성 - 구문 분석
    구문 분석은 입력 데이터를 구조화된 형태로 변환하는 과정으로, 컴퓨터 언어에서는 소스 코드를 분석하여 추상 구문 트리를 생성하고, 자연어 처리에서는 텍스트의 문장 구조와 의미를 분석한다.
  • 컴파일러 구성 - 바이너리 재컴파일러
  • 가상화 소프트웨어 - X86 가상화
    X86 가상화는 x86 아키텍처 기반 시스템에서 가상 머신을 구현하는 기술로, 소프트웨어 기반 가상화와 하드웨어 지원 가상화로 나뉘며, CPU 제조사의 가상화 확장 기술을 활용하여 가상 머신의 성능을 향상시킨다.
  • 가상화 소프트웨어 - VM웨어 v스피어
    VM웨어 v스피어는 VM웨어에서 개발한 서버 가상화 플랫폼으로, 다양한 운영체제 지원, 가상 시스템 확장, vMotion 개선, 스토리지 관리 효율성 향상 등의 특징을 가지며 클라우드 환경 지원 강화 및 관리 효율성 증대에 초점을 맞추어 지속적으로 업데이트되고 있다.
JIT 컴파일
개요
유형컴파일러
컴파일 시점런타임
목표성능 향상, 플랫폼 독립성
작동 방식
기본 원리런타임에 바이트코드를 기계어로 번역
최적화 기법인라인 확장
루프 최적화
죽은 코드 제거
장점 및 단점
장점빠른 실행 속도
플랫폼 독립성 유지
동적 최적화 가능
단점초기 컴파일 시간 필요
메모리 사용량 증가
보안 문제 발생 가능성
구현
주요 구현체자바 가상 머신 (JVM)
.NET 공통 언어 런타임 (CLR)
V8
역사
초기 연구1960년대
발전1990년대 자바 등장과 함께 본격화
활용 분야
웹 브라우저자바스크립트 엔진
게임 엔진게임 로직 실행
서버 사이드Node.js
관련 기술
정적 컴파일 (AOT)미리 컴파일하여 실행 속도 향상
인터프리터코드를 한 줄씩 실행

2. 역사

최초로 출판된 JIT 컴파일러는 일반적으로 1960년 존 매카시가 LISP에 대한 연구에서 비롯된 것으로 여겨진다. 그의 논문 "기호 표현의 재귀 함수와 기계에 의한 계산, 파트 I"에서 그는 런타임 중에 변환되는 함수를 언급하며, 이를 통해 컴파일러의 출력을 천공 카드에 저장할 필요가 없도록 했다. 이는 오늘날의 JIT와는 다소 다르며 "컴파일 앤 고 시스템"으로 더 정확하게 불릴 수 있다.

또 다른 초기 예시는 켄 톰슨으로, 1968년에 QED 텍스트 편집기에서 패턴 매칭을 위해 정규 표현식을 사용하면서 속도를 높이기 위해 호환 타임 셰어링 시스템의 IBM 7094 코드로 JIT 컴파일을 구현했다. 제임스 G. 미첼은 1970년에 실험적인 언어 ''LC²''를 위해 인터프리터에서 컴파일된 코드를 파생하는 영향력 있는 기술을 개발했다.

1983년경 스몰토크는 JIT 컴파일의 새로운 측면을 개척했다. 예를 들어, 필요할 때만 기계어로 변환하고 그 결과를 나중에 사용하기 위해 캐시했다. 메모리가 부족해지면 시스템은 이 코드의 일부를 삭제하고 필요할 때 다시 생성하기도 했다. 썬 마이크로시스템즈의 Self 언어는 이러한 스몰토크의 기술을 더욱 발전시켜, 완전한 객체 지향 언어임에도 불구하고 최적화된 C 코드 속도의 절반에 달하는 성능을 보여주며 한때 세계에서 가장 빠른 스몰토크 시스템으로 평가받았다.

Self 프로젝트는 중단되었지만, 그 연구는 자바 언어 개발로 이어졌다. "Just-in-time compilation"이라는 용어는 제조업의 "Just-in-time" 생산 방식에서 유래했으며, 제임스 고슬링이 1993년부터 사용하면서 자바를 통해 대중화되었다. 현재 JIT 컴파일 기술은 자바 가상 머신의 대부분의 구현에서 사용되며, 특히 HotSpot VM은 Self의 연구 결과를 기반으로 JIT 컴파일을 광범위하게 활용한다.

HP의 다이나모(Dynamo) 프로젝트는 PA-8000 기계 코드를 입력받아 다시 최적화된 PA-8000 코드를 출력하는 실험적인 JIT 컴파일러였다. 이 시스템은 기존 컴파일러가 시도하기 어려운 코드 인라인, 동적 라이브러리 호출 최적화 등 기계 코드 수준의 런타임 최적화를 통해 경우에 따라 30%까지 성능 향상을 보여주었다.

최근에는 2020년 11월 PHP 8.0 버전과 2024년 10월 CPython[1]에도 JIT 컴파일러가 도입되었다.

3. 설계

바이트코드 컴파일 시스템에서는 소스 코드가 특정 기계에 종속되지 않고 여러 컴퓨터 아키텍처 간에 이식 가능중간 표현바이트코드로 먼저 변환된다. 이 바이트코드는 가상 머신(VM)에서 해석되거나 실행될 수 있다. JIT 컴파일러는 이 바이트코드를 프로그램 실행 중에 동적으로 기계어로 컴파일하여 실행 속도를 높인다. 컴파일은 파일, 함수 또는 임의의 코드 조각 단위로 수행될 수 있으며, 코드가 실행되기 직전에 이루어지므로 'Just-In-Time'이라는 이름이 붙었다. 한번 컴파일된 기계어 코드는 캐시에 저장되어 동일한 코드를 다시 실행할 때 컴파일 과정 없이 빠르게 재사용될 수 있다.

JIT 컴파일은 단순히 바이트코드를 해석만 하는 기존의 인터프리터 방식보다 훨씬 뛰어난 성능을 낸다. 소스 코드를 직접 해석하는 인터프리터보다는 더욱 빠르다. JIT 컴파일 환경은 정적 컴파일의 성능에 근접하면서도, 바이트코드 해석 방식이 가지는 유연성과 이식성이라는 장점을 유지하는 것을 목표로 한다. 바이트코드 컴파일러가 배포 전에 원본 소스 코드를 분석하고 기본적인 최적화를 수행하는 '무거운 작업'을 미리 처리하는 경우가 많다. 따라서 런타임에 이루어지는 바이트코드에서 기계어로의 컴파일은 소스 코드에서 직접 컴파일하는 것보다 훨씬 빠르다. 또한, 배포된 바이트코드는 특정 플랫폼에 종속적인 네이티브 코드와 달리 이식성이 뛰어나다.

JIT 컴파일러는 가상 머신 개발 과정을 단순화하는 데에도 기여한다.


  • 복잡한 최적화 과정은 바이트코드 컴파일러가 미리 처리하므로, JIT 컴파일러는 이를 고려하지 않아도 된다.
  • 바이트코드는 기계어로 빠르게 변환되도록 설계되어 있어, 일반적인 컴파일러보다 JIT 컴파일러를 만드는 과정이 상대적으로 수월하다.


JIT 컴파일된 코드는 인터프리터 방식보다 훨씬 좋은 성능을 낼 뿐만 아니라, 특정 환경에서는 정적으로 컴파일된 코드보다 더 나은 성능을 보이기도 한다. 이는 실행 시점에 컴파일을 수행하기 때문에 얻을 수 있는 장점들 덕분이다.

  • '''실행 환경 맞춤 최적화''': 컴파일 시점에 프로그램이 실행될 CPU운영체제의 특성을 파악하여 최적화할 수 있다. 예를 들어, CPU가 SSE2 같은 특정 벡터 명령어 집합을 지원하는 것을 감지하면, 이를 활용하는 코드를 생성하여 성능을 높일 수 있다. 정적 컴파일러로 이런 수준의 최적화를 하려면 각 플랫폼별 바이너리를 만들거나, 하나의 바이너리 안에 여러 버전의 코드를 포함해야 한다.
  • '''통계 기반 동적 최적화''': 프로그램 실행 중에 어떤 코드가 자주 사용되는지 등의 통계를 수집하고, 이를 바탕으로 코드를 재배치하거나 다시 컴파일하여 성능을 더욱 향상시킬 수 있다. (일부 정적 컴파일러도 프로파일 정보를 활용하지만, JIT는 런타임 정보를 실시간으로 반영할 수 있다.)
  • '''전역 코드 최적화''': 동적 링크의 유연성을 유지하면서도, 정적 컴파일 및 링크 과정에서 발생할 수 있는 오버헤드 없이 전역적인 코드 최적화(예: 라이브러리 함수의 인라인화)를 수행할 수 있다. 특히, 객체 지향 언어에서 가상 함수 호출과 같은 간접 호출을 특정 조건 하에 직접 호출이나 인라인 코드로 변환하여 성능을 개선할 수 있다. 런타임 검사가 필요한 경우에도, JIT 컴파일을 통해 이러한 검사를 루프 밖으로 이동시키는 등의 최적화로 속도를 크게 향상시킬 수 있다.
  • '''효율적인 쓰레기 수집 지원 및 캐시 활용''': 바이트코드 시스템은 실행된 코드를 재정렬하여 캐시 활용도를 높이는 데 더 용이하며, 이는 쓰레기 수집을 사용하는 언어에서도 성능 이점을 제공할 수 있다.

4. 성능

JIT 컴파일된 코드는 일반적인 인터프리터 언어에 비해 훨씬 좋은 성능을 낸다. 바이트코드를 읽어 빠른 속도로 기계어를 생성하며, 이 기계어는 캐시에 저장되어 재사용 시 컴파일을 다시 할 필요가 없기 때문이다. 정적 컴파일 언어만큼 빠르면서 인터프리터 언어의 빠른 응답 속도를 추구한다.

JIT 컴파일은 실행 환경을 파악하여 최적화할 수 있다는 장점이 있다.


  • 실행되는 CPU나 운영체제에 맞춰 컴파일을 다르게 진행할 수 있다. 예를 들어, CPU가 특정 명령어 세트(SSE2 등)를 지원하면 이를 활용하도록 최적화한다.
  • 실행 중인 CPU, 캐시, 주기억 장치 등의 정보를 활용하여 최적화된 코드를 생성할 수 있다.
  • 가비지 컬렉션 (GC) 기능을 구현하기 용이하다.
  • 객체 지향 언어에서 메서드 호출을 최적화할 수 있다. 예를 들어, 특정 조건 하에서 가상 메서드 호출을 더 빠른 직접 호출이나 인라인 확장으로 대체할 수 있다.


그러나 JIT 컴파일에는 단점도 존재한다. 코드를 로드하고 컴파일하는 데 시간이 필요하므로 애플리케이션 초기 실행 시 지연이 발생할 수 있다. 이를 "시작 시간 지연" 또는 "웜업 시간"이라고 부른다. 일반적으로 더 많은 최적화를 수행할수록 생성되는 코드의 품질은 높아지지만 초기 지연도 길어지므로, 컴파일 시간과 코드 품질 사이의 절충이 필요하다. 또한, 컴파일 자체가 프로그램 실행 시간의 오버헤드가 되며, 사전 컴파일 방식에서 가능한 고도의 최적화를 수행하기는 어렵다.

초기 지연을 줄이기 위한 여러 접근 방식이 있다. HotSpot Java Virtual Machine처럼 인터프리테이션과 JIT 컴파일을 결합하여, 자주 사용되는 코드만 컴파일하는 방식이 있다. (자세한 내용은 아래 문단 참조) Microsoft의 네이티브 이미지 생성기 (Ngen)는 .NET Framework에서 사용되는 기술로, 공통 중간 언어 코드를 미리 기계어 코드로 컴파일하여(사전 컴파일) 런타임 컴파일 필요성을 줄인다. Ngen은 시작 시간을 개선할 수 있지만, 실행 시점의 프로파일링 정보 없이 정적으로 컴파일되므로 JIT 컴파일된 코드보다 성능이 낮을 수 있다.

JIT 컴파일이 항상 안정적인 성능 향상을 보장하는 것은 아니다. 일부 연구에 따르면, 상당수의 경우 프로그램 실행이 안정적인 최고 성능 상태에 도달하지 못하거나, 웜업 기간 후에 오히려 성능이 저하되는 경우도 관찰되었다. 안정적인 성능 상태에 도달하더라도 수백 번의 반복 실행이 필요한 경우도 있었다.

4. 1. 적응적 컴파일 (Adaptive Compilation)

JIT 컴파일러의 단점을 보완하는 한 가지 방식으로 적응적 컴파일(Adaptive Compilation)이 있다. 이는 프로그램 실행 시작 시점에는 인터프리터 방식으로 코드를 실행하다가, 반복적으로 실행되는 코드를 감지(프로파일링)하여 해당 부분만 JIT 컴파일하는 방식이다. 코드가 처음 사용될 때 바로 컴파일하는 것이 아니라, 여러 번 호출된 후에 컴파일을 시작하는 것을 지연 컴파일(Lazy Compilation)이라고 부르기도 한다.

프로그램 실행 시간의 대부분은 코드의 극히 일부에서 소비된다는 경험칙(80-20 법칙)이 있다. 일반적으로 실행 시간의 80%는 전체 코드의 20%에서 발생한다. 적응적 컴파일은 이렇게 자주 사용되는 코드만을 선별하여 컴파일함으로써, 프로그램 시작 시의 오버헤드와 메모리 사용량 증가를 억제하면서도 실행 속도를 향상시킬 수 있다.

SunHotSpot Java Virtual Machine은 인터프리테이션과 JIT 컴파일을 결합하는 적응적 방식을 사용한다. 애플리케이션 코드는 처음에 인터프리터 방식으로 실행되지만, JVM은 자주 실행되는 바이트코드 시퀀스를 지속적으로 감시하고 이를 하드웨어에서 직접 실행 가능한 기계 코드로 변환한다. 몇 번 실행되지 않는 코드는 컴파일 시간을 절약하고 초기 지연 시간을 줄이기 위해 인터프리터 방식으로 계속 실행된다. 반면, 자주 실행되는 코드는 초기에는 느리게 인터프리터 방식으로 실행되다가 JIT 컴파일을 통해 이후 고속으로 실행된다. 또한, 초기 코드 인터프리테이션 단계에서 수집된 실행 통계 정보는 컴파일 시 더 나은 최적화를 수행하는 데 활용될 수 있다. 이러한 적응적 최적화(Adaptive Optimization)는 정적 컴파일 과정에서는 얻을 수 없는 런타임 정보를 바탕으로 최적화를 수행하기 때문에, 경우에 따라 정적 컴파일보다 더 나은 성능을 보이기도 한다.

그러나 적응적 컴파일 방식이 모든 상황에 유리한 것은 아니다. 예를 들어, 수백 건 이내의 소량 데이터를 처리하는 배치 잡(batch job)이 하루에도 수백, 수천 번씩 실행되는 경우 문제가 발생할 수 있다. JIT 컴파일, 특히 적응적 컴파일을 사용하면 각 잡이 시작될 때마다 공통 클래스에 대한 컴파일 과정이 반복적으로 수행된다. 처리할 데이터 양이 적기 때문에, 대부분의 클래스는 컴파일로 인한 성능 향상 효과를 보기도 전에, 혹은 컴파일조차 완료되기 전에 잡 실행이 종료될 수 있다. 결과적으로, 실제 작업 로직 처리보다 이러한 컴파일 관련 오버헤드에 더 많은 시스템 자원이 소모될 수 있다. 처리 데이터 양에 따라 JIT/AOT(Ahead-Of-Time) 컴파일의 유불리가 달라질 수 있지만, 이를 명확히 구분하여 적용하기는 어렵다. 장시간 실행되는 배치 잡이나 온라인 서비스 환경에서는 JIT 컴파일러, 특히 적응적 컴파일 방식이 일반적으로 더 적합하다고 여겨진다. 일부 JIT 컴파일러는 적응적 최적화를 위해 '몇 번 실행되면 컴파일을 시작할 것인가'와 같은 파라미터를 제공하기도 한다.

5. 보안

JIT 컴파일은 코드를 실행 가능한 데이터 형태로 다루기 때문에 근본적으로 보안 문제와 악용 가능성을 가지고 있다.

JIT 컴파일 과정은 소스 코드나 바이트 코드를 기계어로 변환한 뒤, 이를 메모리에서 직접 실행하는 방식으로 이루어진다. 일반적인 사전 컴파일 방식처럼 기계어를 디스크에 저장하고 별도의 프로그램으로 불러오는 것이 아니라, 메모리에 바로 기계어를 쓰고 즉시 실행하는 것이다. 하지만 최신 컴퓨터 아키텍처에는 실행 공간 보호라는 보안 기능이 있어서 임의의 메모리 영역에서 코드를 실행하는 것을 기본적으로 차단한다. 따라서 JIT 컴파일을 사용하려면 해당 메모리 영역을 '실행 가능'으로 특별히 표시해야 한다. 보안을 더욱 강화하기 위해서는, 코드를 메모리에 기록한 에는 해당 영역을 '읽기 전용'으로 변경하는 것이 중요하다. 메모리가 '쓰기 가능' 상태이면서 동시에 '실행 가능' 상태로 남아있으면 심각한 보안 취약점이 될 수 있기 때문이다 (W^X 원칙 참조). 예를 들어, 웹 브라우저 파이어폭스자바스크립트용 JIT 컴파일러는 버전 46부터 이러한 메모리 보호 기능을 도입하여 보안을 강화했다.

이러한 JIT 컴파일의 특성을 악용하는 대표적인 공격 기법 중 하나로 JIT 스프레이(JIT spraying)가 있다. 이는 힙 스프레이(heap spraying) 기법과 JIT 컴파일을 결합한 컴퓨터 보안 취약점 공격 방식이다. 공격자는 JIT 컴파일 기능을 이용하여 실행 가능한 악성 코드를 메모리의 특정 영역(힙)에 대량으로 채워 넣는다. 그런 다음 프로그램의 실행 흐름을 조작하여 이 악성 코드가 위치한 메모리 영역으로 이동시켜 실행함으로써, 시스템을 장악하거나 추가적인 악성 행위를 시도할 수 있다.

6. 활용

JIT 컴파일은 다양한 환경에서 프로그램 실행 속도를 높이기 위해 활용된다.


  • 동적 기능: 프로그램 실행 중에 결정되는 기능, 특히 정규 표현식 처리에 유용하다. 예를 들어, 텍스트 편집기에서 사용자가 입력한 정규 표현식을 실시간으로 기계어로 컴파일하면 패턴 검색 속도를 크게 향상시킬 수 있다. 이는 정규 표현식 패턴이 실행 시점에 주어지므로 미리 컴파일해 둘 수 없기 때문이다. 많은 최신 정규 표현식 라이브러리도 내부적으로 JIT 컴파일을 사용하여 성능을 개선한다.

  • 런타임 환경: 여러 최신 런타임 환경은 JIT 컴파일을 핵심 기술로 사용한다.
  • 자바: 대부분의 자바 가상 머신(JVM) 구현, 특히 HotSpot VM은 JIT 컴파일을 통해 성능을 최적화한다.
  • .NET: 마이크로소프트의 .NET 환경은 설계 초기부터 JIT 컴파일을 기반으로 만들어졌다.
  • PHP: 버전 8.0부터 JIT 컴파일러를 도입했다.
  • CPython: 파이썬의 표준 구현인 CPython은 3.13 버전부터 실험적으로 JIT 컴파일러를 도입했다.[1]

  • 에뮬레이션: 일부 에뮬레이터는 JIT 컴파일을 사용하여 서로 다른 CPU 아키텍처 간의 코드 변환을 수행한다. 예를 들어, x86 아키텍처용 코드를 ARM 아키텍처에서 실행해야 할 때, JIT 컴파일러가 실시간으로 명령어를 번역하고 최적화하여 실행 속도를 높인다. 트랜스메타크루소 프로세서는 x86 명령어를 내부 VLIW 명령어로 변환하는 데 JIT 컴파일을 사용했으며, DEC의 FX!32 에뮬레이터에서도 JIT 컴파일 기술이 사용되었다.

7. 주요 JIT 컴파일러

시만텍의 symjit 및 보랜드의 JIT 컴파일러는 초창기 주요 JIT 컴파일러였다.

썬 마이크로시스템즈의 HotSpot 컴파일러는 본격적으로 적응적 컴파일 방식을 채택했다. HotSpot 이후에는 JIT 컴파일러 부분의 인터페이스가 규정되어 JIT 컴파일 엔진 부분을 교체하는 것이 가능해졌다.

IBM의 IBM JDK, BEA 시스템즈의 JRockit은 모두 독자적인 적응적 컴파일을 수행한다. JRockit은 특히 x86에 특화하여 실행 효율을 높이고 있다.

학술 분야에서는, 슈토에 의한 ShuJIT나, 후지쯔 연구소와 도쿄공업대학에 의한 리플렉션 기능을 다루는 OpenJIT 등이 있다.

참조

[1] 웹사이트 What’s New In Python 3.13 https://docs.python.[...] 2024-11-27
[2] 문서 an overview of TraceMonkey http://hacks.mozilla[...] hacks.mozilla.org



본 사이트는 AI가 위키백과와 뉴스 기사,정부 간행물,학술 논문등을 바탕으로 정보를 가공하여 제공하는 백과사전형 서비스입니다.
모든 문서는 AI에 의해 자동 생성되며, CC BY-SA 4.0 라이선스에 따라 이용할 수 있습니다.
하지만, 위키백과나 뉴스 기사 자체에 오류, 부정확한 정보, 또는 가짜 뉴스가 포함될 수 있으며, AI는 이러한 내용을 완벽하게 걸러내지 못할 수 있습니다.
따라서 제공되는 정보에 일부 오류나 편향이 있을 수 있으므로, 중요한 정보는 반드시 다른 출처를 통해 교차 검증하시기 바랍니다.

문의하기 : help@durumis.com