차세대 Ruby JIT 컴파일러, ZJIT 소개

[EN] ZJIT: Building a Next Generation Ruby JIT / Maxime Chevalier-Boisvert @maximecb

3줄 요약

  • ZJIT는 YJIT의 한계를 극복하고 Ruby 성능을 혁신하기 위해 Shopify에서 개발 중인 차세대 JIT 컴파일러입니다.
  • 표준화된 아키텍처와 SSA 기반 중간 표현(IR)을 도입하여 유지보수성과 확장성을 높이고, 빠른 JIT-to-JIT 호출과 컴파일 작업 재사용 기능을 포함합니다.
  • Ruby 3.5에 YJIT와 함께 제공될 예정이며, Ruby의 장기적인 경쟁력 확보와 성능 향상에 크게 기여할 것으로 기대됩니다.

Maxim Shar는 Shopify의 Ruby 및 Rails 인프라 팀 소속으로, Ruby의 성능을 한 단계 끌어올릴 차세대 JIT 컴파일러인 ZJIT에 대해 발표했습니다. 기존 YJIT의 성공적인 도입이 Ruby 생태계에 긍정적인 영향을 미쳤음에도 불구하고, 특정 워크로드에서의 한계와 더 높은 성능에 대한 지속적인 요구는 ZJIT 개발의 주요 동기가 되었습니다. ZJIT는 YJIT를 통해 얻은 지난 4년 반의 경험과 수백 가지 실험 결과를 바탕으로, 더욱 유지보수하기 쉽고 확장 가능한 아키텍처를 목표로 설계되었습니다. 이는 Ruby의 장기적인 발전을 위한 필수적인 투자로 간주됩니다.

YJIT의 성공과 한계

YJIT는 2020년 MicroJIT이라는 프로토타입으로 시작하여 3개월 만에 10%의 속도 향상을 달성했습니다. 이후 9개월간의 집중 개발을 통해 Railsbench에서 한 자릿수, Optcarrot에서 두 자릿수 속도 향상을 목표로 했으나, 예상보다 훨씬 좋은 성과를 거두며 Ruby 3.1에 공식적으로 통합되었습니다. 특히 Ruby 3.3에서는 Ruby 2.7 인터프리터 대비 최대 3배 빠른 성능을 보여주었으며, Shopify의 스토어프론트 렌더링 인프라뿐만 아니라 Mastodon, Discourse 등 다양한 기술 기업에 배포되어 최대 30%의 성능 개선을 이끌어냈습니다. YJIT의 도입은 개발자들이 최신 Ruby 버전으로 업그레이드하는 강력한 동기가 되었으며, 이는 Ruby 커뮤니티 전반의 활성화에 기여했습니다.

하지만 YJIT는 최근 성능 개선 폭이 둔화되는 정체기에 접어들었다는 평가를 받고 있습니다. 특히 중첩 루프와 같은 단순한 마이크로벤치마크에서는 JavaScript JIT에 비해 현저히 낮은 성능을 보였습니다. 이는 YJIT가 웹 애플리케이션의 데이터베이스 요청과 같은 외부 요인으로 인한 병목 현상을 해결할 수 없다는 점 외에도, JIT 자체의 최적화 수준에 대한 근본적인 한계를 시사합니다. Maxim Shar는 Ruby의 장기적인 생존과 경쟁력 유지를 위해 이러한 성능 병목을 해결하고, 궁극적으로 세계적인 수준의 Ruby JIT를 구축하는 것이 중요하다고 강조했습니다.

ZJIT의 핵심 설계 원칙

ZJIT는 YJIT의 한계를 극복하고 미래 지향적인 Ruby JIT를 구축하기 위해 두 가지 주요 설계 변경을 도입합니다.

  1. 메서드 기반 JIT 컴파일러로의 전환: YJIT는 Maxim Shar의 박사 연구를 기반으로 한 Lazy Basic Block Versioning 아키텍처를 채택했습니다. 이는 빠른 프로토타이핑에 유리했지만, 확장성에 한계가 있었습니다. ZJIT는 대신 컴파일러 교과서에서 흔히 다루는 전통적인 메서드 기반 JIT 컴파일러 아키텍처를 채택합니다. 이는 검증된 설계로서 개발 위험을 최소화하고 높은 성공 가능성을 보장합니다. 또한, 모듈화된 구조를 통해 향후 유지보수와 확장을 용이하게 하며, 보다 표준적인 설계는 Ruby 커뮤니티의 새로운 기여자들에게도 접근성을 높여 개발 참여를 독려할 수 있습니다.

  2. SSA(Static Single Assignment) 기반 중간 표현(IR) 도입: YJIT는 Yarv 바이트코드를 직접 기계어로 컴파일하는 방식을 사용했습니다. Yarv 바이트코드는 MRI 인터프리터에 최적화되어 있어 CISC(Complex Instruction Set Computer)와 유사하게 크고 복잡한 명령어를 포함합니다. 반면, ZJIT는 자체적인 SSA 기반 IR을 도입합니다. 이 IR은 복잡한 의미론을 작고 구성 가능한 RISC(Reduced Instruction Set Computer)와 유사한 프리미티브로 분해하여 코드 추론 및 최적화를 용이하게 합니다. SSA 기반 IR은 LLVM, GCC 등에서 널리 사용되는 de facto 표준이며, 그 유연성과 견고함이 입증되었습니다.

ZJIT의 주요 기능 및 현재 상태

ZJIT는 특히 두 가지 핵심 기능을 통해 성능 향상을 목표로 합니다.

  1. 빠른 JIT-to-JIT 호출: Ruby VM에서 메서드 호출은 복잡한 스택 설정(VM 스택CFP 스택)으로 인해 비용이 많이 듭니다. YJIT는 이를 8~12배 빠르게 만들었지만, 여전히 최적의 성능에는 미치지 못했습니다. ZJIT는 CPU의 callreturn 명령어를 사용하고 C 스택을 직접 활용하여 JIT된 함수 간의 호출 성능을 극대화합니다. 초기 실험에서 재귀 피보나치 벤치마크에서 YJIT보다 60% 이상 빠른 성능을 보여주며 매우 고무적인 결과를 얻었습니다.

  2. 컴파일 작업 저장 및 재사용: JIT 컴파일러는 실행 중인 프로그램과 메모리 및 CPU 자원을 공유하므로, 컴파일 시간과 자원 사용량을 최소화하는 것이 중요합니다. ZJIT는 컴파일된 기계 코드와 메타데이터를 직렬화하여 영구 저장하는 기능을 목표로 합니다. 이를 통해 워밍업 시간을 단축하고, 한 번 컴파일된 코드를 여러 번 재사용하여 더 높은 수준의 최적화를 적용할 수 있게 됩니다. 이는 Shopify와 같이 대규모 서버 환경에서 코드 재배포 시 효율성을 크게 향상시킬 수 있습니다.

현재 ZJIT는 개발 시작 2개월 반 만에 커스텀 SSA IR, 제어 흐름, 빠른 JIT-to-JIT 호출, 상수 및 타입 전파, 데드 코드 제거 등의 핵심 기능이 구현되었습니다. 일부 마이크로벤치마크에서는 이미 인터프리터와 YJIT보다 빠른 성능을 보이고 있습니다. Ruby 3.5에 YJIT와 함께 업스트림될 예정이며, YJIT는 당분간 유지보수 모드로 전환됩니다. ZJIT는 올해 하반기부터 점차 실사용 가능한 수준으로 발전하여, 연말까지 YJIT의 성능을 능가하고 더 나아가 압도하는 것을 목표로 하고 있습니다.

인력 채용

Shopify의 Ruby 및 Rails 인프라 팀은 ZJIT 개발을 위해 컴파일러 전문가, C 및 Rust 시스템 프로그래머, 그리고 세계적인 수준의 Ruby 및 Rails 전문가를 적극적으로 채용하고 있습니다.

ZJIT는 Ruby의 미래 성능을 책임질 중요한 프로젝트입니다. YJIT의 성공을 기반으로, 표준화된 아키텍처와 SSA 기반 IR, 그리고 혁신적인 최적화 기법들을 통해 Ruby는 더욱 강력하고 효율적인 언어로 거듭날 것입니다. 이러한 노력은 Ruby가 끊임없이 발전하는 소프트웨어 환경에서 경쟁력을 유지하고 장기적으로 생존할 수 있는 견고한 기반을 마련할 것입니다. 아직 초기 단계임에도 불구하고 ZJIT는 고무적인 성과를 보여주고 있으며, Ruby 커뮤니티의 지속적인 관심과 참여가 ZJIT의 성공적인 안착과 발전에 크게 기여할 것으로 예상됩니다.