Aaron Patterson은 Ruby의 `Class#new` 메서드 최적화에 대한 새로운 접근 방식을 제시합니다. `Class#new`는 Ruby on Rails와 같은 애플리케이션에서 객체 생성의 핵심적인 역할을 수행하며, 이 메서드의 효율성은 전체 애플리케이션의 성능에 지대한 영향을 미칩니다. 본 발표는 Ruby가 C 언어의 속도를 능가할 수 있는 가능성을 탐구하고, 특히 `Class#new`를 Ruby 자체로 재구현함으로써 성능을 획기적으로 개선하는 방법에 초점을 맞춥니다.
현재 Class#new
의 C 구현은 Ruby와 C 간의 복잡한 호출 과정을 포함하고 있습니다. Ruby 코드에서 Class#new
를 호출하면 C 함수가 실행되고, 이 C 함수는 다시 Ruby로 작성된 initialize
메서드를 호출하는 구조입니다. 이 과정에서 언어 간의 호출 규약(Calling Convention) 전환이 빈번하게 발생하며, 이는 상당한 오버헤드를 초래합니다. 또한, 메서드 탐색 속도를 높이는 인라인 캐시(Inline Cache)는 Ruby에서 C 메서드를 호출할 때도 사용되어 성능에 영향을 미칩니다. Ruby와 C는 인자 전달 방식이 상이하므로, 키워드 인자가 C에서는 해시로 변환되는 등 추가적인 데이터 변환 비용이 발생합니다.
발표자는 이러한 문제점을 해결하기 위해 Class#new
를 Ruby로 직접 재구현하는 방안을 제안합니다. 초기 시도에서는 initialize
메서드의 private
접근 제한과 BasicObject
에 send
메서드가 없다는 문제에 직면합니다. 이를 극복하고자, send
명령어에 Fcall
플래그를 추가하는 새로운 프리미티브(Primitive)와 객체 할당을 위한 새로운 프리미티브를 도입하여 allocate
메서드의 몽키 패치를 방지합니다. 궁극적인 최적화 방안은 Class#new
의 바이트코드를 new
호출 지점에 직접 인라인(Inline)하는 것입니다. 이는 컴파일러 수준에서 new
호출을 감지하고 Class#new
의 명령어를 삽입함으로써, 불필요한 함수 호출 오버헤드를 제거하는 혁신적인 접근 방식입니다.
다양한 인자 수와 타입(일반 인자, 키워드 인자, 다른 클래스 객체)에 대한 성능 벤치마크 결과는 놀라웠습니다. Ruby 3.5에서 인라인화된 Class#new
는 Ruby 3.4 대비 최소 1.4배에서 최대 6.2배까지 향상된 성능을 기록했습니다. 특히 키워드 인자 수가 증가할수록 성능 향상 폭이 더욱 커지는 경향을 보였는데, 이는 언어 간 호출 규약 전환 비용이 크게 줄어들었음을 명확히 보여줍니다.
물론, 이러한 최적화에는 몇 가지 단점도 존재합니다. allocate
메서드의 메모리 사용량이 약 12% 증가하고, 스택 트레이스에서 Class#new
프레임이 사라지는 현상이 발생합니다. 그러나 이러한 단점들은 전체 코드베이스에 미치는 영향이 미미하며, 전반적인 성능 향상에 비하면 충분히 감수할 수 있는 수준으로 평가됩니다.
본 발표를 통해 우리는 `Class#new`의 C 구현 방식, 인라인 캐시의 작동 원리, 호출 규약의 중요성, 그리고 Ruby로 `Class#new`를 재구현했을 때의 성능 특성을 심층적으로 이해할 수 있었습니다. Aaron Patterson의 연구는 Ruby 내부 구조에 대한 깊은 통찰력을 제공하며, 개발자들이 Ruby 프로그래밍을 더욱 효율적이고 즐겁게 할 수 있도록 기여하는 중요한 발걸음입니다. 이러한 최적화 노력은 Ruby가 계속해서 발전하고 더 넓은 분야에서 활용될 수 있는 기반을 마련합니다.