1P by GN⁺ | ★ favorite | 댓글 1개
  • YJIT가 Ruby 3.2 대비 성능과 메모리 사용량을 크게 개선하고, Optcarrot에서 인터프리터보다 3배 이상 빨라짐
  • YJIT의 Code GC가 기본 비활성화되고, --yjit-exec-mem-size가 새 코드 컴파일을 멈추는 하드 리밋으로 동작함
  • RubyVM::YJIT.enable 추가로 명령행 인자나 환경 변수를 바꾸지 않고 런타임에 YJIT 활성화 가능함
  • 기본 gem으로 Prism 파서가 추가되고, Prism.parse(source), Prism.parse_comments(source), Prism.parse_success?(source) API 사용 가능함
  • ruby --parser=prism 또는 RUBYOPT="--parser=prism"로 Prism 컴파일러 실험 가능하지만, 해당 플래그는 디버깅 전용임
  • 파서 생성기가 Bison에서 Lrama로 교체되고, Lrama 내부 파서는 유지보수성을 위해 Racc가 생성한 LR 파서로 교체됨
  • pure-Ruby JIT 컴파일러 RJIT가 MJIT를 대체하지만, Unix의 x86-64 아키텍처만 지원하고 실험 목적에 한정됨
  • M:N thread scheduler가 도입되어 M개의 Ruby 스레드를 N개의 네이티브 스레드가 관리하지만, C 확장 호환성을 깨뜨릴 수 있어 main Ractor에서는 기본 비활성화됨
  • it가 인자 없이 호출되고 일반 매개변수가 없는 블록에서 사용되는 방식은 지원 중단(deprecation)되며, Ruby 3.4에서는 첫 번째 블록 매개변수 참조가 될 예정임
  • Regexp::new는 Ruby 3.2에서 지원 중단된 3개 인자 호출 대신 최대 2개 인자만 허용함
  • ext/readline이 은퇴하고, 향후 ext/readline API와 호환되는 pure Ruby 구현인 reline에 의존함
  • ext/readline이 필요한 경우 gem install readline-ext로 rubygems.org에서 설치 가능하며, libreadline 또는 libedit 같은 라이브러리 설치가 더 이상 필요하지 않음
  • RubyGems와 Bundler는 일부 gem을 Gemfile 또는 gemspec에 추가하지 않고 require하면 경고하며, 이 gem들은 향후 Ruby 버전에서 bundled gem이 될 예정임
  • bootsnap gem 사용 시 해당 경고가 억제되며, 이 버전의 제한으로 애플리케이션을 최소 한 번 DISABLE_BOOTSNAP=1 환경 변수와 함께 실행하는 방식이 권장됨
  • 기본 gem으로 prism 0.19.0이 추가되고, racc 1.7.3은 default gem에서 bundled gem으로 승격됨

댓글과 토론

Hacker News 의견들
  • Ruby 3.3으로 Ruby가 크게 돌아왔다고 봄. 개발자 행복에 집중하지만 느리다고 조롱받던 언어가 이제는 더 이상 느리지 않음
    YJIT는 놀라운 기술이고, 객체 shape나 여러 가지 가비지 컬렉션 최적화와 함께 Ruby가 꽤 빨라지고 있음. Shopify 같은 대형 Ruby 사용처도 3.3 사전 릴리스에 YJIT를 켜고 운영하면서 두 자릿수 퍼센트 성능 개선을 보고함
    개인적으로 Ruby와 그 미래가 정말 기대되고, Ruby 3.3으로 작업해서 고객 운영 사이트에 적용해보고 싶음
    https://railsatscale.com/2023-09-18-ruby-3-3-s-yjit-runs-sho...

    • 약간의 성능 향상이 Ruby를 새로 선택하게 만들지는 모르겠지만, 점진적으로 개선하는 건 반가움. 성능을 지나치게 걱정하는 건 거의 항상 성급한 최적화이고, Ruby는 지금까지 요구한 일에는 충분히 빨랐음
      Redis 기반 DNS 레코드 저장소와 PowerDNS 사이의 접착 코드에도 썼는데, 전체 스택이 작은 VPS 14대에서 월 5억 쿼리를 처리하면서 htop에 티도 안 났음. 성능 때문에 PowerDNS를 쓴 건 아니고, 공개 인터넷에 노출되는 암호화·HTTP·DNS 같은 걸 직접 구현하는 건 보통 좋지 않아서였음
      요즘 웹의 아이러니는 “느린” Ruby와 서버 렌더링으로 만든 웹앱이, 실제로 느린 최종적 일관성 데이터베이스와 비대한 프론트엔드 웹앱보다 페이지 로딩이 더 빠르다는 점임. 현대 웹에서 사소한 일을 할 때도 로딩 스피너를 너무 자주 봄
    • Ruby 언어 자체는 빠를 수 있지만, 전체 생태계는 고통스러울 정도로 느림. 데이터베이스 조회와 다른 서비스 호출을 거쳐 요청마다 JSON 1MB를 반환하는 서버를 Rails로 만들면 초당 100요청 정도가 나왔고, 같은 서비스를 Go로 다시 쓰니 초당 10만 요청을 처리했음
    • “두 자릿수 성능 개선”이 10% 빠르다는 뜻인지, 10배 빠르다는 뜻인지 궁금했음
      링크를 눌러보니 10%대 개선이었음. Rust, Java, Go, C++ 같은 “빠른” 언어보다 Ruby가 대략 50~200배 느리다는 인식에는 별 차이를 못 줄 것 같음
    • Ruby가 개발자 경험을 개선할 수 있는 영역 하나는 더 나은 디버깅 경험임. Chrome DevTools에 너무 익숙해져서인지, 마지막으로 메타프로그래밍이 많은 Ruby 코드를 디버깅했을 때 무슨 일이 벌어지는지 파악하기가 고통스러웠음
    • “두 자릿수”라는 말을 듣고 50%나 80%를 기대했는데, 링크를 보니 13~15% 였음
  • Ruby 3.3은 지난 10년 중 가장 중요하고 기능이 풍부한 Ruby 릴리스 중 하나 같음. Python보다 먼저 운영 준비가 된 JIT를 내놓을 줄은 몰랐고, Prism, Lrama, IRB도 있음
    하지만 충분히 다뤄지지 않는 게 Ractor, M:N 스레드 스케줄러, Fiber, Async임. 특히 Rails 맥락에서 이 기능들을 운영 환경에서 쓰는 사람이 있는지, 생각을 듣고 싶음

    • Ruby가 Python보다 먼저 운영 준비된 JIT를 내놓은 건 충분히 예측 가능했음. Ruby에는 호스트 인터프리터의 구현 세부사항마다 의존하는 거대한 과학 계산 커뮤니티가 없었기 때문임
    • Ruby 3.3이 그렇게 중요한 릴리스인지 궁금함. 무엇이 그렇게 의미 있는지 모르겠음
      그래도 Ractor는 더 조명받을 만함. 엄청 큰 기능임
    • Ruby와 Python을 모두 아우르는 단일 작업 큐가 왜 없는지 정말 이해가 안 됨. 어느 시점부터 사람들이 HTTP 기반 마이크로서비스로 정보를 주고받기 시작한 건 알지만, 결국 두 언어가 공유하는 저장 형식을 가진 단순한 작업 큐가 Ruby/Rails 기반 시스템과 머신러닝 스택을 연결하는 더 나은 방식임
      아마 회사 내부에는 RabbitMQ나 Redis 기반의 사설 커스텀 해법이 수천 개는 있을 것 같음
  • 매년 크리스마스마다 시계처럼 Ruby Lang이 새 릴리스를 내놓음

  • Heroku에서 사용 가능함: https://devcenter.heroku.com/changelog-items/2772

  • 좋아 보임. 새 IRB는 자동 완성과 디버깅이 더 나아진 대화형 Ruby 인터프리터임. Ruby 3.3의 변경 대부분은 개발자 도구 개선으로 이어질 것 같음
    언어 API는 대체로 이전 릴리스와 일관되게 유지됨. 여러 버전 관리자별 메모를 담아 Ruby 3.3 업데이트 가이드를 썼음: https://mac.install.guide/ruby/update.html
    궁금한데 어떤 버전 관리자를 추천할 만한지 모르겠음. 이전에는 asdf, frum, chruby를 추천했음

    • rbenv나 asdf에 별 문제를 느끼지 않음. 로컬과 운영 환경에서 둘 다 쓰고 있음
  • Socket.getaddrinfo 같은 이름 확인을 이제 중단할 수 있고, 이름 확인이 필요할 때마다 worker pthread를 만들어 그 안에서 getaddrinfo(3)를 실행한다고 함
    다른 언어 런타임도 비슷하게 하는지 궁금함. 스레드를 만드는 건 너무 무거워 보이지만 실제로는 큰 문제가 아닐 수도 있음. 자체 벤치마크 기준으로도 오버헤드는 작지만 0은 아님
    10000.times { Addrinfo.getaddrinfo("www.ruby-lang.org";, 80) }는 패치 전 2.3초, 패치 후 3.0초였고, 100.times { URI.open("https://www.ruby-lang.org";).read }는 패치 전 3.36초, 패치 후 3.40초였음

    • 표준 라이브러리의 모든 입출력 연산이 async/fiber를 지원해야 해서 그런 건지 궁금함
      Python처럼 같은 연산을 async “색깔”로 다시 구현한 게 아니라, 모든 것이 기본적으로 비동기로 옮겨졌다는 인상을 받았는데 그게 맞는지 모르겠음
    • 새 스레드를 만드는 것보다 fiber가 더 가볍지 않을까 싶음
  • 이미 Python과 NodeJS를 안다면 Ruby를 배울 가치가 있는지 궁금함. Ruby는 흥미롭지만 어렵게 느껴짐

    • Ruby는 기본적으로 덜 인기 있지만 더 우아한 Python에 가까움. 탄탄한 범용 언어이고, 특히 셸 스크립트나 데이터 정리 같은 작업에 좋음
      Node와 Python에 능숙하다면 배우기 꽤 쉬울 것임. 단점은 그 언어들을 이미 안다면 근본적으로 새로운 걸 해주지는 않는다는 점임. 장점은 주로 미학적인데, Ruby는 코드를 정말 아름답게 표현하는 방식을 제공하고 장려하며, 그걸 경험하는 재미가 있음
    • Ruby는 Python이나 Node보다 셸 스크립트성 작업과 시스템 작업을 위한 대화형·탐색형 프로그래밍에 훨씬 좋다고 봄. “더 나은 bash”나 “더 나은 Perl”로 쓰면 충분히 가치 있음
      주로 TypeScript 코드베이스에서 일하지만, 로그 데이터나 반정형 텍스트를 다루거나 여러 파일에 정규식 기반 재작성을 할 때 Ruby를 자주 꺼내 씀. Ruby는 매우 재미있고, 정기적으로 써본 언어 중 아마 가장 재미있는 언어라서 그 자체로 보상이 됨
    • 무엇을 얻고 싶은지에 따라 다름. 가장 흥미로운 부분은 Ruby가 여러 면에서 Python과 아주 비슷하면서도 동시에 상당히 다르다는 점임
      가장 큰 예는 Ruby가 블록을 정말 좋아한다는 것임. 표준 라이브러리 곳곳에 있고, 문법 설탕도 많고, 블록을 중심으로 한 DSL도 셀 수 없이 많음. 표준적인 함수형 기능도 표준 라이브러리에 오래전부터 들어 있어 함수형 스타일을 매끄럽고 읽기 좋게 쓸 수 있음
      Python에서도 기술적으로는 대부분 가능하지만 쓰고 읽기가 훨씬 어색해 보임. Python에는 람다가 있지만 사소한 용도 이상으로는 그다지 좋아하지 않는 느낌이고, 대신 함수가 어디서나 일급 값으로 쓰임
    • 배우면서 코드를 개선할 힌트를 얻으려면 Rubocop을 쓰면 좋음. 훌륭한 린터이자 선생님임
    • 10년 조금 넘게 전인 2011~2013년에 Ruby, 더 정확히는 Rails의 Ruby를 조금 써봤음. 당시에는 이미 2005년에 등장해 2007~2009년에 크게 유행한 RoR의 여운이 남아 있던 시기였음
      최신 기술에 민감한 사람들은 RoR로 만든 것보다 훨씬 빠른 node.js와 AngularJS 기반 API 지향 개발로 넘어가고 있었음. 2013년의 RoR은 표준적인 방식이 여전히 모놀리식이었고, 프론트엔드/백엔드 분리라는 새 패러다임으로 잘 전환하지 못하면서 나이가 들어 보였음
      Ruby 자체가 아니라 Ruby로 만든 프레임워크 이야기를 길게 한 이유는, 지금도 Ruby 개발의 95%는 Rails 애플리케이션일 것 같기 때문임. Ruby가 유명해진 건 대부분 Ruby on Rails 덕분이었고, RoR이 하락세라면 Ruby도 Perl처럼 작은 열성 커뮤니티로 줄어들 때까지 같은 흐름을 따를 것 같음
      언어 자체만 보면 Python이나 TypeScript 대신 Ruby를 선택할 이유가 떠오르지 않음. 언어나 플랫폼 어느 면에서도 이미 더 잘 자리 잡은 경쟁자들보다 Ruby가 더 잘하는 것이 없어 보임
  • Prism이 흥미로움. 이를 사용하는 Ruby 코드 분석 도구가 있는지 궁금함. 회사 코드 분석 방법을 찾고 있었음

  • RUBY_MAX_CPU=n 환경 변수가 최대 N개, 즉 최대 네이티브 스레드 수를 설정하고 기본값은 8이라고 함
    기본값은 Rust의 Tokio나 수많은 M:N 런타임처럼 논리 코어 수여야 하지 않나 싶음

    • 나중에 추가할 수 있는 최적화이고 약간의 미묘함이 있음. 기억하기로 Go도 몇 년간 비슷했고, init()이나 main()에서 GO_MAX_PROCS를 설정하던 기억이 남
    • 최대 CPU 수에 하드 캡을 거는 건 맞지 않아 보임. 사용 가능한 코어 수에 의존하면 되지 않을까 싶음
  • Ruby 4.0에서는 지금 같은 암묵적이고 전역 네임스페이스에 gem을 가져오는 방식 대신 명시적 import를 허용했으면 좋겠음

    • Chris Salzberg의 Im에 관심이 갈 수 있음. Ruby 3.2에서도 이미 사용 가능함: https://github.com/shioyama/im
      추정 단계의 “namespace on read” 기능 논의도 있음: https://bugs.ruby-lang.org/issues/19744
    • 암묵적이든 명시적이든 전역 네임스페이스는 괜찮다고 봄. 이론상으로는 어색하지만 실제로는 관례와 사회적 규범 덕분에 문제가 되는 일이 드묾
      전역 네임스페이스가 각 gem의 정확히 한 버전만 지원한다는 점은 안정적인 ABI와 폐기 유예 기간을 중시하는 건강한 문화를 장려하기도 함. 어떤 언어 생태계와 비교하면 꿈같은 환경임
    • 이건 Ruby가 아니라 Rails 쪽 아닌가 싶음. 대부분 프로젝트에서는 그 차이가 사실상 없다는 건 알지만, Rails 개발진의 결정이고 Ruby 개발진이 하는 일과는 무관할 수도 있음
      아니면 완전히 틀린 말을 하고 있는 걸 수도 있음
    • Ruby 4.0은 2030년 전까지 보기 어려울 것임
      https://youtu.be/4MM5b2F9zrM?si=UT3aUbD6p_uBZehS&t=2347