Ruby 3.5의 빠른 할당

Fast Allocations in Ruby 3.5

작성자
발행일
2025년 05월 21일

핵심 요약

  • 1 Ruby 3.5는 객체 할당 속도를 획기적으로 향상시키며, 특히 키워드 매개변수에서 두드러진 성능 개선을 보입니다.
  • 2 이는 `Class#new` 메서드를 인라이닝하고 새로운 YARV 명령어를 도입하여 스택 프레임 및 매개변수 복사 오버헤드를 제거함으로써 달성되었습니다.
  • 3 YJIT 활성화 시 키워드 매개변수 할당이 최대 6.5배 빨라지는 등 전반적인 애플리케이션 성능 향상에 기여할 것으로 기대됩니다.

도입

Ruby 3.5는 객체 할당 성능을 대폭 개선하여 이전 버전에 비해 훨씬 빠른 속도를 제공할 예정입니다. 이 글은 이러한 속도 향상이 어떻게 가능했는지에 대한 심층적인 분석을 제공하며, 특히 YJIT(Just-In-Time) 컴파일러의 활성화 여부와 위치(positional) 및 키워드(keyword) 매개변수 유형에 따른 성능 변화를 벤치마크를 통해 상세히 설명합니다. 주요 목표는 객체 할당 비용을 줄여 Ruby 애플리케이션의 전반적인 반응성과 효율성을 높이는 것입니다.

Ruby 3.5의 벤치마크 결과는 모든 객체 할당 유형에서 Ruby 3.4.2 대비 상당한 속도 향상을 보여줍니다. 위치 매개변수의 경우, 매개변수 수와 관계없이 YJIT 없이 약 1.8배, YJIT 활성화 시 약 2.3배 빠른 일정한 성능 향상을 유지합니다. 반면, 키워드 매개변수는 매개변수 수가 증가할수록 속도 향상 비율이 더욱 커지는 흥미로운 양상을 보입니다. 예를 들어, 3개의 키워드 매개변수만으로도 Ruby 3.5는 Ruby 3.4.2보다 3배 빠르며, YJIT를 함께 사용하면 무려 6.5배 이상 빨라지는 놀라운 결과를 보여줍니다. 이러한 성능 향상의 핵심은 Class#new 메서드의 내부 동작 최적화에 있습니다. 기존 Class#new는 객체를 할당하고 initialize 메서드를 호출하는 방식으로 작동하며, 주요 병목 현상은 Ruby 내부 호출 규약과 C 언어 호출 규약 간의 불일치에서 발생했습니다. C 함수는 매개변수를 레지스터를 통해 전달하는 반면, Ruby는 스택을 사용하므로, Ruby에서 C 함수를 호출할 때 매개변수 복사 오버헤드가 불가피했습니다. 특히 키워드 매개변수의 경우, C 함수로 전달하기 위해 해시를 할당하고 매개변수를 반복하여 설정하는 추가적인 비용이 발생했습니다. 초기에는 Class#new를 Ruby로 재작성하는 방안이 고려되었으나, ... (스플랫 매개변수) 사용 시 발생하는 추가 객체 할당 문제와 initialize 호출 시 발생하는 인라인 캐시 미스율 문제로 인해 효율성이 저하될 수 있다는 우려가 있었습니다. CRuby의 단형 인라인 캐시(monomorphic inline cache)는 단일 캐시 항목만 지원하므로, 다양한 유형의 객체가 생성되는 Class#new 호출에서는 캐시 적중률이 낮아질 수 있었습니다. 이러한 문제들을 해결하기 위해 YARV의 저자인 Koichi Sasada의 제안에 따라 Class#new를 Ruby로 구현하는 대신, 새로운 YARV 명령어인 opt_new를 추가하고 Class#new의 구현을 인라이닝(inlining)하는 방식을 채택했습니다. 인라이닝은 호출 코드에 피호출 코드를 직접 삽입하는 방식으로, Class#new 호출 시 실제 Class#new 메서드를 호출하는 대신 객체 할당 및 initialize 호출에 필요한 바이트코드를 직접 생성합니다. 이 최적화의 주요 이점은 다음과 같습니다. 첫째, Class#new에 대한 별도의 스택 프레임을 제거하여 스택 오버헤드를 줄입니다. 둘째, 매개변수 복사 없이 스택에 있는 매개변수를 initialize 메서드가 직접 소비할 수 있게 하여 효율성을 높입니다. 셋째, new 호출마다 initialize 호출 사이트가 생기므로 인라인 캐시 적중률이 크게 향상됩니다. 물론 이 최적화에는 몇 가지 단점도 있습니다. 명령어 수가 증가하여 메모리 사용량이 소폭 증가하지만, 이는 new를 사용하는 호출 사이트 수에 비례하며 전체 힙 크기 대비 매우 미미한 수준(0.5%)입니다. 또한, caller 스택 트레이스에서 Class#new 프레임이 사라지는 작은 하위 호환성 문제가 발생할 수 있습니다.

결론

Ruby 3.5의 객체 할당 속도 향상은 `Class#new` 메서드의 인라이닝과 `opt_new`와 같은 새로운 YARV 명령어 도입을 통해 이루어진 중요한 성과입니다. 이러한 최적화는 특히 키워드 매개변수를 사용하는 경우에 상당한 성능 개선을 가져오며, 스택 프레임 제거, 매개변수 복사 방지, 인라인 캐시 적중률 향상이라는 다각적인 이점을 제공합니다. 비록 작은 하위 호환성 문제와 메모리 사용량 증가가 있지만, 전반적인 성능 향상 효과는 그 단점을 상회합니다. 이 글의 저자는 Koichi Sasada와 John Hawthorn에게 감사를 표하며, Ruby 3.5의 정식 출시를 통해 개발자들이 더욱 효율적인 Ruby 애플리케이션을 구축할 수 있게 될 것이라는 기대를 나타냅니다.

댓글 0

댓글 작성

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

아직 댓글이 없습니다

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