Ruby 3.0.0 릴리스

Ruby 3.0.0 릴리스를 알리게 되어 기쁩니다. 우리는 2015년부터 성능, 동시성, 타입 기능을 목표로 한 Ruby 3을 위해 열심히 개발해 왔습니다. 특히 성능 부분에서, Matz 씨는 “Ruby3는 Ruby2보다 3배 더 빠를 것”이라고 해 Ruby 3x3으로 알려지기도 했습니다.

Optcarrot 3000 frames

NES의 게임 에뮬레이션 작업을 통해 싱글 스레드 성능을 측정한 optcarrot 벤치마크를 보면, Ruby 2.0보다 3배 더 빠른 성능을 달성했습니다!

이 벤치마크는 benchmark-driver.github.io/hardware.html에 표기된 환경에서 측정되었습니다. 8c510e4095 커밋이 Ruby 3.0 코드로 사용되었습니다. 환경과 벤치마크에 따라 3배 빠르지 않을 수도 있습니다.

Ruby 3.0.0의 목표는 다음 항목을 통해 다루고 있습니다.

  • 성능
    • MJIT
  • 동시성
    • Ractor
    • Fiber 스케줄러
  • 타입 (정적 분석)
    • RBS
    • TypeProf

위의 성능 향상과 함께, Ruby 3.0에 몇 가지 새 기능이 도입됩니다.

성능

제가 콘퍼런스 키노트에서 처음 “Ruby3x3”을 선언했을 때, 코어 팀 구성원을 포함한 많은 사람들은 “Matz 씨는 허풍쟁이”라고 느꼈습니다. 사실 저도 그렇게 느꼈어요. 하지만 우리는 해냈습니다. (일부 벤치마크에서) Ruby3.0을 Ruby2.0보다 세 배 빠르게 만드는 데 성공한 코어 팀이 자랑스럽습니다. – Matz

MJIT

MJIT의 많은 개선이 추가되었습니다. 자세한 내용은 NEWS를 확인하세요.

Ruby 3.0에서 JIT는 게임(Optcarrot), AI(Rubykon), 적은 메서드를 여러 번 호출하는 데 대부분의 시간을 사용하는 애플리케이션 등의 제한적인 작업 성능을 향상시킵니다.

Ruby 3.0이 JIT이 적용된 코드 크기를 현저히 줄였지만, Rails와 같은 작업에 대해서는 아직 준비되지 않았습니다. 굉장히 많은 메서드에 시간을 사용하는 Rails의 경우 JIT으로 인해 명령어 캐시 미스가 더 악화됩니다. 이 문제에 대한 추후 개선은 Ruby 3.1 소식을 기다려주세요.

동시성 / 병렬성

지금은 멀티코어 시대입니다. 동시성은 아주 중요합니다. Ruby는 Ractor, Async Fiber와 함께 진정한 동시성 언어가 될 것입니다. — Matz

Ractor (실험적)

Ractor는 스레드 안전에 대한 걱정이 없는 병렬 실행을 제공하기 위해 설계된 액터 모델과 비슷한 동시 실행 추상화 모델입니다.

여러 개의 Ractor를 만들고 병렬로 실행할 수 있습니다. Ractor는 일반 객체를 공유할 수 없기 때문에 스레드 안전한 병렬 프로그램을 만들 수 있습니다. Ractor 간의 통신은 메시지 넘기기를 통해서 지원됩니다.

객체 공유를 제한하기 위해, Ractor는 Ruby 문법에 여러 제한을 추가했습니다(여러 개의 Ractor를 사용하지 않는다면 제한은 없습니다).

명세와 구현은 아직 완성되지 않았으므로 앞으로 변경될 수 있습니다. 그러므로 이 기능은 실험적으로 제공되며 처음 Ractor.new를 실행하면 “실험적 기능”이라는 경고를 표시합니다.

다음은 유명한 벤치마크 tak 함수(Tak (function) - Wikipedia)의 실행 시간을 측정하는 작은 프로그램입니다. 4번 순차적으로 실행하거나, Ractor를 통해 4번 병렬로 실행합니다.

def tarai(x, y, z) =
  x <= y ? y : tarai(tarai(x-1, y, z),
                     tarai(y-1, z, x),
                     tarai(z-1, x, y))
require 'benchmark'
Benchmark.bm do |x|
  # 순차적 버전
  x.report('seq'){ 4.times{ tarai(14, 7, 0) } }

  # 병렬 버전
  x.report('par'){
    4.times.map do
      Ractor.new { tarai(14, 7, 0) }
    end.each(&:take)
  }
end
Benchmark result:
          user     system      total        real
seq  64.560736   0.001101  64.561837 ( 64.562194)
par  66.422010   0.015999  66.438009 ( 16.685797)

이 결과는 Ubuntu 20.04, Intel(R) Core(TM) i7-6700(4 코어, 8 하드웨어 스레드)에서 측정되었습니다. 이는 병렬 버전이 순차적 버전보다 3.87배 빠르다는 것을 보여줍니다.

더 자세한 내용은 doc/ractor.md를 확인하세요.

Fiber 스케줄러

블로킹 작업에 끼어들 수 있는 Fiber#scheduler가 도입됩니다. 이는 기존에 존재하는 코드를 변경하지 않고 가벼운 동시성을 지원할 수 있게 합니다. 어떻게 동작하는지 궁금하다면 “Don’t Wait For Me, Scalable Concurrency for Ruby 3”을 보세요.

현재 지원되는 클래스와 메서드는 다음과 같습니다.

  • Mutex#lock, Mutex#unlock, Mutex#sleep
  • ConditionVariable#wait
  • Queue#pop, SizedQueue#push
  • Thread#join
  • Kernel#sleep
  • Process.wait
  • IO#wait, IO#read, IO#write와 관련 메서드(예: #wait_readable, #gets, #puts 등).
  • IO#select지원되지 않습니다.

이 예제 프로그램은 HTTP 요청 몇 개를 동시에 수행합니다.

require 'async'
require 'net/http'
require 'uri'

Async do
  ["ruby", "rails", "async"].each do |topic|
    Async do
      Net::HTTP.get(URI "https://www.google.com/search?q=#{topic}")
    end
  end
end

이 코드는 이벤트 루프를 지원하는 async를 사용합니다. 이 이벤트 루프는 Net::HTTP를 논블로킹 작업으로 만들기 위해 Fiber#scheduler 훅을 사용합니다. Ruby의 논블로킹 실행을 제공하기 위해 다른 gem도 이 인터페이스를 사용할 수 있습니다. 그리고 이러한 gem은 논블로킹 훅을 지원하는 Ruby의 다른 구현체(예: JRuby, TruffleRuby)와도 호환됩니다.

정적 분석

2010년대는 정적 타입 프로그래밍 언어의 시대였습니다. Ruby는 추상 해석을 통해, 타입 선언 없이 정적 타입 체크를 하여 미래를 추구합니다. RBS와 TypeProf는 미래를 향한 첫걸음입니다. 다음 단계도 기대해주세요. — Matz

RBS

RBS는 Ruby 프로그램의 타입을 기술하기 위한 언어입니다.

TypeProf와 다른 도구들을 포함해서, RBS를 지원하는 타입 검사기는 RBS 정의를 통해 Ruby 프로그램을 더 잘 이해합니다.

클래스나 모듈에 정의된 메서드, 인스턴스 변수와 타입, 상속이나 믹스인 관계에 대한 정의를 작성할 수 있습니다.

RBS의 목표는 Ruby 프로그램에서 흔히 보이는 패턴을 지원하는 것과 유니언 타입, 메서드 오버로딩, 제네릭을 포함하는 고급 타입을 작성할 수 있도록 하는 것입니다. 또한 _인터페이스 타입_을 이용해 덕타이핑을 지원합니다.

Ruby 3.0은 RBS로 작성된 타입 정의를 해석하고 처리하는 rbs gem을 포함합니다. 다음은 클래스, 모듈, 상수 정의를 포함하는 RBS의 작은 예시입니다.

module ChatApp
  VERSION: String
  class Channel
    attr_reader name: String
    attr_reader messages: Array[Message]
    attr_reader users: Array[User | Bot]              # `|`는 유니언 타입을 의미합니다. 여기에서는 `User` 또는 `Bot`이라는 의미입니다.
    def initialize: (String) -> void
    def post: (String, from: User | Bot) -> Message   # 메서드 오버로딩을 지원합니다.
            | (File, from: User | Bot) -> Message
  end
end

더 자세한 내용은 rbs gem의 README를 확인하세요.

TypeProf

TypeProf는 Ruby 패키지에 포함된 타입 분석 도구입니다.

지금의 TypeProf는 타입 추론 도구 역할을 합니다.

TypeProf는 타입 어노테이션이 없는 일반적인 Ruby 코드를 읽어서, 어떤 메서드가 정의되어 있고 어떻게 사용되는지 분석하고, RBS 형식으로 타입 시그니처의 프로토타입을 생성합니다.

다음은 TypeProf의 간단한 데모입니다.

예제 입력입니다.

# test.rb
class User
  def initialize(name:, age:)
    @name, @age = name, age
  end
  attr_reader :name, :age
end
User.new(name: "John", age: 20)

예제 출력입니다.

$ typeprof test.rb
# Classes
class User
  attr_reader name : String
  attr_reader age : Integer
  def initialize : (name: String, age: Integer) -> [String, Integer]
end

입력을 “test.rb”에 저장하고 “typeprof test.rb” 커맨드를 통해 TypeProf를 실행할 수 있습니다.

TypeProf를 온라인에서 사용해볼 수도 있습니다. (TypeProf를 서버 쪽에서 실행하는 거라서, 잠시 동작하지 않을 수도 있습니다!)

더 자세한 내용은 TypeProf 문서데모를 확인하세요.

TypeProf는 실험적이고 아직 완성되지 않았습니다. Ruby 언어의 일부만 지원되고, 타입 오류 감지 기능은 제한적입니다. 하지만 언어 기능의 지원 범위, 분석 성능, 사용성이 빠르게 개선되고 있는 중입니다. 어떤 종류의 피드백이든 환영합니다.

그 이외의 주목할 만한 기능

  • 한 줄 패턴 매칭을 재설계했습니다. (실험적)

    • =>가 추가됩니다. 오른 방향 대입처럼 사용할 수 있습니다.

      0 => a
      p a #=> 0
      
      {b: 0, c: 1} => {b:}
      p b #=> 0
      
    • intrue 또는 false를 반환하도록 변경됩니다.

      # 버전 3.0
      0 in 1 #=> false
      
      # 버전 2.7
      0 in 1 #=> raise NoMatchingPatternError
      
  • 검색 패턴이 추가됩니다. (실험적)

    case ["a", 1, "b", "c", 2, "d", "e", "f", 3]
    in [*pre, String => x, String => y, *post]
      p pre  #=> ["a", 1]
      p x    #=> "b"
      p y    #=> "c"
      p post #=> [2, "d", "e", "f", 3]
    end
    
  • end 없는 메서드 정의가 추가됩니다.

    def square(x) = x * x
    
  • Hash#except가 내장됩니다.

    h = { a: 1, b: 2, c: 3 }
    p h.except(:a) #=> {:b=>2, :c=>3}
    
  • 메모리 뷰가 실험적인 기능으로 추가됩니다.

    • 이는 숫자 배열이나 비트맵 이미지와 같은 메모리 공간을 확장 라이브러리 간에 교환하기 위한 새로운 C-API 집합입니다. 확장 라이브러리는 모양, 요소의 형식 등으로 구성된 메모리 공간의 메타데이터를 공유할 수 있습니다. 이러한 메타데이터를 사용하여 확장 라이브러리는 다차원 배열을 적절하게 공유할 수 있습니다. 이 기능은 Python의 버퍼 프로토콜을 참고하여 설계되었습니다.

성능 향상

  • IRB에 긴 코드를 붙여 넣는 속도가 Ruby 2.7.0에 포함된 버전보다 53배 빨라졌습니다. 예를 들어, 이 샘플 코드를 붙여 넣는 데 드는 시간이 11.7초에서 0.22초로 줄어들었습니다.
  • IRB에 measure 커맨드가 추가되었습니다. 간단한 실행 시간 측정이 가능합니다.

    irb(main):001:0> 3
    => 3
    irb(main):002:0> measure
    TIME is added.
    => nil
    irb(main):003:0> 3
    processing time: 0.000058s
    => 3
    irb(main):004:0> measure :off
    => nil
    irb(main):005:0> 3
    => 3
    

그 이외의 2.7 이후로 주목할 만한 변경

  • 키워드 인자가 다른 인자들로부터 분리됩니다.
    • 원칙적으로 Ruby 2.7에서 경고를 출력하는 코드는 동작하지 않습니다. 자세한 내용은 이 문서를 확인하세요.
    • 한편, 인자를 전달할 때 앞쪽 인자를 사용할 수 있습니다.

      def method_missing(meth, ...)
        send(:"do_#{ meth }", ...)
      end
      
  • 패턴 매칭(case/in)은 이제 실험적이지 않습니다.
  • $SAFE 기능이 완전히 제거됩니다. 이 값은 이제 일반 전역 변수입니다.
  • Ruby 2.5에서 백트레이스의 순서가 역순이 되었습니다만, 이를 취소합니다. 이제 백트레이스는 Ruby 2.4처럼 동작합니다. 예외가 발생한 곳의 오류 메시지와 줄 번호가 가장 먼저 출력되며, 이를 호출한 곳의 정보가 그 뒤에 출력됩니다.
  • 표준 라이브러리를 업데이트했습니다.
    • RubyGems 3.2.3
    • Bundler 2.2.3
    • IRB 1.3.0
    • Reline 0.2.0
    • Psych 3.3.0
    • JSON 2.5.1
    • BigDecimal 3.0.0
    • CSV 3.1.9
    • Date 3.1.0
    • Digest 3.0.0
    • Fiddle 1.0.6
    • StringIO 3.0.0
    • StringScanner 3.0.0
  • 다음 라이브러리는 이제 내장 gem이나 표준 라이브러리가 아닙니다. 각 기능이 필요한 경우에는 해당하는 gem을 설치해주세요.
    • sdbm
    • webrick
    • net-telnet
    • xmlrpc
  • 다음 기본 gem은 이제 내장 gem이 됩니다.
    • rexml
    • rss
  • 다음 표준 라이브러리가 기본 gem이 되고, rubygems.org에 배포됩니다.
    • English
    • abbrev
    • base64
    • drb
    • debug
    • erb
    • find
    • net-ftp
    • net-http
    • net-imap
    • net-protocol
    • open-uri
    • optparse
    • pp
    • prettyprint
    • resolv-replace
    • resolv
    • rinda
    • set
    • securerandom
    • shellwords
    • tempfile
    • tmpdir
    • time
    • tsort
    • un
    • weakref
    • digest
    • io-nonblock
    • io-wait
    • nkf
    • pathname
    • syslog
    • win32ole

더 자세한 내용은 NEWS커밋 로그를 확인해주세요.

이러한 변경사항에 따라, Ruby 2.7.0 이후로 파일 4028개 수정, 200058줄 추가(+), 154063줄 삭제(-)가 이루어졌습니다!

Ruby3.0은 마일스톤입니다. 언어는 진화했고, 호환성은 유지했습니다. 이게 끝이 아닙니다. Ruby는 계속 진보하고, 더 대단해질 것입니다. 기대해주세요! — Matz

메리 크리스마스, 해피 홀리데이, Ruby 3.0과 함께 프로그래밍을 즐겨보세요!

다운로드

  • https://cache.ruby-lang.org/pub/ruby/3.0/ruby-3.0.0.tar.gz

    SIZE: 19539509
    SHA1: 233873708c1ce9fdc295e0ef1c25e64f9b98b062
    SHA256: a13ed141a1c18eb967aac1e33f4d6ad5f21be1ac543c344e0d6feeee54af8e28
    SHA512: e62f4f63dc12cff424e8a09adc06477e1fa1ee2a9b2b6e28ca22fd52a211e8b8891c0045d47935014a83f2df2d6fc7c8a4fd87f01e63c585afc5ef753e1dd1c1
    
  • https://cache.ruby-lang.org/pub/ruby/3.0/ruby-3.0.0.tar.xz

    SIZE: 14374176
    SHA1: c142899d70a1326c5a71311b17168f98c15e5d89
    SHA256: 68bfaeef027b6ccd0032504a68ae69721a70e97d921ff328c0c8836c798f6cb1
    SHA512: 2a23c2894e62e24bb20cec6b2a016b66d7df05083668726b6f70af8338211cfec417aa3624290d1f5ccd130f65ee7b52b5db7d428abc4a9460459c9a5dd1a450
    
  • https://cache.ruby-lang.org/pub/ruby/3.0/ruby-3.0.0.zip

    SIZE: 23862057
    SHA1: 2a9629102d71c7fe7f31a8c91f64e570a40d093c
    SHA256: a5e4fa7dc5434a7259e9a29527eeea2c99eeb5e82708f66bb07731233bc860f4
    SHA512: e5bf742309d79f05ec1bd1861106f4b103e4819ca2b92a826423ff451465b49573a917cb893d43a98852435966323e2820a4b9f9377f36cf771b8c658f80fa5b
    

Ruby는

Ruby는 1993년에 Matz(마츠모토 유키히로) 씨가 처음 개발했고, 현재는 오픈 소스로서 개발되고 있습니다. 여러 플랫폼에서 동작하며, 특히 웹 개발에서 전 세계적으로 이용되고 있습니다.