ruby-changes:50280
From: k0kubun <ko1@a...>
Date: Wed, 14 Feb 2018 00:58:43 +0900 (JST)
Subject: [ruby-changes:50280] k0kubun:r62398 (trunk): mjit_compile.inc.erb: replace opt_key insn
k0kubun 2018-02-14 00:58:38 +0900 (Wed, 14 Feb 2018) New Revision: 62398 https://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=revision&revision=62398 Log: mjit_compile.inc.erb: replace opt_key insn with opt_send_without_block insn if call cache has valid ISeq. If the receiver is not optimized target of opt_key (i.e. Hash or Array), it triggers JIT cancel and it would be slow. This change allows JIT to drop the check for Hash/Array and continue to execute JIT even if the receiver is not Hash or Array. See the following benchmark results. It's not improved so much, but it would be effective when we achieve Ruby method inlining in _mjit_compile_send.erb. * Micro benchmark Given the following bench.rb, ``` class HashWithIndifferentAccess < Hash def []=(key, value) super(key.to_s, value) end def [](key) super(key.to_s) end end indhash = HashWithIndifferentAccess.new indhash[:foo] = 'bar' key = 'foo' 100000000.times do indhash[key] end ``` ** before ``` $ time ./ruby --disable-gems --jit-verbose=1 /tmp/bench.rb JIT success (31.4ms): block in <main>@/tmp/bench.rb:15 -> /tmp/_ruby_mjit_p18206u0.c JIT success (669.3ms): []@/tmp/bench.rb:6 -> /tmp/_ruby_mjit_p18206u1.c Successful MJIT finish ./ruby --disable-gems --jit-verbose=1 /tmp/bench.rb 12.21s user 0.04s system 107% cpu 11.394 total ``` ** after ``` $ time ./ruby --disable-gems --jit-verbose=1 /tmp/bench.rb JIT success (41.0ms): block in <main>@/tmp/bench.rb:15 -> /tmp/_ruby_mjit_p17293u0.c JIT success (679.0ms): []@/tmp/bench.rb:6 -> /tmp/_ruby_mjit_p17293u1.c Successful MJIT finish ./ruby --disable-gems --jit-verbose=1 /tmp/bench.rb 11.54s user 0.06s system 108% cpu 10.726 total ``` The execution time is shortened. * optcarrot benchmark Optcarrot has no room to be improved by this change. Almost nothing is changed. fps: 59.54 (before) -> 59.51 (after) * discourse benchmark I expected this to be improved a little, but it isn't too. ** before (JIT) ``` categories_admin: 50: 12 75: 13 90: 14 99: 22 home_admin: 50: 12 75: 13 90: 16 99: 22 topic_admin: 50: 12 75: 13 90: 15 99: 21 categories: 50: 18 75: 19 90: 23 99: 27 home: 50: 3 75: 4 90: 4 99: 12 topic: 50: 11 75: 11 90: 14 99: 20 ``` ** after (JIT) ``` categories_admin: 50: 12 75: 12 90: 16 99: 24 home_admin: 50: 12 75: 12 90: 14 99: 21 topic_admin: 50: 12 75: 13 90: 16 99: 21 categories: 50: 17 75: 18 90: 23 99: 32 home: 50: 3 75: 4 90: 4 99: 10 topic: 50: 11 75: 12 90: 13 99: 20 ``` Modified files: trunk/test/ruby/test_jit.rb trunk/tool/ruby_vm/views/mjit_compile.inc.erb Index: tool/ruby_vm/views/mjit_compile.inc.erb =================================================================== --- tool/ruby_vm/views/mjit_compile.inc.erb (revision 62397) +++ tool/ruby_vm/views/mjit_compile.inc.erb (revision 62398) @@ -21,6 +21,11 @@ https://github.com/ruby/ruby/blob/trunk/tool/ruby_vm/views/mjit_compile.inc.erb#L21 % 'opt_call_c_function', # low priority % ] % +% opt_send_without_block = RubyVM::Instructions.find { |i| i.name == 'opt_send_without_block' } +% if opt_send_without_block.nil? +% raise 'opt_send_without_block not found' +% end +% % # Available variables and macros in JIT-ed function: % # ec: the first argument of _mjitXXX % # reg_cfp: the second argument of _mjitXXX @@ -46,6 +51,8 @@ switch (insn) { https://github.com/ruby/ruby/blob/trunk/tool/ruby_vm/views/mjit_compile.inc.erb#L51 case BIN(<%= insn.name %>): % if %w[opt_send_without_block send].include?(insn.name) <%= render 'mjit_compile_send', locals: { insn: insn } -%> +% elsif %w[opt_aref].include?(insn.name) # experimental. TODO: increase insns and make the list automatically by finding DISPATCH_ORIGINAL_INSN +<%= render 'mjit_compile_send', locals: { insn: opt_send_without_block } -%> % end <%= render 'mjit_compile_insn', locals: { insn: insn, dispatched: false } -%> break; Index: test/ruby/test_jit.rb =================================================================== --- test/ruby/test_jit.rb (revision 62397) +++ test/ruby/test_jit.rb (revision 62398) @@ -419,11 +419,40 @@ class TestJIT < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_jit.rb#L419 assert_compile_once('[1] << 2', result_inspect: '[1, 2]') end - def test_compile_insn_opt_aref_aset - assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '8') + def test_compile_insn_opt_aref + # optimized call (optimized JIT) -> send call + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '21', success_count: 2, min_calls: 1) + begin; + obj = Object.new + def obj.[](h) + h + end + + block = proc { |h| h[1] } + print block.call({ 1 => 2 }) + print block.call(obj) + end; + + # send call -> optimized call (send JIT) -> optimized call + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '122', success_count: 1, min_calls: 2) + begin; + obj = Object.new + def obj.[](h) + h + end + + block = proc { |h| h[1] } + print block.call(obj) + print block.call({ 1 => 2 }) + print block.call({ 1 => 2 }) + end; + end + + def test_compile_insn_opt_aset + assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '5') begin; hash = { '1' => 2 } - hash['1'] + hash[1.to_s] + (hash['2'] = 2) + (hash[2.to_s] = 2) + (hash['2'] = 2) + (hash[1.to_s] = 3) end; end @@ -479,8 +508,8 @@ class TestJIT < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_jit.rb#L508 end # Shorthand for normal test cases - def assert_eval_with_jit(script, stdout: nil, success_count:) - out, err = eval_with_jit(script, verbose: 1, min_calls: 1) + def assert_eval_with_jit(script, stdout: nil, success_count:, min_calls: 1) + out, err = eval_with_jit(script, verbose: 1, min_calls: min_calls) actual = err.scan(/^#{JIT_SUCCESS_PREFIX}:/).size assert_equal( success_count, actual, -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/