프로그램을 실행 전에 미리 컴파일하는 방식과 달리, 실행 시점에 코드를 컴파일하는 방법이 있습니다. 이를 JIT(Just-In-Time) 컴파일 또는 동적 번역(Dynamic Translation)이라고 부릅니다.
이 글에서는 먼저 JIT 컴파일이 어떻게 작동하는지 살펴보고, Ruby on Rails 애플리케이션에 JIT 컴파일이 좋은 선택이 될 수 있는 이유를 알아본 뒤, 현재 사용 가능한 세 가지 옵션(YJIT, MJIT, TenderJIT)과 설치 방법까지 자세히 다뤄보겠습니다.
JIT 컴파일러의 작동 원리
JIT(Just-In-Time) 컴파일은 프로그램 실행 중에 컴파일을 수행하는 코드 실행 방식입니다.
소스 코드를 직접 변환할 수도 있지만, 일반적으로는 바이트코드(bytecode)를 기계어(machine code)로 변환한 후 이를 직접 실행하는 방식이 가장 널리 사용됩니다.
JIT 컴파일러를 사용하는 시스템은 실행 중인 코드를 지속적으로 분석합니다. 이 과정에서 컴파일 또는 재컴파일로 얻는 속도상의 이점이 그 비용보다 큰 코드 영역을 식별하게 됩니다.
Ruby에 JIT 컴파일이 주는 이점
JIT 컴파일은 프로그램을 기계어로 변환하는 두 가지 전통적인 방식, 즉 인터프리터(interpretation)와 AOT(Ahead-of-Time) 컴파일의 장점(및 단점)을 결합합니다.
간단히 말해, 인터프리터의 유연성과 생성된 코드의 빠른 실행 속도를 동시에 얻을 수 있으며, 대신 컴파일과 링크에 따른 추가 오버헤드가 발생합니다.
JIT 컴파일은 동적 재컴파일(dynamic recompilation)과 특정 마이크로아키텍처에 맞춘 최적화 같은 적응형 최적화(adaptive optimization) 기법을 활용할 수 있는 동적 컴파일 방식입니다. 런타임 시스템이 늦은 바인딩(late-bound) 데이터 타입을 처리하고 보안 보장을 적용할 수 있기 때문에, Ruby와 같은 동적 프로그래밍 언어는 인터프리팅 및 JIT 컴파일에 특히 적합합니다.
또한 GCC와 같은 최적화 컴파일러는 레지스터 기반 아키텍처를 채택했을 때 명령어를 더 효율적으로 최적화할 수 있습니다. 컴파일러는 레지스터 기반 아키텍처의 중간 표현(intermediate representation) 위에서 작동하며, 컴파일 과정에서 명령어가 중간 표현에 도달하면 GCC는 추가 패스(pass)를 거쳐 CPU가 명령어를 더 빠르게 실행하도록 만듭니다.
Ruby용 JIT 컴파일러: YJIT, MJIT, TenderJIT
이제 Ruby에서 사용할 수 있는 세 가지 JIT 컴파일러인 MJIT, YJIT, TenderJIT을 하나씩 살펴보고, 각각 설정하는 방법을 알아보겠습니다.
MJIT: Ruby의 메서드 기반 JIT 컴파일러
MJIT은 Vladimir Makarov가 구현했으며, C 언어 기반으로 Ruby에 처음 도입된 컴파일러 방식입니다. Ruby 2.6부터 사용할 수 있으며, YARV 명령어를 활용해 자주 사용되는 명령어들을 바이너리 코드로 컴파일합니다.
MJIT은 입출력(I/O)에 의존하지 않는 프로그램에서 성능을 향상시킵니다.
다만 성능 면에서는 YJIT이 이 초기 C 기반 컴파일러를 능가합니다. Ruby 3의 JIT은 MRI 역사상 가장 빠른 JIT으로, MJIT의 훌륭한 토대 위에 만들어진 결과물입니다.
MJIT 사용 방법
MJIT을 사용하려면 Ruby 2.6 환경에서 --jit 옵션으로 JIT을 활성화하면 됩니다.
이 단계를 건너뛰면 MJIT은 오류를 표시합니다.
Ruby 2.6에는 JIT 전용 설정 옵션들이 포함되어 있어 작동 방식을 파악하는 데 도움이 됩니다. ruby --help 명령어로 해당 옵션들을 확인할 수 있습니다.
요약하자면, MJIT은 별도의 스레드에서 비동기적으로 실행되며, 연산이 처음 5회 실행된 후 JIT 컴파일을 시작합니다.
YJIT: Ruby on Rails를 위한 새로운 선택지
YJIT은 Ruby 3.1과 함께 출시된 비교적 최신 JIT 컴파일러로, 많은 개선 사항과 향상된 성능을 약속합니다. Shopify가 설계하고 실험적인 결과를 내고 있는 진행 중인 프로젝트이기 때문에, 특히 대규모 애플리케이션에서는 신중하게 사용해야 합니다.
이 점을 염두에 두더라도, YJIT은 Ruby on Rails 애플리케이션의 성능을 확실히 향상시킵니다. 실제 대부분의 소프트웨어는 YJIT의 기본 블록 버저닝(basic block versioning) JIT 컴파일러가 제공하는 빠른 웜업(warm-up)과 성능 개선의 혜택을 받습니다.
YJIT 프로젝트의 목표는 CRuby에 JIT 컴파일러를 점진적으로 통합하여, 궁극적으로 대부분의 코드 실행을 인터프리터 대신 처리하는 것입니다.
공식 벤치마크('YJIT: Building a New JIT Compiler for CRuby' 참조)에 따르면, YJIT은 기본 CRuby 인터프리터 대비 다음과 같은 성능 향상을 보여주었습니다:
- railsbench에서 20% 향상
- liquid 템플릿 렌더링에서 39% 향상
- activerecord에서 37% 향상
다만 한 가지 유의할 점이 있습니다.
railsbench에서 실행되는 명령어 중 약 79%만 YJIT이 처리하고, 나머지는 기본 인터프리터에서 실행됩니다.
출처: YJIT: Building a New JIT Compiler for CRuby
즉, YJIT의 현재 결과를 더욱 개선하기 위해서는 여전히 해야 할 일이 많다는 뜻입니다.
그럼에도 불구하고 YJIT은 모든 벤치마크, 심지어 가장 까다로운 항목에서도 최소한 인터프리터와 동등한 성능을 보여주며, 각 벤치마크를 단 한 번의 반복만으로 거의 최고 성능에 도달합니다.
YJIT 사용 방법
참고: YJIT은 현재 x86-64 플랫폼의 macOS와 Linux에서만 사용할 수 있습니다. 또한 앞서 언급했듯이 아직은 대규모 애플리케이션에는 권장되지 않습니다.
YJIT은 기본적으로 비활성화되어 있습니다. 활성화하려면 먼저 명령줄에서 --yjit 옵션을 지정해야 합니다.
설치 여부를 확인하려면 ruby --enable-yjit -v를 실행하세요. warning: unknown argument for --enable: 'yjit'라는 경고가 표시되면 설치가 필요합니다.
설치 후에는 irb를 열고 RUBY_YJIT_ENABLE=1을 설정합니다. 이제 종료하면 YJIT을 사용할 준비가 완료됩니다. ruby --enable-yjit -v 명령어는 ruby 3.1.0p0 (2021-12-25 revision fb4df44d16) [arm64-darwin21]과 같은 결과를 반환해야 합니다.
TenderJIT: 순수 Ruby로 작성된 실험적 JIT
TenderJIT은 설계가 YJIT에 크게 기반을 둔 Ruby용 실험적 JIT 컴파일러입니다. TenderJIT의 가장 큰 특징은 순수 Ruby로 작성되었다는 점입니다.
이것은 데모 프로젝트로, 최종적으로는 gem 형태로 배포하는 것이 목표입니다. 그동안 실험해볼 수는 있지만, 아직 개발이 진행 중이라는 점을 유의하세요. TenderJIT을 사용하려면 Ruby 3.0.2 이상이 필요합니다.
TenderJIT 사용 방법
TenderJIT은 현재 메서드를 자동으로 컴파일하지 않습니다. 메서드를 컴파일하려면 TenderJIT을 수동으로 설정해야 합니다.
저장소를 클론한 후 다음 명령어를 실행하세요:
그리고 코드에 직접 수동으로 설정해야 합니다:
TenderJIT은 대상 메서드의 각 YARV 명령어를 읽어들인 후 이를 기계어로 변환합니다.
TenderJIT에 대한 더 많은 예제를 원한다면 다음 영상들을 확인해보세요: Aaron Patterson과 함께하는 'A JIT compiler for Ruby', 그리고 'Hacking on TenderJIT!' 등이 있습니다.
마무리
이번 글에서는 Ruby의 세 가지 JIT 컴파일러인 MJIT, YJIT, TenderJIT과 각각의 설정 방법을 간략히 살펴보았습니다. 세 가지 옵션 모두 실험적인 단계이며 각자 고유한 제약 사항이 있습니다.
하지만 그중에서도 YJIT이 현재 가장 성숙한 단계에 있으며, 성장과 확장 가능성도 가장 큽니다. 다른 Ruby JIT보다 뛰어난 성능을 보여주었고, Ruby 3.1.0과 함께 개발되었으며, 빠르게 CRuby의 핵심 요소로 자리 잡고 있습니다.
Ruby용 컴파일러를 직접 만들어보고 싶다면 관련 포스트를 참고해보세요.
즐거운 코딩 되세요!
P.S. Ruby Magic의 새 글을 가장 빠르게 받아보고 싶다면 Ruby Magic 뉴스레터를 구독하고 어떤 글도 놓치지 마세요!

Renata Marques
게스트 저자 Renata는 Ruby와 JavaScript에 집중하는 소프트웨어 엔지니어이자 컴퓨터 과학자입니다. 오픈 소스 프로젝트에 기여하며 개발자 커뮤니티를 돕는 일을 즐깁니다. 여가 시간에는 영화와 TV 프로그램을 감상하고 토론하며, 독서, 음악 감상, 사진 촬영, 영상 제작을 즐깁니다.
Renata Marques의 모든 글 보기