Skip to content
  • Aaron Patterson's avatar
    8ac8225c
    Inline Class#new. · 8ac8225c
    Aaron Patterson authored
    
    
    This commit inlines instructions for Class#new.  To make this work, we
    added a new YARV instructions, `opt_new`.  `opt_new` checks whether or
    not the `new` method is the default allocator method.  If it is, it
    allocates the object, and pushes the instance on the stack.  If not, the
    instruction jumps to the "slow path" method call instructions.
    
    Old instructions:
    
    ```
    > ruby --dump=insns -e'Object.new'
    == disasm: #<ISeq:<main>@-e:1 (1,0)-(1,10)>
    0000 opt_getconstant_path                   <ic:0 Object>             (   1)[Li]
    0002 opt_send_without_block                 <calldata!mid:new, argc:0, ARGS_SIMPLE>
    0004 leave
    ```
    
    New instructions:
    
    ```
    > ./miniruby --dump=insns -e'Object.new'
    == disasm: #<ISeq:<main>@-e:1 (1,0)-(1,10)>
    0000 opt_getconstant_path                   <ic:0 Object>             (   1)[Li]
    0002 putnil
    0003 swap
    0004 opt_new                                <calldata!mid:new, argc:0, ARGS_SIMPLE>, 11
    0007 opt_send_without_block                 <calldata!mid:initialize, argc:0, FCALL|ARGS_SIMPLE>
    0009 jump                                   14
    0011 opt_send_without_block                 <calldata!mid:new, argc:0, ARGS_SIMPLE>
    0013 swap
    0014 pop
    0015 leave
    ```
    
    This commit speeds up basic object allocation (`Foo.new`) by 60%, but
    classes that take keyword parameters see an even bigger benefit because
    no hash is allocated when instantiating the object (3x to 6x faster).
    
    Here is an example that uses `Hash.new(capacity: 0)`:
    
    ```
    > hyperfine "ruby --disable-gems -e'i = 0; while i < 10_000_000; Hash.new(capacity: 0); i += 1; end'" "./ruby --disable-gems -e'i = 0; while i < 10_000_000; Hash.new(capacity: 0); i += 1; end'"
    Benchmark 1: ruby --disable-gems -e'i = 0; while i < 10_000_000; Hash.new(capacity: 0); i += 1; end'
      Time (mean ± σ):      1.082 s ±  0.004 s    [User: 1.074 s, System: 0.008 s]
      Range (min … max):    1.076 s …  1.088 s    10 runs
    
    Benchmark 2: ./ruby --disable-gems -e'i = 0; while i < 10_000_000; Hash.new(capacity: 0); i += 1; end'
      Time (mean ± σ):     627.9 ms ±   3.5 ms    [User: 622.7 ms, System: 4.8 ms]
      Range (min … max):   622.7 ms … 633.2 ms    10 runs
    
    Summary
      ./ruby --disable-gems -e'i = 0; while i < 10_000_000; Hash.new(capacity: 0); i += 1; end' ran
        1.72 ± 0.01 times faster than ruby --disable-gems -e'i = 0; while i < 10_000_000; Hash.new(capacity: 0); i += 1; end'
    ```
    
    This commit changes the backtrace for `initialize`:
    
    ```
    aaron@tc ~/g/ruby (inline-new)> cat test.rb
    class Foo
      def initialize
        puts caller
      end
    end
    
    def hello
      Foo.new
    end
    
    hello
    aaron@tc ~/g/ruby (inline-new)> ruby -v test.rb
    ruby 3.4.2 (2025-02-15 revision d2930f8e) +PRISM [arm64-darwin24]
    test.rb:8:in 'Class#new'
    test.rb:8:in 'Object#hello'
    test.rb:11:in '<main>'
    aaron@tc ~/g/ruby (inline-new)> ./miniruby -v test.rb
    ruby 3.5.0dev (2025-03-28T23:59:40Z inline-new c4157884e4) +PRISM [arm64-darwin24]
    test.rb:8:in 'Object#hello'
    test.rb:11:in '<main>'
    ```
    
    It also increases memory usage for calls to `new` by 122 bytes:
    
    ```
    aaron@tc ~/g/ruby (inline-new)> cat test.rb
    require "objspace"
    
    class Foo
      def initialize
        puts caller
      end
    end
    
    def hello
      Foo.new
    end
    
    puts ObjectSpace.memsize_of(RubyVM::InstructionSequence.of(method(:hello)))
    aaron@tc ~/g/ruby (inline-new)> make runruby
    RUBY_ON_BUG='gdb -x ./.gdbinit -p' ./miniruby -I./lib -I. -I.ext/common  ./tool/runruby.rb --extout=.ext  -- --disable-gems  ./test.rb
    656
    aaron@tc ~/g/ruby (inline-new)> ruby -v test.rb
    ruby 3.4.2 (2025-02-15 revision d2930f8e) +PRISM [arm64-darwin24]
    544
    ```
    
    Thanks to @ko1 for coming up with this idea!
    
    Co-Authored-By: default avatarJohn Hawthorn <john@hawthorn.email>
    8ac8225c
    Inline Class#new.
    Aaron Patterson authored
    
    
    This commit inlines instructions for Class#new.  To make this work, we
    added a new YARV instructions, `opt_new`.  `opt_new` checks whether or
    not the `new` method is the default allocator method.  If it is, it
    allocates the object, and pushes the instance on the stack.  If not, the
    instruction jumps to the "slow path" method call instructions.
    
    Old instructions:
    
    ```
    > ruby --dump=insns -e'Object.new'
    == disasm: #<ISeq:<main>@-e:1 (1,0)-(1,10)>
    0000 opt_getconstant_path                   <ic:0 Object>             (   1)[Li]
    0002 opt_send_without_block                 <calldata!mid:new, argc:0, ARGS_SIMPLE>
    0004 leave
    ```
    
    New instructions:
    
    ```
    > ./miniruby --dump=insns -e'Object.new'
    == disasm: #<ISeq:<main>@-e:1 (1,0)-(1,10)>
    0000 opt_getconstant_path                   <ic:0 Object>             (   1)[Li]
    0002 putnil
    0003 swap
    0004 opt_new                                <calldata!mid:new, argc:0, ARGS_SIMPLE>, 11
    0007 opt_send_without_block                 <calldata!mid:initialize, argc:0, FCALL|ARGS_SIMPLE>
    0009 jump                                   14
    0011 opt_send_without_block                 <calldata!mid:new, argc:0, ARGS_SIMPLE>
    0013 swap
    0014 pop
    0015 leave
    ```
    
    This commit speeds up basic object allocation (`Foo.new`) by 60%, but
    classes that take keyword parameters see an even bigger benefit because
    no hash is allocated when instantiating the object (3x to 6x faster).
    
    Here is an example that uses `Hash.new(capacity: 0)`:
    
    ```
    > hyperfine "ruby --disable-gems -e'i = 0; while i < 10_000_000; Hash.new(capacity: 0); i += 1; end'" "./ruby --disable-gems -e'i = 0; while i < 10_000_000; Hash.new(capacity: 0); i += 1; end'"
    Benchmark 1: ruby --disable-gems -e'i = 0; while i < 10_000_000; Hash.new(capacity: 0); i += 1; end'
      Time (mean ± σ):      1.082 s ±  0.004 s    [User: 1.074 s, System: 0.008 s]
      Range (min … max):    1.076 s …  1.088 s    10 runs
    
    Benchmark 2: ./ruby --disable-gems -e'i = 0; while i < 10_000_000; Hash.new(capacity: 0); i += 1; end'
      Time (mean ± σ):     627.9 ms ±   3.5 ms    [User: 622.7 ms, System: 4.8 ms]
      Range (min … max):   622.7 ms … 633.2 ms    10 runs
    
    Summary
      ./ruby --disable-gems -e'i = 0; while i < 10_000_000; Hash.new(capacity: 0); i += 1; end' ran
        1.72 ± 0.01 times faster than ruby --disable-gems -e'i = 0; while i < 10_000_000; Hash.new(capacity: 0); i += 1; end'
    ```
    
    This commit changes the backtrace for `initialize`:
    
    ```
    aaron@tc ~/g/ruby (inline-new)> cat test.rb
    class Foo
      def initialize
        puts caller
      end
    end
    
    def hello
      Foo.new
    end
    
    hello
    aaron@tc ~/g/ruby (inline-new)> ruby -v test.rb
    ruby 3.4.2 (2025-02-15 revision d2930f8e) +PRISM [arm64-darwin24]
    test.rb:8:in 'Class#new'
    test.rb:8:in 'Object#hello'
    test.rb:11:in '<main>'
    aaron@tc ~/g/ruby (inline-new)> ./miniruby -v test.rb
    ruby 3.5.0dev (2025-03-28T23:59:40Z inline-new c4157884e4) +PRISM [arm64-darwin24]
    test.rb:8:in 'Object#hello'
    test.rb:11:in '<main>'
    ```
    
    It also increases memory usage for calls to `new` by 122 bytes:
    
    ```
    aaron@tc ~/g/ruby (inline-new)> cat test.rb
    require "objspace"
    
    class Foo
      def initialize
        puts caller
      end
    end
    
    def hello
      Foo.new
    end
    
    puts ObjectSpace.memsize_of(RubyVM::InstructionSequence.of(method(:hello)))
    aaron@tc ~/g/ruby (inline-new)> make runruby
    RUBY_ON_BUG='gdb -x ./.gdbinit -p' ./miniruby -I./lib -I. -I.ext/common  ./tool/runruby.rb --extout=.ext  -- --disable-gems  ./test.rb
    656
    aaron@tc ~/g/ruby (inline-new)> ruby -v test.rb
    ruby 3.4.2 (2025-02-15 revision d2930f8e) +PRISM [arm64-darwin24]
    544
    ```
    
    Thanks to @ko1 for coming up with this idea!
    
    Co-Authored-By: default avatarJohn Hawthorn <john@hawthorn.email>
Loading