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의 동시성 및 성능 개선에 크게 기여할 수 있음을 시사합니다.