Ruby 3.4의 새로운 기능: 모듈형 가비지 컬렉션 및 MMTk

New for Ruby 3.4: Modular Garbage Collection and MMTk | Rails at Scale

3줄 요약

  • Ruby 3.4는 런타임에 GC 구현체를 교체할 수 있는 '모듈형 GC' 기능을 도입합니다.
  • 첫 번째 모듈형 GC 구현체로 언어 독립적인 메모리 관리 라이브러리인 MMTk 기반의 새로운 GC가 포함됩니다.
  • 이 변화는 Ruby GC의 유연성과 발전 가능성을 크게 높이며, 최신 GC 알고리즘 도입의 발판을 마련합니다.

Ruby에서 가비지 컬렉션(GC)은 항상 성능 최적화를 위한 중요한 논의 주제였습니다. 과거부터 튜닝 전략이나 최적화 패치가 제안되어 왔으며, 이는 Ruby 언어 발전의 오랜 역사와 함께 해왔습니다. 2023년 RubyKaigi에서 Ruby GC의 역사와 진화에 대해 발표했던 Shopify는 Ruby GC를 더 쉽게 수정하고 메모리 관리 연구의 최신 동향을 반영할 수 있도록 개선 작업을 진행해 왔습니다. 기쁘게도 이러한 목표를 위해 Ruby 3.4에서 상당한 진전을 이루었습니다. Ruby는 이제 처음으로 런타임에 기본 Mark & Sweep 컬렉터를 대체할 수 있는 모듈형 GC 기능과, 이 기능을 활용하는 완전히 새로운 GC 구현체를 함께 제공합니다. 본문에서는 이 두 가지 새로운 개념, 즉 Ruby VM이 다양한 GC 라이브러리와 상호작용하는 방식과 범용 메모리 관리 라이브러리인 MMTk 기반의 새로운 GC 구현체에 대해 자세히 설명합니다.

새로운 모듈형 GC 기능은 Ruby의 기존 GC 코드 위에 표준화된 인터페이스를 도입하고 구현 코드를 별도의 컴파일 단위로 분리함으로써 가능해졌습니다. 이는 gc.c 파일에서 VM의 나머지 부분에 공개되어야 하는 부분과 현재 GC 구현의 세부 사항을 구분하는 작업을 포함했습니다. 기존 GC를 성공적으로 분리한 후, 우리는 재컴파일이나 재링크 없이 구현체를 오버라이드하는 기능을 개발했습니다. 이 방식은 dlopen을 사용하여 런타임에 공유 라이브러리를 로드하고, 공유 라이브러리가 제공하는 구현체로 함수 포인터 맵을 생성하며, 설정된 공유 라이브러리가 없을 경우 정적으로 컴파일된 기본 GC 구현체로 폴백하는 방식을 사용합니다.

모듈형 GC는 다음과 같이 사용할 수 있습니다:

  1. Ruby 빌드 시 모듈형 GC 디렉터리 설정: ./configure --with-modular-gc=$HOME/ruby-mod-gc 와 같이 configure 플래그를 사용하여 기능을 활성화하고 GC 라이브러리를 로드할 경로를 지정합니다. 이 플래그가 없으면 기존 GC가 정적으로 컴파일됩니다.
  2. GC 라이브러리 빌드: make modular-gc MODULAR_GC=default 와 같이 make 타겟을 사용하여 GC 라이브러리를 빌드합니다. MODULAR_GC 변수에는 gc 디렉터리 하위의 GC 라이브러리 디렉터리 이름(예: default, mmtk)을 사용합니다.
  3. GC 라이브러리 로드: RUBY_GC_LIBRARY 환경 변수에 GC 라이브러리 이름(예: default, mmtk)을 설정하여 실행합니다. 라이브러리는 librubygc.[이름].[확장자] (.so 또는 .bundle) 명명 규칙을 따릅니다.
  4. 실행 중인 GC 확인: ruby -v 출력에 +GC 또는 +GC[mmtk] 형태로 모듈형 GC 활성화 여부 및 로드된 라이브러리 이름이 표시됩니다. 또한, GC.config[:implementation]을 통해 현재 실행 중인 GC의 이름을 확인할 수 있습니다.

이 기능은 기존 GC 구현체의 변경 사항을 쉽게 테스트하거나 다양한 구성을 실험하는 데 유용합니다.

한편, Ruby와 함께 제공될 새로운 GC 구현체는 Memory Management Toolkit (MMTk) 기반입니다. MMTk는 언어 독립적인 정교하고 강력한 메모리 관리 프레임워크입니다. 이는 MarkSweep, SemiSpace, Immix, LXR 등 다양한 기성 GC 전략을 제공하여 언어 개발자가 처음부터 GC를 구축할 필요 없이 활용할 수 있도록 합니다. 전통적으로 언어 구현체와 GC는 밀접하게 발전해 왔지만, 초기에는 구현이 쉬운 구식 알고리즘(참조 카운팅, 보수적 트레이싱)을 선택하는 경우가 많았고, 이는 나중에 성능 병목이 되어 변경이 어렵다는 문제가 있었습니다. MMTk는 이러한 문제를 해결하는 도구로서, MMTk와 통합하면 자체 고성능 GC를 구현하는 대신 MMTk 내의 모든 구현체와 미래의 알고리즘을 거의 공짜로 얻는 효과를 누릴 수 있습니다.

Ruby의 첫 번째 모듈형 GC 구현체로 MMTk가 선택된 것은 이러한 이점 때문입니다. MMTk GC 라이브러리 코드는 ruby/mmtk 리포지토리에 있으며, Ruby 소스 트리의 gc/mmtk 디렉터리로 동기화됩니다. 구현은 두 부분으로 구성됩니다. 첫째, Rust로 작성된 MMTk 통합 부분은 MMTk가 Ruby 객체를 가비지 컬렉트하는 방법을 알려줍니다. 둘째, C로 작성된 Ruby GC 통합 부분은 모듈형 GC API의 모든 엔드포인트를 구현합니다. 이 두 부분은 make modular-gc MODULAR_GC=mmtk 명령으로 함께 빌드 및 링크됩니다.

MMTk 기반 GC는 RUBY_GC_LIBRARY=mmtk 환경 변수로 로드되며, MMTK_HEAP_MODE, MMTK_HEAP_MIN, MMTK_HEAP_MAX, MMTK_PLAN, MMTK_THREADS와 같은 환경 변수를 통해 다양한 MMTk 관련 매개변수를 설정할 수 있습니다. 현재는 MarkSweep 플랜만 지원되지만, 향후 Immix, LXR 등 MMTk가 지원하는 모든 플랜을 지원할 계획입니다.

현재 이 작업은 매우 실험적 단계이며, 프로덕션 워크로드에 대한 테스트나 성능 최적화가 충분히 이루어지지 않았습니다. 따라서 Ruby 버전 관리자를 통해 쉽게 사용할 수 있는 옵션은 아니며, 3.4.0 소스나 git 리포지토리에서 직접 빌드해야 합니다. 단기적인 우선순위는 모듈형 GC 시스템 전반과 MMTk 구현체의 성능을 개선하는 것입니다. 프로덕션 워크로드 및 내부 시스템에 대한 테스트를 시작하고 Immix, LXR과 같은 고급 MMTk 알고리즘 지원을 목표로 하고 있습니다. 이러한 제한점에도 불구하고, 이 작업은 Ruby 메모리 관리의 발전에 있어 정말 중요한 단계라고 생각합니다. 유연하고 일반적인 가비지 컬렉션 추상화를 통해 가능한 강력한 기능들을 탐색할 수 있게 되었으며, 앞으로의 개발 주기를 통해 이 기능을 계속 개선해 나갈 것입니다. 마지막으로, 이 작업은 Ruby와 MMTk 커뮤니티의 많은 분들, 특히 Peter Zhu, Aaron Patterson, Eileen Uchitelle (Ruby 측) 및 Kunshan Wang, Steve Blackburn (MMTk 측)의 엄청난 노력 없이는 불가능했을 것입니다. 그들의 헌신에 깊이 감사드립니다.