Ruby 3.4의 불필요한 암시적 할당 제거 최적화

[EN] Eliminating Unnecessary Implicit Allocations / Jeremy Evans @jeremyevans0

3줄 요약

  • Ruby 3.4는 이전 버전에서 발생했던 불필요한 객체 할당(allocation regressions)을 제거하고, 향후 회귀 방지를 위한 할당 테스트 스위트를 도입했습니다.
  • 대규모 리터럴 배열 및 스플랫(splat)을 포함한 리터럴 배열의 할당을 단일 배열로 최적화하여 메모리 효율성을 크게 향상시켰습니다.
  • 호출자 측 위치 스플랫 할당을 컴파일러 수준에서 최적화하고 평가 순서 문제를 해결함으로써 전반적인 Ruby 성능을 개선했습니다.

Jeremy Evans는 Ruby 3.4에서 불필요한 암시적 할당을 제거하기 위한 코드 변경 사항을 심층적으로 발표했습니다. 이 발표는 Ruby의 성능과 메모리 효율성을 개선하는 데 중점을 두며, 특히 Ruby 3에서 발생했던 할당 회귀(allocation regressions) 문제를 해결하고, 향후 유사한 문제가 재발하는 것을 방지하기 위한 체계적인 접근 방식을 소개합니다. 그의 작업은 Ruby 개발의 안정성과 효율성을 한 단계 끌어올리는 데 중요한 기여를 했습니다.

발표자는 먼저 Ruby 3에서 자신이 유발했던 세 가지 주요 할당 회귀 사례를 설명하고, 이들이 Ruby 3.4에서 다시 할당이 발생하지 않도록 수정되었음을 강조했습니다. 첫째, 위치 스플랫, 키워드 스플랫, 블록을 함께 사용할 때의 불필요한 배열 할당, 둘째, 키워드 인자를 받는 메서드를 키워드 스플랫으로 호출할 때의 해시 할당, 셋째, 위치 스플랫과 정적 리터럴 키워드를 함께 사용할 때의 해시 할당이 모두 제거되었습니다. 이러한 회귀를 방지하고 새로운 할당 문제를 조기에 식별하기 위해 ‘할당 테스트 스위트’를 개발하여 도입했습니다. 이 테스트 스위트는 특정 호출 유형에 대한 예상 객체 할당 수를 검증하며, 회귀 방지, Prism 컴파일러의 할당 문제 발견 및 수정, 그리고 불필요한 할당 사례 식별에 크게 기여했습니다.

이어서 리터럴 배열 최적화에 대해 상세히 설명합니다. Ruby 3.3에서는 대규모 리터럴 배열(예: 257개 이상의 요소)이 VM 스택 오버플로우 방지를 위해 256개 단위로 청킹되어 여러 개의 배열을 할당했습니다. Ruby 3.4에서는 push_to_array VM 명령어를 활용하여 모든 요소를 단일 배열에 추가함으로써 단일 배열 할당으로 최적화했습니다. 또한, 위치 스플랫이 포함된 리터럴 배열의 경우, Ruby 3.3에서 여러 배열이 할당되었던 것을 push_to_array와 새로운 concat_2_array 명령어(새로운 배열을 할당하지 않음)를 사용하여 Ruby 3.4에서는 단일 배열 할당으로 줄였습니다. 키워드 스플랫이 포함된 리터럴 배열에서는 concat_array의 한계로 불필요한 할당이 발생했으나, 새로운 push_to_array_keyword_splat VM 명령어를 도입하여 빈 해시가 아닌 경우 객체를 추가함으로써 단일 배열 할당을 가능하게 했습니다. 이와 함께 빈 키워드 스플랫이 배열에 포함되던 버그도 Ruby 3.2.1/3.3 이상에서 수정되었습니다.

마지막으로 호출자 측 위치 스플랫 할당 최적화에 대해 논의합니다. Ruby 3.3의 기존 최적화 도구로는 동적 해시나 비정적 키워드를 사용하는 경우의 호출자 측 위치 스플랫 할당을 처리할 수 없었습니다. Ruby 3.4에서는 이 최적화를 컴파일러(setup_args_core 함수) 수준으로 이동시켰습니다. 특히, dup_rest 인자를 포인터로 변경하여 재귀 호출 전반에 걸쳐 할당 필요 정보를 효과적으로 추적할 수 있게 했습니다. 발표자는 단일 위치 스플랫만 사용되거나 특정 조건에서 키워드 인자나 블록이 사용될 때 배열 할당을 피할 수 있는 네 가지 구문 분석 트리 사례를 식별했습니다. 또한, 메서드 호출이나 특정 상수 참조와 같이 평가 순서 문제를 야기할 수 있는 경우를 검사하는 함수를 추가하여 안전하게 할당을 피할 수 있도록 했습니다. 키워드 스플랫과 블록 전달 표현식에서 변이가 발생하는 평가 순서 문제를 해결하기 위해 dup_rest가 두 가지 정보를 추적하도록 확장하고, compile_single_keyword_splats 함수를 통해 키워드 스플랫 해시를 복제하도록 하여 문제를 해결했습니다. 이러한 변경 덕분에 Ruby 3에서 불필요하게 배열을 할당했던 모든 호출자 측 위치 스플랫 사례가 Ruby 3.4에서는 할당이 발생하지 않게 되었으며, 기존의 최적화 도구 변경 사항도 제거될 수 있었습니다.

Ruby 3.4의 이러한 심층적인 최적화 작업은 불필요한 객체 할당을 대폭 줄여 Ruby 애플리케이션의 메모리 사용량과 전반적인 성능을 획기적으로 향상시켰습니다. 특히, 할당 테스트 스위트의 성공적인 도입은 향후 Ruby 개발에서 회귀를 방지하고 새로운 최적화 기회를 지속적으로 식별하는 데 중요한 기반을 마련했습니다. 발표자는 여전히 일부 호출자 및 피호출자 측의 암시적 할당이 Ruby의 내부 API 한계로 인해 해결하기 어렵다고 언급했지만, 이번 발표에서 다룬 개선 사항들은 Ruby의 효율성을 한 단계 끌어올리는 데 지대한 역할을 했습니다. 이 발표는 Ruby 개발자들이 Ruby 3.4의 성능 개선에 대한 깊이 있는 이해를 돕고, 더욱 효율적이고 최적화된 코드를 작성하는 데 실질적인 기여를 할 것입니다.