Ruby 1.9.3의 GVL 개선

[17S10] CRuby lock design improvement and why it sucks (ja)

작성자
RubyKaigi
발행일
2025년 10월 05일

핵심 요약

  • 1 Ruby 1.9.3에서는 현대적인 하드웨어 및 OS 환경과의 불일치로 발생하던 GVL(Global Virtual Lock)의 불공정성과 성능 문제를 해결하기 위해 GVL 해제 로직을 하이브리드 방식으로 개선했습니다.
  • 2 기존 Ruby 1.9.2의 GVL 구현은 단순했지만, `sched_yield`의 OS 스케줄러 정책 변화와 NUMA 아키텍처 같은 하드웨어 트렌드와 맞지 않아 특정 상황에서 스레드 기아 현상이나 성능 저하를 초래했습니다.
  • 3 새로운 GVL 구현은 I/O에 의한 자발적 해제 시에는 기존의 경합 방식을 유지하고, 실행 시간 만료에 의한 강제 해제 시에는 다른 스레드에게 GVL을 양보하는 파이썬식 공정성 로직을 적용하여 최악의 성능 저하를 방지하고 예측 가능한 동작을 보장합니다.

도입

발표자는 Ruby 1.9.3의 GVL(Global Virtual Lock) 구현 변경에 대해 설명합니다. GVL은 Ruby의 멀티스레딩 모델에서 중요한 역할을 하며, 스크립트 작성의 용이성을 제공하지만, 멀티코어 환경에서의 성능 확장성에는 한계가 있습니다. 특히, 기존 Ruby 1.9.2 버전에서는 특정 스레드 간 통신 시 교착 상태나 성능 불균형이 발생하는 문제가 발견되었고, 이는 현대적인 CPU 아키텍처와 OS 스케줄러의 변화에 따른 GVL 디자인의 한계 때문이었습니다. 이 발표는 이러한 문제의 원인을 분석하고 Ruby 1.9.3에서 적용된 GVL 개선 방안과 그 성능적 함의를 다룹니다.

Ruby 1.9.2 이전 GVL 디자인의 문제점

  • 단순한 GVL 해제 로직: GVL은 스레드가 계속 보유하다가 I/O 같은 시스템 호출 시에만 해제되었습니다. CPU 연산만 하는 스레드는 GVL을 독점하여 다른 스레드 전환이 어려웠고, 타이머 스레드의 강제 해제 시도에도 한계가 있었습니다.

  • 하드웨어 및 OS 스케줄러와의 불일치: NUMA 아키텍처 같은 다중 CPU 환경에서 GVL 해제 후 즉시 재획득 시 불공정성이 발생했습니다. sched_yield 시스템 호출은 현대 OS 스케줄러(특히 RHEL6)에서 의도한 스레드 양보를 제공하지 못했으며, 이는 과거 Java Thread.yield() 남용 경험에 영향을 받았습니다.

Ruby 1.9.3의 GVL 개선 방안 (하이브리드 접근)

Ruby 1.9.3에서는 GVL 해제 로직을 두 가지 상황으로 나누어 처리하는 하이브리드 방식을 채택했습니다.

  • I/O에 의한 자발적 GVL 해제: I/O 작업 등으로 GVL을 자발적으로 해제하는 경우, 다음 GVL 획득은 기존 1.9.2와 유사한 경합 방식으로 진행되어 I/O 시 스레드 전환 오버헤드를 최소화합니다.

  • 실행 시간 만료에 의한 강제 GVL 해제: 타이머 스레드에 의해 GVL 보유 시간이 만료되어 강제로 해제될 때는 파이썬과 유사한 공정성 로직을 적용하여, 해당 스레드가 즉시 재획득하지 못하고 다른 스레드가 GVL을 획득할 때까지 대기하도록 합니다.

성능 영향

  • pass_thread 벤치마크에서는 1.9.2의 비효율적인 비지 루프가 개선되어 성능이 향상되었습니다.

  • pipe 벤치마크에서는 공정성 로직 도입으로 스레드 전환 횟수가 증가하여 1.9.2보다 다소 느려졌습니다.

  • Mutex 지속 경합 시나리오에서는 락 획득/해제 비용 증가로 1.9.2 대비 2배까지 느려질 수 있으나, 일반적인 환경에서는 체감하기 어렵습니다.

  • 가장 중요한 개선점은 1.9.2에서 발생했던 극심한 불공정성으로 인한 벤치마크의 비정상적인 장기 실행 문제가 1.9.3에서 개선되어 예측 가능한 동작을 보장하게 된 것입니다.

결론

Ruby 1.9.3의 GVL 개선은 현대적인 다중 코어 하드웨어 및 OS 환경에서 발생할 수 있는 멀티스레딩의 불공정성 문제를 해결하려는 중요한 시도였습니다. 기존 1.9.2의 단순한 GVL 구현이 특정 시나리오에서 스레드 기아 현상이나 예측 불가능한 성능 저하를 초래했던 점을 인식하고, 자발적 해제와 강제 해제 상황에 따라 다른 공정성 로직을 적용하는 하이브리드 접근 방식을 도입했습니다. 비록 일부 벤치마크에서는 성능 저하가 관찰되었으나, 이는 주로 락을 지속적으로 경합하는 비효율적인 코드 패턴에서 나타나는 현상으로, 실제 사용 환경에서는 스레드 간 공정성을 확보하고 극단적인 성능 편차를 줄이는 데 기여했습니다. Ruby 개발팀은 이러한 내부적인 노력을 통해 개발자들이 복잡한 멀티스레딩 문제를 걱정 없이 스크립트를 작성할 수 있도록 지원하고 있습니다.

댓글 0

로그인이 필요합니다

댓글을 작성하거나 대화에 참여하려면 로그인이 필요합니다.

로그인 하러 가기

아직 댓글이 없습니다

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