Ruby VM에서 인스턴스 변수는 객체 유형에 따라 다르게 저장됩니다. ‘immediates’는 인스턴스 변수를 가질 수 없으며, T_OBJECT는 객체 슬롯 내에 배열처럼 저장됩니다. T_CLASS와 T_MODULE은 ‘companion’ 슬롯에 인스턴스 변수를 저장합니다. 그러나 String, Array, Hash와 같은 다른 객체 유형(T_STRING, T_ARRAY, T_HASH 등)은 객체 슬롯이 이미 다른 데이터로 사용되므로, 인스턴스 변수를 저장하기 위해 ‘제네릭 인스턴스 변수 해시 테이블(generic_fields_tbl_)’이라는 전역 해시 테이블을 사용합니다. 이 해시 테이블 접근 방식은 해시 조회 비용이 높고, 다중 Ractor 환경에서는 VM 잠금을 획득해야 하므로 심각한 성능 병목 현상을 초래했습니다.
이러한 문제를 해결하기 위해 여러 최적화 방안이 모색되었습니다. 초기에는 T_STRUCT 객체의 인스턴스 변수를 ‘모양(shapes)’으로 인코딩하여 멤버와 변수를 함께 배치하는 아이디어가 있었으나, 복잡한 모양(complex shapes) 처리의 어려움과 Struct 객체의 배열과 같은 특성으로 인해 포기되었습니다. 대신, Struct 객체 슬롯 내의 여유 공간에 인스턴스 변수 버퍼에 대한 직접 참조를 저장하는 ‘직접 참조(Direct References)’ 방식이 도입되었습니다. 이는 T_CLASS와 유사한 전략으로, T_STRUCT 객체에 대한 인스턴스 변수 접근 시 전역 해시 테이블과 VM 잠금을 우회하여 성능을 크게 향상시켰습니다.
T_DATA 객체(C 확장 기능에서 사용)의 경우, 기존 RTypedData 구조체의 ‘typed_flag’ 필드가 1비트 정보를 저장하는 데 8바이트를 비효율적으로 사용하고 있었던 점에 주목했습니다. 최근 Jeremy Evans가 Set 클래스를 C로 재구현하면서 이 2비트 정보를 포인터의 하위 비트(low bits)로 이동시켜 8바이트의 공간을 확보한 사례가 있었습니다. 이 공간을 T_IMEMO/fields에 대한 직접 참조를 저장하는 데 활용하여 T_DATA 객체의 인스턴스 변수 접근 성능을 개선할 가능성이 논의되었습니다.
또한, T_STRING, T_ARRAY, T_HASH와 같이 슬롯 내 여유 공간을 확보하기 어려운 유형에 대해서는 ‘조회 캐시(Lookup Cache)’ 아이디어가 제안되었습니다. 이는 파이버(Fiber) 로컬 저장소에 마지막으로 조회한 객체와 해당 T_IMEMO/fields를 캐시하여 해시 조회 횟수를 줄이는 방식입니다. ActiveSupport::SafeBuffer의 경우, @html_safe = true
대신 @html_unsafe = false
로 변경하여 인스턴스 변수 설정을 아예 회피함으로써 String#html_safe 메서드의 성능을 두 배 향상시키는 등, Ruby 코어뿐만 아니라 상위 라이브러 수준에서도 성능 최적화가 가능함을 보여주었습니다.