GN⁺: 루비 3.3 출시
(ruby-lang.org)Ruby 3.3.0 출시
- Ruby 3.3.0 버전이 출시됨. 새로운 파서인 Prism 도입, 파서 생성기로 Lrama 사용, 순수 Ruby로 작성된 JIT 컴파일러 RJIT 추가, 특히 YJIT의 성능 개선이 이루어짐.
Prism 파서
- Prism은 Ruby 언어를 위한 이식 가능하고, 오류에 강하며, 유지보수가 용이한 재귀 하향 파서로 기본 gem으로 제공됨.
- Prism은 생산 환경에 적합하며 활발히 유지보수되고 있으며, Ripper를 대체하여 사용 가능함.
- Prism 사용법에 대한 상세한 문서 제공됨.
- Prism은 CRuby 내부에서 사용되는 C 라이브러리이자 Ruby 코드를 파싱할 필요가 있는 모든 도구에서 사용할 수 있는 Ruby gem임.
- Prism API의 주요 메소드로는
Prism.parse(source)
,Prism.parse_comments(source)
,Prism.parse_success?(source)
등이 있음. - Prism 저장소에 직접 pull request나 이슈를 제출하여 기여할 수 있음.
- Prism 컴파일러를 실험적으로 사용하려면
ruby --parser=prism
또는RUBYOPT="--parser=prism"
를 사용할 수 있으나, 디버깅 목적으로만 사용해야 함.
Lrama 파서 생성기
- Bison을 Lrama LALR 파서 생성기로 대체함.
- Ruby의 파서에 대한 미래 비전을 참조하면 관심 있는 사람들이 확인할 수 있음.
- 유지보수를 위해 내부 Lrama 파서가 Racc에 의해 생성된 LR 파서로 교체됨.
- 파라미터화 규칙 (?, *, +) 지원, Ruby parse.y에서 사용될 예정임.
YJIT
- Ruby 3.2 대비 주요 성능 개선 사항이 있음.
- splat 및 rest 인자에 대한 지원이 개선됨.
- 가상 머신의 스택 작업에 대해 레지스터가 할당됨.
- 선택적 인자가 있는 더 많은 호출이 컴파일됨. 예외 처리기도 컴파일됨.
- 지원되지 않는 호출 유형 및 메가모픽 호출 사이트는 더 이상 인터프리터로 나가지 않음.
- Rails의
#blank?
및 특수한#present?
와 같은 기본 메소드가 인라인 처리됨. -
Integer#*
,Integer#!=
,String#!=
,String#getbyte
,Kernel#block_given?
,Kernel#is_a?
,Kernel#instance_of?
,Module#===
등이 특별히 최적화됨. - 컴파일 속도가 Ruby 3.2보다 약간 빨라짐.
- Optcarrot에서 인터프리터보다 3배 이상 빠름!
- Ruby 3.2 대비 메모리 사용량이 크게 개선됨.
- 컴파일된 코드의 메타데이터가 훨씬 적은 메모리를 사용함.
-
--yjit-call-threshold
가 30에서 40,000개 이상의 ISEQ가 있는 애플리케이션의 경우 120으로 자동으로 상승됨. -
--yjit-cold-threshold
가 추가되어 냉각된 ISEQ의 컴파일을 건너뜀. - Arm64에서 더 컴팩트한 코드가 생성됨.
- 코드 GC는 기본적으로 비활성화됨.
-
--yjit-exec-mem-size
는 새 코드 컴파일이 중단되는 하드 리밋으로 처리됨. - 코드 GC로 인한 성능 저하가 없으며, Pitchfork를 사용하여 서버가 리포킹할 때 더 나은 복사-쓰기 동작을 보임.
- 원하는 경우
--yjit-code-gc
로 코드 GC를 활성화할 수 있음. -
RubyVM::YJIT.enable
을 추가하여 실행 시간에 YJIT를 활성화할 수 있음. - Rails 7.2는 이 방법을 사용하여 기본적으로 YJIT를 활성화할 예정임.
- 애플리케이션이 부팅을 완료한 후에만 YJIT를 활성화하려면 이 방법을 사용할 수 있음.
- 부팅 시 YJIT를 비활성화하면서 다른 YJIT 옵션을 사용하려면
--yjit-disable
을 사용할 수 있음. - 기본적으로 더 많은 YJIT 통계가 제공됨.
-
yjit_alloc_size
및 여러 메타데이터 관련 통계가 기본적으로 제공됨. -
--yjit-stats
에 의해 생성된ratio_in_yjit
통계가 릴리스 빌드에서 사용 가능함. 특별한 통계 또는 개발 빌드가 더 이상 필요하지 않음. - 더 많은 프로파일링 기능이 추가됨.
-
--yjit-perf
가 Linux perf와 함께 프로파일링을 용이하게 하기 위해 추가됨. -
--yjit-trace-exits
가--yjit-trace-exits-sample-rate=N
을 사용한 샘플링을 지원함. - 더 철저한 테스트와 다수의 버그 수정이 이루어짐.
RJIT
- 순수 Ruby로 작성된 JIT 컴파일러 RJIT이 도입되고 MJIT이 대체됨.
- RJIT은 Unix 플랫폼의 x86-64 아키텍처에서만 지원됨.
- MJIT과 달리, 런타임에 C 컴파일러가 필요하지 않음.
- RJIT은 실험적인 목적으로만 존재함.
- 생산 환경에서는 YJIT을 계속 사용해야 함.
- Ruby JIT 개발에 관심이 있는 경우, RubyKaigi의 3일차에 있는 k0kubun의 발표를 확인할 것을 권장함.
M:N 스레드 스케줄러
- M:N 스레드 스케줄러가 도입됨.
- M Ruby 스레드가 N 네이티브 스레드(운영 체제 스레드)에 의해 관리되므로 스레드 생성 및 관리 비용이 감소함.
- M:N 스레드 스케줄러는 C 확장과 호환성을 깰 수 있으므로 기본적으로 메인 Ractor에서 비활성화됨.
-
RUBY_MN_THREADS=1
환경 변수를 사용하여 메인 Ractor에서 M:N 스레드를 활성화할 수 있음. - 비메인 Ractor에서는 항상 M:N 스레드가 활성화됨.
-
RUBY_MAX_CPU=n
환경 변수는 N의 최대 개수(네이티브 스레드의 최대 개수)를 설정함. 기본값은 8임. - Ractor당 하나의 Ruby 스레드만 실행될 수 있으므로, 싱글 Ractor 애플리케이션(대부분의 애플리케이션)은 1개의 네이티브 스레드만 사용함.
- 차단 작업을 지원하기 위해 N보다 많은 네이티브 스레드가 사용될 수 있음.
성능 개선
-
defined?(@ivar)
가 Object Shapes를 사용하여 최적화됨. -
Socket.getaddrinfo
와 같은 이름 해석이 이제 인터럽트될 수 있음(사용 가능한 환경에서 pthreads가 있는 경우). - 가비지 컬렉터에 대한 여러 성능 개선 사항이 있음.
- 젊은 객체가 노인 객체에 의해 참조되면 즉시 노인 세대로 승격되지 않아 주요 GC 수집 빈도가 크게 감소함.
- 주요 GC 수집을 유발하는 보호되지 않은 객체의 수를 제어하는 새로운
REMEMBERED_WB_UNPROTECTED_OBJECTS_LIMIT_RATIO
조정 변수가 도입됨. 기본값은 0.01(1%)로 설정되어 주요 GC 수집 빈도가 크게 감소함. - Write Barriers가 누락된 많은 핵심 유형에 대해 구현됨. 이로 인해 소규모 GC 수집 시간과 주요 GC 수집 빈도가 크게 감소함.
- 대부분의 핵심 클래스가 이제 Variable Width Allocation을 사용함. 이로 인해 이러한 클래스의 할당 및 해제가 더 빨라지고 메모리 사용량이 줄어들며 힙 단편화가 감소함.
- 가비지 컬렉터에 약한 참조 지원이 추가됨.
기타 주목할 만한 변경 사항
- IRB는 고급 irb:rdbg 통합, ls, show_source 및 show_cmds 명령에 대한 페이저 지원, ls 및 show_source 명령에 의해 제공되는 정보의 정확성 및 유용성 향상, 타입 분석을 사용한 실험적 자동 완성 등을 포함하여 여러 개선 사항을 받음.
- IRB는 또한 향후 개선을 용이하게 하기 위해 광범위한 리팩토링을 거치고 수십 개의 버그 수정을 받음.
호환성 문제
- 인자 없이 블록 내에서
it
호출은 더 이상 사용되지 않으며, Ruby 3.4에서는 첫 번째 블록 매개변수를 참조하게 됨. - 사용되지 않는 환경 변수가 제거됨.
표준 라이브러리 업데이트
- RubyGems과 Bundler는 사용자가 Gemfile이나 gemspec에 추가하지 않고 다음 gem을 요구할 경우 경고를 표시함. 이는 해당 gem들이 미래 버전의 Ruby에서 번들 gem이 될 예정이기 때문임.
- 다음과 같은 기본 gem이 추가되거나 업데이트됨: prism 0.19.0, RubyGems 3.5.3, abbrev 0.1.2 등 다수.
- 다음과 같은 번들 gem이 기본 gem에서 승격되거나 업데이트됨: racc 1.7.3, minitest 5.20.0 등 다수.
GN⁺의 의견
- Prism 파서 도입: Ruby 3.3.0의 가장 중요한 특징 중 하나는 새로운 Prism 파서의 도입이다. 이는 Ruby 코드를 더 효율적으로 파싱하고, 오류에 강하며, 유지보수가 쉬운 파서를 제공함으로써 Ruby 개발자들에게 큰 도움이 될 것이다.
- YJIT의 성능 개선: YJIT의 주요 성능 개선은 Ruby 애플리케이션의 실행 속도를 크게 향상시킬 것이며, 특히 메모리 사용량 감소와 GC 최적화는 대규모 Ruby 애플리케이션의 성능과 안정성에 긍정적인 영향을 미칠 것이다.
- M:N 스레드 스케줄러: M:N 스레드 스케줄러의 도입은 멀티스레딩 Ruby 애플리케이션의 성능을 개선할 수 있는 잠재력을 가지고 있다. 이는 스레드 관리 비용을 줄이고, 더 효율적인 병렬 처리를 가능하게 할 것이다.
Hacker News 의견
-
Ruby 3.3의 등장으로, 개발자의 행복을 중시하는 언어인 Ruby가 이전의 느린 이미지를 벗어나 빠른 속도를 자랑함.
- YJIT 기술과 객체 형태, GC 최적화 등의 혁신을 통해 Ruby의 성능이 크게 향상됨.
- Shopify와 같은 대형 Ruby 사용 업체들이 Ruby 3.3의 성능 개선을 경험하고 있음.
- Ruby의 미래에 대해 개인적으로 매우 기대하고 있으며, Ruby 3.3을 고객의 프로덕션 사이트에 적용하는 데 기대감을 표함.
-
Ruby 3.3은 지난 10년간 가장 중요하고 기능이 풍부한 릴리스로, Python보다 먼저 JIT를 출시한 것에 대해 놀라움을 표함.
- Prism, Lrama, IRB 등 다양한 기능들이 이전 해커뉴스 제출에서 논의됨.
- Ractor, M:N 스레드 스케줄러, Fibre, Async와 같은 기능들이 Rails의 맥락에서 충분히 언급되지 않았으며, 이 기능들을 프로덕션에서 사용하는 사람들의 경험을 듣고 싶어함.
-
Heroku에서 Ruby 3.3을 사용할 수 있음을 알림.
-
매년 크리스마스마다 Ruby 언어는 새로운 릴리스를 출시함.
-
Python과 NodeJS를 이미 알고 있는 경우, Ruby를 배우는 것이 가치가 있는지에 대한 질문을 함. Ruby를 매력적이지만 어렵게 느낌.
-
Socket.getaddrinfo
와 같은 이름 해석이 중단될 수 있음. 이름 해석이 필요할 때마다 워커 pthread를 생성하고getaddrinfo(3)
를 실행함.- 다른 언어 런타임도 비슷한 작업을 하는지에 대한 질문을 함. 스레드 생성이 무겁게 느껴질 수 있지만, 벤치마크에 따르면 오버헤드는 최소화되어 있음.
-
Prism이 흥미로움. Ruby 코드 분석 도구로 Prism을 사용하는 예가 있는지에 대한 질문을 함.
-
RUBY_MAX_CPU=n
환경 변수가 네이티브 스레드의 최대 수를 설정함. 기본값은 8임.- 기본값이 논리 코어의 수와 같아야 하는지에 대한 의문을 제기함. Rust의 Tokio와 많은 다른 M:N 런타임들처럼.
-
Prism을 사용한 좋은 예제에 대한 링크를 찾고 있음. 릴리스 페이지에서 "주목할 만한 API" 외에는 별다른 것을 보지 못해 실망함을 표함.
-
완벽한 크리스마스 선물이라고 언급함.