ruby-changes:54039
From: tenderlove <ko1@a...>
Date: Fri, 7 Dec 2018 03:28:25 +0900 (JST)
Subject: [ruby-changes:54039] tenderlove:r66258 (trunk): Speed up hash literals by duping
tenderlove 2018-12-07 03:28:21 +0900 (Fri, 07 Dec 2018) New Revision: 66258 https://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=revision&revision=66258 Log: Speed up hash literals by duping This commit replaces the `newhashfromarray` instruction with a `duphash` instruction. Instead of allocating a new hash from an array stored in the Instruction Sequences, store a hash directly in the instruction sequences and dup it on execution. == Instruction sequence changes == ```ruby code = <<-eorby { "foo" => "bar", "baz" => "lol" } eorby insns = RubyVM::InstructionSequence.compile(code, __FILE__, nil, 0, frozen_string_literal: true) puts insns.disasm ``` On Ruby 2.5: ``` == disasm: #<ISeq:<compiled>@test.rb:0 (0,0)-(0,36)>==================== 0000 putobject "foo" 0002 putobject "bar" 0004 putobject "baz" 0006 putobject "lol" 0008 newhash 4 0010 leave ``` Ruby 2.6@r66174 3b6321083a2e3525da3b34d08a0b68bac094bd7f: ``` $ ./ruby test.rb == disasm: #<ISeq:<compiled>@test.rb:0 (0,0)-(0,36)> (catch: FALSE) 0000 newhashfromarray 2, ["foo", "bar", "baz", "lol"] 0003 leave ``` Ruby 2.6 + This commit: ``` $ ./ruby test.rb == disasm: #<ISeq:<compiled>@test.rb:0 (0,0)-(0,36)> (catch: FALSE) 0000 duphash {"foo"=>"bar", "baz"=>"lol"} 0002 leave ``` == Benchmark Results == Compared to 2.5.3: ``` $ make benchmark ITEM=hash_literal_small COMPARE_RUBY=/Users/aaron/.rbenv/versions/2.5.3/bin/ruby generating known_errors.inc known_errors.inc unchanged ./revision.h unchanged /Users/aaron/.rbenv/shims/ruby --disable=gems -rrubygems -I./benchmark/lib ./benchmark/benchmark-driver/exe/benchmark-driver \ --executables="compare-ruby::/Users/aaron/.rbenv/versions/2.5.3/bin/ruby -I.ext/common --disable-gem" \ --executables="built-ruby::./miniruby -I./lib -I. -I.ext/common -r./prelude --disable-gem" \ $(find ./benchmark -maxdepth 1 -name '*hash_literal_small*.yml' -o -name '*hash_literal_small*.rb' | sort) Calculating ------------------------------------- compare-ruby built-ruby hash_literal_small2 1.498 1.877 i/s - 1.000 times in 0.667581s 0.532656s hash_literal_small4 1.197 1.642 i/s - 1.000 times in 0.835375s 0.609160s hash_literal_small8 0.620 1.215 i/s - 1.000 times in 1.611638s 0.823090s Comparison: hash_literal_small2 built-ruby: 1.9 i/s compare-ruby: 1.5 i/s - 1.25x slower hash_literal_small4 built-ruby: 1.6 i/s compare-ruby: 1.2 i/s - 1.37x slower hash_literal_small8 built-ruby: 1.2 i/s compare-ruby: 0.6 i/s - 1.96x slower ``` Compared to r66255 ``` $ make benchmark ITEM=hash_literal_small COMPARE_RUBY=/Users/aaron/.rbenv/versions/ruby-trunk/bin/ruby generating known_errors.inc known_errors.inc unchanged ./revision.h unchanged /Users/aaron/.rbenv/shims/ruby --disable=gems -rrubygems -I./benchmark/lib ./benchmark/benchmark-driver/exe/benchmark-driver \ --executables="compare-ruby::/Users/aaron/.rbenv/versions/ruby-trunk/bin/ruby -I.ext/common --disable-gem" \ --executables="built-ruby::./miniruby -I./lib -I. -I.ext/common -r./prelude --disable-gem" \ $(find ./benchmark -maxdepth 1 -name '*hash_literal_small*.yml' -o -name '*hash_literal_small*.rb' | sort) Calculating ------------------------------------- compare-ruby built-ruby hash_literal_small2 1.567 1.831 i/s - 1.000 times in 0.638056s 0.546039s hash_literal_small4 1.298 1.652 i/s - 1.000 times in 0.770214s 0.605182s hash_literal_small8 0.873 1.216 i/s - 1.000 times in 1.145304s 0.822047s Comparison: hash_literal_small2 built-ruby: 1.8 i/s compare-ruby: 1.6 i/s - 1.17x slower hash_literal_small4 built-ruby: 1.7 i/s compare-ruby: 1.3 i/s - 1.27x slower hash_literal_small8 built-ruby: 1.2 i/s compare-ruby: 0.9 i/s - 1.39x slower ``` Modified files: trunk/compile.c trunk/insns.def trunk/test/ruby/test_jit.rb Index: test/ruby/test_jit.rb =================================================================== --- test/ruby/test_jit.rb (revision 66257) +++ test/ruby/test_jit.rb (revision 66258) @@ -239,8 +239,8 @@ class TestJIT < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_jit.rb#L239 assert_compile_once('a = 1; { a: a }', result_inspect: '{:a=>1}', insns: %i[newhash]) end - def test_compile_insn_newhashfromarray - assert_compile_once('{ a: 1 }', result_inspect: '{:a=>1}', insns: %i[newhashfromarray]) + def test_compile_insn_duphash + assert_compile_once('{ a: 1 }', result_inspect: '{:a=>1}', insns: %i[duphash]) end def test_compile_insn_newrange Index: insns.def =================================================================== --- insns.def (revision 66257) +++ insns.def (revision 66258) @@ -454,6 +454,16 @@ duparray https://github.com/ruby/ruby/blob/trunk/insns.def#L454 val = rb_ary_resurrect(ary); } +/* dup hash */ +DEFINE_INSN +duphash +(VALUE hash) +() +(VALUE val) +{ + val = rb_hash_dup(hash); +} + /* if TOS is an array expand, expand it to num objects. if the number of the array is less than num, push nils to fill. if it is greater than num, exceeding elements are dropped. @@ -514,19 +524,6 @@ newhash https://github.com/ruby/ruby/blob/trunk/insns.def#L524 } } -/* make new Hash object from (frozen) Array object */ -DEFINE_INSN -newhashfromarray -(rb_num_t num, VALUE ary) -() -(VALUE hash) -// attr bool leaf = false; /* rb_hash_bulk_insert() can call methods. */ -{ - VM_ASSERT(num * 2 == (rb_num_t)RARRAY_LEN(ary)); - hash = rb_hash_new_with_size(num); - rb_hash_bulk_insert(num * 2, RARRAY_CONST_PTR_TRANSIENT(ary), hash); -} - /* put new Range object.(Range.new(low, high, flag)) */ DEFINE_INSN newrange Index: compile.c =================================================================== --- compile.c (revision 66257) +++ compile.c (revision 66258) @@ -3984,7 +3984,12 @@ compile_array(rb_iseq_t *iseq, LINK_ANCH https://github.com/ruby/ruby/blob/trunk/compile.c#L3984 ADD_INSN1(ret, line, duparray, ary); } else { /* COMPILE_ARRAY_TYPE_HASH */ - ADD_INSN2(ret, line, newhashfromarray, INT2FIX(RARRAY_LEN(ary)/2), ary); + VALUE hash; + + hash = rb_hash_new_with_size(RARRAY_LEN(ary) / 2); + rb_hash_bulk_insert(RARRAY_LEN(ary), RARRAY_CONST_PTR_TRANSIENT(ary), hash); + iseq_add_mark_object_compile_time(iseq, hash); + ADD_INSN1(ret, line, duphash, hash); } } else { -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/