Ruby Ractor 로컬 가비지 컬렉션: 병렬 처리를 통한 성능 향상

[EN] Toward Ractor local GC / Koichi Sasada @ko1

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

핵심 요약

  • 1 Ruby Ractor의 병렬 처리를 위한 로컬 가비지 컬렉션 도입은 성능 향상에 유망하나, 공유 가능한 객체(Sharable Objects) 관리의 복잡성으로 인해 큰 도전 과제를 안고 있습니다.
  • 2 제안된 보수적인 접근 방식은 공유 가능한 객체를 항상 살아있는 루트 객체로 간주하고, 필요 시 전역 가비지 컬렉션으로 대체하여 안전성과 성능 향상을 동시에 달성합니다.
  • 3 마이크로벤치마크 결과, Ractor 로컬 가비지 컬렉션은 기존 전역 가비지 컬렉션 대비 최대 5배의 상당한 성능 개선을 보여주며, 특히 Ractor 수가 증가할수록 효율성이 두드러집니다.

도입

Ruby 3.0에 도입된 Ractor는 병렬 컴퓨팅과 견고한 동시성 프로그래밍을 가능하게 하는 새로운 동시성 모델입니다. Ractor는 각기 격리된 객체 공간을 가지므로, 공유 가능한 변경 가능한 상태로 인한 일반적인 동시성 버그를 피할 수 있습니다. 이러한 격리된 객체 공간의 특성상, 각 Ractor가 독립적으로 또는 병렬로 가비지 컬렉션(GC)을 수행하는 'Ractor 로컬 가비지 컬렉션'의 개념이 자연스럽게 제안되었습니다. 이는 Ruby 애플리케이션의 전반적인 성능과 효율성을 크게 향상시킬 잠재력을 가지고 있습니다.

Ractor 로컬 GC의 핵심 과제: 공유 가능한 객체 관리

Ractor 로컬 GC의 가장 큰 난관은 클래스, 모듈, 클로저 객체, Ractor 객체 등 Ractor 간에 공유될 수 있는 ‘공유 가능한 객체(Sharable Objects)’를 처리하는 방식입니다. 각 Ractor가 독립적으로 GC를 수행할 때, 다른 Ractor에서 참조하고 있는 공유 가능한 객체가 해당 Ractor의 GC 범위 내에서 참조되지 않아 잘못 해제될 위험이 있습니다. 즉, A Ractor가 소유한 공유 객체 sh1을 B Ractor가 참조하고 있을 때, A Ractor의 로컬 GC가 sh1을 해제해버리면 B Ractor에서 접근 시 문제가 발생합니다.

참조 추적의 어려움

이러한 문제를 해결하기 위해 Ractor 간의 참조 정보를 정확하게 추적하는 것이 이상적이지만, 실제 구현에서는 매우 어렵습니다. 참조는 언제든지 업데이트될 수 있고 Ractor는 병렬로 실행되기 때문에, 참조 상태를 모든 Ractor에 걸쳐 정확하게 유지하는 것은 복잡성과 성능 오버헤드를 야기합니다. 잘못된 추적 정보는 살아있는 객체를 실수로 해제하는 치명적인 버그로 이어질 수 있습니다.

보수적인 접근 방식 제안

발표자는 이러한 참조 추적의 어려움을 인정하고, ‘보수적인 접근 방식’을 제안합니다. 이 방식은 다음과 같습니다:

  • 로컬 GC 시 공유 가능한 객체는 항상 살아있는 것으로 간주: Ractor 로컬 GC가 실행될 때, 공유 가능한 객체로 표시된 모든 객체는 GC 루트로 간주되어 항상 살아있는 상태로 유지됩니다. 이는 참조 누락으로 인한 객체 해제 오류를 방지합니다.
  • 전역 GC로의 폴백: 이 접근 방식은 메모리 해제가 지연될 수 있다는 단점이 있지만, 공유 가능한 객체의 수가 상대적으로 적고 대부분 수명이 길다는 점을 고려하면 실용적입니다. 공유 가능한 객체의 메모리 해제가 필요할 때는 기존의 ‘전역 가비지 컬렉션’을 사용합니다. 전역 GC는 모든 Ractor를 중지시키고 전체 객체 공간을 스캔하여 정확하게 참조를 파악하고 해제합니다. 이 방식은 느리지만, 로컬 GC보다 훨씬 적은 빈도로 실행되므로 전체 성능에 미치는 영향이 최소화됩니다.

마이크로벤치마크를 통한 성능 검증

제안된 Ractor 로컬 GC는 여러 마이크로벤치마크를 통해 성능이 검증되었습니다:

  • 단명 객체 할당: Ractor 수가 증가할수록 기존 마스터 버전은 병렬 처리 이점을 거의 얻지 못하고 성능이 정체되거나 저하되는 반면, 로컬 GC 버전은 최대 5배 가까운 속도 향상을 보였습니다. 이는 로컬 GC가 단명 객체 처리에 매우 효과적임을 보여줍니다.
  • 장명 객체 할당: 많은 배열을 생성하고 유지하는 시나리오에서도 기존 마스터 버전은 Ractor 수가 증가할수록 성능이 오히려 악화되는 병목 현상을 보였습니다. 반면, 로컬 GC 버전은 16개의 Ractor에서 4배 이상의 속도 향상을 달성하며 효율적인 장명 객체 관리를 입증했습니다.
  • 정규 표현식 매칭: 정규 표현식 매칭과 같은 복잡한 작업에서도 로컬 GC는 기존 버전에 비해 향상된 성능을 보여주었습니다.

이러한 결과는 보수적인 Ractor 로컬 GC 접근 방식이 Ruby의 동시성 및 성능 개선에 크게 기여할 수 있음을 시사합니다.

결론

Ractor 로컬 가비지 컬렉션은 Ruby의 병렬 처리 성능을 극대화하기 위한 필수적인 단계입니다. 비록 공유 가능한 객체에 대한 완벽한 참조 추적이 어렵다는 문제에 직면했지만, 공유 가능한 객체를 항상 살아있는 것으로 간주하고 전역 GC를 보조적으로 활용하는 보수적인 접근 방식은 실제 마이크로벤치마크에서 유의미한 성능 향상을 입증했습니다. 이는 '완벽한 것보다 완성하는 것이 낫다(Done is better than perfect)'는 철학을 보여주며, Ruby 3.5에 Ractor 로컬 GC를 도입하기 위한 중요한 진전입니다. 향후 과제로는 구현 완료, 전역 GC의 병렬 마킹 도입, 그리고 장기적으로는 공유 가능한 객체에 대한 더욱 정교한 참조 추적 알고리즘 개발이 있습니다.

댓글 0

댓글 작성

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

아직 댓글이 없습니다

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