Ruby Class#new 메서드의 성능 최적화: 새로운 접근 방식

[JA] Speeding up Class#new / Aaron Patterson @tenderlove

작성자
RubyKaigi
발행일
2025년 05월 27일

핵심 요약

  • 1 본 발표는 Ruby의 핵심 메서드인 `Class#new`의 성능 최적화에 대한 새로운 접근 방식을 다룹니다.
  • 2 C로 구현된 현재 `Class#new`의 내부 동작, 인라인 캐시, 그리고 호출 규약의 영향을 심층 분석합니다.
  • 3 Ruby에서 `Class#new`를 재구현하고 인라인화 기법을 적용하여 상당한 성능 향상을 달성했음을 보여줍니다.

도입

Aaron Patterson이 발표한 이 세션은 Ruby 프로그래밍 언어의 핵심인 `Class#new` 메서드의 성능 최적화를 목표로 합니다. Ruby 개발자로서 그는 Ruby를 더 많은 사람이 사용하고, 기존 사용자들이 계속해서 Ruby를 사용할 수 있도록 성능 개선에 힘쓰고 있습니다. 특히 `Class#new`는 Rails 애플리케이션에서 요청을 처리할 때 수많은 객체를 생성하는 데 사용되는 매우 빈번한 메서드이므로, 이 메서드의 성능 개선은 전체 애플리케이션 속도 향상에 크게 기여할 수 있습니다. 본 발표는 `Class#new`가 C 언어로 어떻게 구현되어 있는지 분석하고, Ruby에서 이를 재구현함으로써 C보다 더 빠른 성능을 달성할 수 있음을 보여줍니다.

현재 Class#new는 C 언어로 구현되어 있으며, 객체 할당 후 initialize 메서드를 호출하는 방식으로 작동합니다. 이 과정에서 Ruby에서 C를 호출하고 다시 C에서 Ruby를 호출하는 언어 간의 전환이 발생하며, 이는 성능 오버헤드를 야기합니다. 이러한 오버헤드를 줄이기 위해 발표자는 인라인 캐시(Inline Cache)와 호출 규약(Calling Convention)이라는 두 가지 핵심 요소를 설명합니다.

인라인 캐시는 메서드 탐색 속도를 높이는 역할을 합니다. Ruby는 메서드를 찾을 때 해당 클래스부터 상위 클래스까지 계층적으로 탐색하는데, 이는 시간이 많이 소요되는 작업입니다. 인라인 캐시는 이전에 탐색했던 메서드의 위치를 캐싱하여 다음 호출 시 빠르게 접근할 수 있도록 돕습니다. 캐시 히트율이 높을수록 성능이 향상되며, 동일한 클래스에서 반복 호출 시 캐시 히트가 발생합니다. 그러나 다른 클래스로 전환하면 캐시 미스가 발생하여 성능이 저하될 수 있습니다. C 언어에서 Ruby 메서드를 호출할 때 사용되는 캐시는 Ruby 프로그램 내부의 인라인 캐시와 달리 전역 테이블에 저장되며, 이는 캐시 제한을 가질 수 있습니다.

호출 규약은 함수 호출 시 인자와 반환 값을 전달하고 찾는 규칙을 의미합니다. Ruby의 VM은 스택을 사용하여 인자를 전달하며, 키워드 인자의 경우 순서를 재배열하는 과정에서 추가적인 비용이 발생할 수 있습니다. C 언어는 키워드 인자를 직접 지원하지 않으므로, Ruby에서 C 함수를 호출할 때 키워드 인자는 해시로 변환되어 전달되고, 다시 C에서 Ruby를 호출할 때는 이 해시를 스택으로 재변환해야 합니다. 이러한 인자 변환 과정 또한 성능 저하의 원인이 됩니다.

발표자는 이러한 문제점들을 해결하기 위해 Class#new를 Ruby로 재구현하는 새로운 접근 방식을 제안합니다. 초기 시도는 initialize 메서드가 private이라는 문제에 부딪혔고, allocate를 직접 호출하지 않고 객체를 할당해야 하는 과제가 있었습니다. 이를 해결하기 위해 Ruby 컴파일러가 특별히 처리할 수 있는 새로운 ‘프리미티브(primitive)’를 도입했습니다. 이 프리미티브는 메서드의 가시성 검사를 우회하고, allocate 메서드를 호출하지 않고도 객체를 직접 할당할 수 있도록 합니다. 궁극적인 최적화는 Class#new의 명령어를 호출자의 바이트코드에 직접 ‘인라인(inline)’하는 것입니다. 이 전략은 Class#new에 대한 별도의 메서드 호출 자체를 없애, 언어 간 전환 및 호출 규약의 오버헤드를 제거합니다.

성능 벤치마크 결과는 이러한 인라인화 기법이 매우 효과적임을 보여줍니다. 인라인화된 Ruby 3.5는 Ruby 3.4 대비 인자의 개수가 증가할수록 더욱 큰 폭의 성능 향상을 보였습니다. 특히 키워드 인자를 사용할 경우 3개 인자에서 3.2배, 10개 인자에서 6.2배까지 빨라지는 놀라운 결과를 보여주었습니다. 인자의 타입과 개수에 따라 성능 향상 폭은 달라지지만, 최소 1.4배의 속도 향상이 확인되었습니다.

물론 이러한 인라인화에는 단점도 존재합니다. 첫째, 메모리 사용량이 증가합니다. allocate 메서드의 메모리 사용량은 인라인화 후 약 12배 증가했지만, 전체 코드에서 차지하는 비중은 미미하다고 설명합니다. 둘째, 스택 트레이스(stack trace)가 변경됩니다. 인라인화로 인해 Class#new의 프레임이 스택 트레이스에서 사라지지만, 이는 허용 가능한 수준의 변화로 간주됩니다.

결론

결론적으로, 본 발표는 Ruby의 `Class#new` 메서드에 대한 심층적인 분석을 통해 성능 최적화의 가능성을 제시했습니다. C 구현의 한계를 파악하고, 인라인 캐시의 역할과 호출 규약의 영향을 이해하는 것이 중요함을 강조했습니다. 나아가 새로운 프리미티브와 인라인화 기법을 활용하여 Ruby에서 `Class#new`를 재구현함으로써 상당한 성능 향상을 달성할 수 있음을 입증했습니다. 이러한 최적화는 Ruby 내부 구조에 대한 깊은 이해를 바탕으로 이루어졌으며, Ruby 개발자들이 더욱 즐겁게 프로그래밍할 수 있도록 기여할 것입니다.

댓글 0

댓글 작성

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

아직 댓글이 없습니다

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