원문 캡처 · railsatscale.com
원문 캡처 · railsatscale.com

Shopify는 Ruby용 새 JIT 컴파일러 ZJIT를 스토어프런트 렌더러(SFR)의 일부 지역에 배포한 뒤, 내부 지표에서 YJIT보다 응답 시간이 조금 짧고 JIT 메모리 사용량도 적었다고 밝혔다. 다만 비교 대상 지역과 트래픽이 제한적이며, 회사도 이를 잠정적인 결과로 보고 계속 모니터링하고 있다.

ZJIT는 정적 단일 대입(SSA) 기반 Ruby JIT 컴파일러다. Shopify가 공개한 24시간 응답 시간 그래프에서는 ZJIT가 YJIT보다 낮은 값을 보였지만, 세로축의 절대 수치는 공개하지 않았다. 다음 24시간에도 같은 결과가 나올지, 계절성 등의 영향이 있는지는 아직 알 수 없다고 설명했다.

평균 JIT 메모리 사용량은 YJIT가 125MiB, ZJIT가 101MiB였다. 이는 Ruby 힙을 포함한 전체 프로세스 메모리가 아니라 JIT의 메모리 오버헤드다. 두 컴파일러는 코드 영역과 메타데이터 영역 크기를 제한한다. 대략적인 계산은 total_size = min(code_size, 48MiB) + min(metadata_size, 80MiB)다.

Shopify에 따르면 YJIT는 지연 기본 블록 버전 관리(LBBV)에 필요한 문맥을 저장해 JIT 타입 정보를 비교적 오래 유지한다. 반면 ZJIT는 더 작은 인터프리터 타입 프로파일을 유지하고, 컴파일러 내부의 타입 정보는 메서드를 컴파일하는 동안에만 보통 밀리초 정도 보존한다. 다만 ZJIT는 LBBV보다 정밀도가 낮은 코드를 더 많이 생성하고 코드 크기도 더 클 수 있어, 메모리를 더 줄일 여지가 있다고 덧붙였다.

컴파일 시간에 관한 정확한 통계는 없지만, Shopify는 ZJIT의 워밍업 곡선이 YJIT와 크게 다르지 않고 정상 상태의 컴파일 시간도 비슷해 보인다고 밝혔다.

비교 결과의 한계

이번 비교는 전 세계 몇몇 소규모 지역에서만 ZJIT를 실행한 결과다. 블랙프라이데이와 사이버먼데이를 앞두고 신중하게 배포했으며, ZJIT 지역과 비슷한 트래픽 규모의 다른 지역에서 실행되는 YJIT를 비교 대상으로 삼았다. 서로 다른 지역을 비교하므로 결과가 불확실하다는 것이 Shopify의 설명이다.

SFR은 메서드 호출, 데이터 이동, 입출력 등을 포함하는 일반적인 Ruby 코드지만, 결과가 다른 벤치마크에도 그대로 적용되지는 않는다. Shopify는 일부 벤치마크에서 YJIT와 비슷한 수준이고 ZJIT가 더 느린 경우도 있다고 밝혔다. 복잡한 호출 규약 기능 일부와 예외 처리기는 아직 지원하지 않는다. 부동소수점 연산도 백엔드에서 지원하지 않아 수치 연산 비중이 큰 벤치마크에서는 YJIT나 다른 JIT보다 나아 보이지 않을 가능성이 있다고 설명했다.

Shopify는 현재 프로파일링·컴파일·인터프리터 전환 시점을 조정하는 적응형 컴파일, 메서드 전체에 적용하는 최적화, 지역 변수 최적화 등을 작업 중이라고 밝혔다. 향후 과제로는 코드 메모리 재활용, 블록 인라이닝, C로 구현된 내장 기능의 Ruby 재작성, 할당 제거, 예외 처리 지원 개선, 별칭 분석 정밀화, 컴파일 시간 단축 등을 들었다.

ZJIT 사용 방법

Ruby에는 ZJIT가 기본으로 컴파일되어 있지만 실행 시 기본 활성화 상태는 아니다. 테스트에서 사용하려면 --zjit 플래그나 RUBY_ZJIT_ENABLE 환경 변수를 지정하거나, 애플리케이션을 시작한 뒤 RubyVM::ZJIT.enable을 호출하면 된다. 중간 표현 뷰어는 tryzjit에서 체험할 수 있다.

Shopify는 성능·동작 관련 버그를 Redmine 또는 GitHub에 제보해 달라고 안내했다. ZJIT 논의를 위한 채팅방도 열었다.