Ruby 1.9.2 이전 GVL 디자인의 문제점
-
단순한 GVL 해제 로직: GVL은 스레드가 계속 보유하다가 I/O 같은 시스템 호출 시에만 해제되었습니다. CPU 연산만 하는 스레드는 GVL을 독점하여 다른 스레드 전환이 어려웠고, 타이머 스레드의 강제 해제 시도에도 한계가 있었습니다.
-
하드웨어 및 OS 스케줄러와의 불일치: NUMA 아키텍처 같은 다중 CPU 환경에서 GVL 해제 후 즉시 재획득 시 불공정성이 발생했습니다.
sched_yield시스템 호출은 현대 OS 스케줄러(특히 RHEL6)에서 의도한 스레드 양보를 제공하지 못했으며, 이는 과거 JavaThread.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에서 개선되어 예측 가능한 동작을 보장하게 된 것입니다.