Ruby용 고급 JIT 컴파일러: TruffleRuby 및 JRuby 탐구

Advanced JIT compilers for Ruby: TruffleRuby and JRuby | AppSignal Blog

작성자
발행일
2025년 07월 16일

핵심 요약

  • 1 TruffleRuby와 JRuby는 Ruby의 성능을 획기적으로 개선하는 고급 JIT(Just-In-Time) 컴파일러입니다.
  • 2 JIT 컴파일은 런타임에 자주 실행되는 코드(핫스팟)를 최적화된 머신 코드로 변환하여 해석 오버헤드를 줄이고 실행 속도를 향상시킵니다.
  • 3 TruffleRuby는 GraalVM 기반으로 병렬 실행 및 다국어 지원에 강점이 있으며, JRuby는 JVM 기반으로 스레드 수준 병렬성과 Java 통합을 제공합니다.

도입

JIT(Just-In-Time) 컴파일은 코드 해석과 전통적인 컴파일의 장점을 결합하여 프로그램 실행 중 성능을 최적화하는 기술입니다. 이는 런타임에 자주 사용되는 코드 영역, 즉 '핫스팟'을 식별하고 이를 최적화된 머신 코드로 변환함으로써 이루어집니다. 이러한 접근 방식은 프로그램이 초기에는 느리게 시작할 수 있지만, 시간이 지남에 따라 성능이 크게 향상되는 '적시(just in time)' 최적화를 가능하게 합니다. Ruby에 JIT 컴파일이 도입된 주요 동기는 Ruby의 동적인 특성과 인터프리터 기반 설계에서 비롯된 오랜 성능 문제를 해결하기 위함이었습니다. 특히 MRI(Matz’s Ruby Interpreter)의 라인별 코드 해석으로 인한 오버헤드, GVL(Global Virtual Machine Lock)로 인한 진정한 병렬 실행의 제약, 그리고 PyPy(Python) 및 V8(JavaScript)과 같은 다른 언어의 고효율 JIT 컴파일러 도입으로 인한 성능 격차 해소가 시급했습니다. 본 글에서는 Ruby의 이러한 성능 과제를 해결하기 위해 등장한 TruffleRuby와 JRuby라는 두 가지 고급 JIT 컴파일러의 주요 특징, 장점 및 한계를 심층적으로 탐구하고, 최근 발표된 ZJIT 컴파일러에 대해서도 간략히 언급합니다.

JIT 컴파일러는 자주 실행되는 바이트코드를 런타임에 네이티브 머신 코드로 동적으로 변환하여 해석 오버헤드를 줄이고 실행 속도를 높입니다. 이를 통해 메서드 인라이닝 및 ‘핫 패스’ 최적화가 가능해지고, 특정 CPU 기능을 활용한 하드웨어별 최적화 및 단일 스레드 성능 개선이 이루어집니다. 그러나 동적 코드 생성은 메모리 사용량을 증가시키며, Ruby의 높은 동적 특성(유연한 타이핑, 런타임 객체 수정 등)은 정적 타입 언어에 비해 최적화 범위에 한계를 둡니다.

TruffleRuby는 GraalVM 위에 구축된 Ruby 구현체로, GraalVM의 JIT 컴파일러와 Truffle 프레임워크를 활용하여 특히 계산 집약적인 Ruby 프로그램에서 상당한 성능 향상을 이룹니다. 주요 특징으로는 GraalVM JIT 통합을 통한 빠른 Ruby 코드 실행, GVL이 없어 Ruby 코드의 병렬 실행 가능, Java, JavaScript, Python 등 GraalVM 지원 언어와의 로우 오버헤드 다국어(Polyglot) 지원, 대부분의 C 확장 지원, 그리고 다국어 디버거 및 모니터링 도구(VisualVM, CPU Tracer 등) 제공 등이 있습니다. 제한 사항으로는 MRI와 100% 호환되지 않는 부분이 있으며, 특정 레거시 OpenSSL 버전에 대한 의존성 문제가 있을 수 있습니다.

JRuby는 Java Virtual Machine (JVM) 기반의 Ruby 구현체로, CRuby와의 높은 호환성을 목표로 하여 Ruby 개발자들이 기존 코드를 최소한의 변경으로 실행하면서 JVM의 성능, 도구 및 배포 옵션을 활용할 수 있도록 합니다. 주요 특징은 Ruby 스레드를 Java 스레드에 매핑하여 GVL 없이 진정한 스레드 수준 병렬성을 제공한다는 점, Java 클래스를 Ruby 프로그램에서 사용하거나 JRuby를 Java 애플리케이션에 내장할 수 있는 긴밀한 Java 통합, JIT 컴파일러를 통한 높은 성능, 그리고 MB당 초당 요청 처리량이 높아 호스팅 비용 절감에 유리한 낮은 메모리 사용량입니다. 제한 사항으로는 대부분의 JVM이 안전하게 fork될 수 없기 때문에 fork()를 지원하지 않으며, JVM에 내장된 기능 부족으로 Continuations를 지원하지 않는다는 점이 있습니다.

성능 벤치마크 (Optcarrot 사용): NES 에뮬레이터 Optcarrot를 활용한 벤치마크 결과, TruffleRuby (GraalVM JVM 모드)가 가장 높은 FPS를 기록하여 최고의 성능을 보였으며, MRI + YJIT가 그 뒤를 이었습니다. JRuby는 초기 성능은 다소 낮았으나, 여러 번 실행 후 안정화된 성능을 보였습니다. 이는 JVM 기반 최적화가 장기 실행 애플리케이션에서 더 효과적이라는 점을 시사합니다.

고급 JIT 컴파일러가 Ruby 생태계에 미치는 영향: YJIT의 발전은 MRI의 경쟁력을 유지시키고 있으며, TruffleRuby와 JRuby의 존재는 개발자들이 애플리케이션의 특정 요구사항(JVM 통합, 네이티브 실행 속도, Ruby 생태계와의 호환성 등)에 따라 최적의 Ruby 구현체를 선택할 수 있도록 합니다. 이러한 다양성은 성능 문제로 인해 개발자들이 덜 선호하는 언어로 전환할 필요성을 줄여주며, Ruby가 다양한 워크로드에서 실행 가능한 강력한 선택지로 남아있도록 합니다. 특히 2023년 Aaron Patterson이 YJIT가 C 확장을 능가하는 성능을 보일 수 있음을 시연한 것은 고급 JIT 컴파일러가 Ruby의 경쟁 우위를 재정의하고 있음을 보여줍니다.

ZJIT 컴파일러는 2025년 5월에 도입된 새로운 JIT 컴파일러로, 더 접근하기 쉽고 커뮤니티 친화적인 컴파일러 아키텍처를 목표로 합니다. YARV 바이트코드를 직접 저수준 IR(LIR)로 컴파일하는 대신, 고수준 SSA 기반 중간 표현(HIR)을 사용하고, 한 번에 하나의 기본 블록 대신 전체 메서드를 컴파일하는 등의 아키텍처적 차이점을 가집니다. 아직 초기 단계이지만, 더 많은 개발자가 Ruby의 성능 개선에 기여할 수 있는 협업의 문을 열어준다는 점에서 중요한 의미를 가집니다.

결론

본 글에서는 Ruby의 성능을 혁신적으로 향상시키는 두 가지 주요 고급 JIT 컴파일러인 TruffleRuby와 JRuby의 핵심 특징과 성능적 이점을 심층적으로 살펴보았습니다. Optcarrot 벤치마크를 통해 이들 컴파일러가 Ruby YJIT와 함께 어떻게 성능 경쟁을 펼치는지 분석했으며, 특히 TruffleRuby가 특정 시나리오에서 뛰어난 성능을 발휘함을 확인했습니다. 또한, 초기 단계이지만 Ruby의 미래 성능 개선에 중요한 역할을 할 것으로 기대되는 ZJIT 컴파일러의 등장을 언급하며, Ruby의 JIT 기술이 지속적으로 발전하고 있음을 강조했습니다. 이러한 JIT 컴파일러들의 발전은 Ruby의 성능 한계를 극복하고 개발자들에게 더욱 강력하고 효율적인 개발 환경을 제공함으로써, Ruby가 현대적인 애플리케이션 개발에서 그 경쟁력을 확고히 유지하는 데 결정적인 기여를 하고 있습니다.

댓글 0

댓글 작성

0/1000
정중하고 건설적인 댓글을 작성해 주세요.

아직 댓글이 없습니다

첫 번째 댓글을 작성해보세요!