1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00
ruby--ruby/test/ruby/test_jit.rb

641 lines
16 KiB
Ruby
Raw Normal View History

# frozen_string_literal: true
require 'test/unit'
require 'tmpdir'
require_relative '../lib/jit_support'
# Test for --jit option
class TestJIT < Test::Unit::TestCase
include JITSupport
# Ensure all supported insns can be compiled. Only basic tests are included.
# TODO: ensure --dump=insns includes the expected insn
def setup
unless JITSupport.supported?
skip 'JIT seems not supported on this platform'
end
end
def test_compile_insn_nop
assert_compile_once('nil rescue true', result_inspect: 'nil')
end
def test_compile_insn_local
assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1')
begin;
foo = 1
foo
end;
end
def test_compile_insn_blockparam
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '3', success_count: 2)
begin;
def foo(&b)
a = b
b = 2
a.call + 2
end
print foo { 1 }
end;
end
def test_compile_insn_getblockparamproxy
skip "support this in mjit_compile"
end
def test_compile_insn_getspecial
assert_compile_once('$1', result_inspect: 'nil')
end
def test_compile_insn_setspecial
assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: 'true')
begin;
true if nil.nil?..nil.nil?
end;
end
def test_compile_insn_instancevariable
assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1')
begin;
@foo = 1
@foo
end;
end
def test_compile_insn_classvariable
assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1')
begin;
@@foo = 1
@@foo
end;
end
def test_compile_insn_constant
assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1')
begin;
FOO = 1
FOO
end;
end
def test_compile_insn_global
assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1')
begin;
$foo = 1
$foo
end;
end
def test_compile_insn_putnil
assert_compile_once('nil', result_inspect: 'nil')
end
def test_compile_insn_putself
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: 'hello', success_count: 1)
begin;
proc { print "hello" }.call
end;
end
def test_compile_insn_putobject
assert_compile_once('0', result_inspect: '0') # putobject_OP_INT2FIX_O_0_C_
assert_compile_once('1', result_inspect: '1') # putobject_OP_INT2FIX_O_1_C_
assert_compile_once('2', result_inspect: '2')
end
def test_compile_insn_putspecialobject_putiseq
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: 'hello', success_count: 2)
begin;
print proc {
def method_definition
'hello'
end
method_definition
}.call
end;
end
def test_compile_insn_putstring_concatstrings_tostring
assert_compile_once('"a#{}b" + "c"', result_inspect: '"abc"')
end
def test_compile_insn_freezestring
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~'end;'}", stdout: 'true', success_count: 1)
begin;
# frozen_string_literal: true
print proc { "#{true}".frozen? }.call
end;
end
def test_compile_insn_toregexp
assert_compile_once('/#{true}/ =~ "true"', result_inspect: '0')
end
mjit_compile.c: use local variables for stack if catch_except_p is FALSE. If catch_except_p is TRUE, stack values should be on VM's stack when exception is thrown and the JIT-ed frame is re-executed by VM's exception handler. If it's FALSE, the JIT-ed frame won't be re-executed and don't need to keep values on VM's stack. Using local variables allows us to reduce cfp->sp motion. Moving cfp->sp is needed only for insns whose handles_frame? is false. So it improves performance. _mjit_compile_insn.erb: Prepare `stack_size` variable for GET_SP, STACK_ADDR_FROM_TOP, TOPN macros. Share pc and sp motion partial view. Use cancel handler created in mjit_compile.c. _mjit_compile_send.erb: ditto. Also, when iseq->body->catch_except_p is TRUE, this stops to call mjit_exec directly. I described the reason in vm_insnhelper.h's comment for EXEC_EC_CFP. _mjit_compile_pc_and_sp.erb: Shared logic for moving sp and pc. As you can see from thsi file, when status->local_stack_p is TRUE and insn.handles_frame? is false, moving sp is skipped. But if insn.handles_frame? is true, values should be rolled back to VM's stack. common.mk: add dependency for the file _mjit_compile_insn_body.erb: Set sp value before canceling JIT on DISPATCH_ORIGINAL_INSN. Replace GET_SP, STACK_ADDR_FROM_TOP, TOPN macros for the case ocal_stack_p is TRUE and insn.handles_frame? is false. In that case, values are not available on VM's stack and those macros should be replaced. mjit_compile.inc.erb: updated comments of macros which are supported by JIT compiler. All references to `cfp->sp` should be replaced and thus INC_SP, SET_SV, PUSH are no longer supported for now, because they are not used now. vm_exec.h: moved EXEC_EC_CFP definition to vm_insnhelper.h because it's tighly coupled to CALL_METHOD. vm_insnhelper.h: Have revised EXEC_EC_CFP definition moved from vm_exec.h. Now it triggers mjit_exec for VM, and has the guard for catch_except_p on JIT-ed code. See comments for details. CALL_METHOD delegates triggering mjit_exec to EXEC_EC_CFP. insns.def: Stopped using EXEC_EC_CFP for the case we don't want to trigger mjit_exec. Those insns (defineclass, opt_call_c_function) are not supported by JIT and it's safe to use RESTORE_REGS(), NEXT_INSN(). expandarray is changed to pass GET_SP() to replace the macro in _mjit_compile_insn_body.erb. vm_insnhelper.c: change to take sp for the above reason. [close https://github.com/ruby/ruby/pull/1828] This patch resurrects the performance which was attached in [Feature #14235]. * Benchmark Optcarrot (with configuration for benchmark_driver.gem) https://github.com/benchmark-driver/optcarrot $ benchmark-driver benchmark.yml --verbose 1 --rbenv 'before;before+JIT::before,--jit;after;after+JIT::after,--jit' --repeat-count 10 before: ruby 2.6.0dev (2018-03-04 trunk 62652) [x86_64-linux] before+JIT: ruby 2.6.0dev (2018-03-04 trunk 62652) +JIT [x86_64-linux] after: ruby 2.6.0dev (2018-03-04 local-variable.. 62652) [x86_64-linux] last_commit=mjit_compile.c: use local variables for stack after+JIT: ruby 2.6.0dev (2018-03-04 local-variable.. 62652) +JIT [x86_64-linux] last_commit=mjit_compile.c: use local variables for stack Calculating ------------------------------------- before before+JIT after after+JIT optcarrot 53.552 59.680 53.697 63.358 fps Comparison: optcarrot after+JIT: 63.4 fps before+JIT: 59.7 fps - 1.06x slower after: 53.7 fps - 1.18x slower before: 53.6 fps - 1.18x slower git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@62655 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2018-03-04 02:04:40 -05:00
def test_compile_insn_newarray
assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '[1, 2, 3]')
begin;
a, b, c = 1, 2, 3
[a, b, c]
end;
end
def test_compile_insn_intern_duparray
assert_compile_once('[:"#{0}"] + [1,2,3]', result_inspect: '[:"0", 1, 2, 3]')
end
def test_compile_insn_expandarray
assert_compile_once('y = [ true, false, nil ]; x, = y; x', result_inspect: 'true')
end
def test_compile_insn_concatarray
assert_compile_once('["t", "r", *x = "u", "e"].join', result_inspect: '"true"')
end
def test_compile_insn_splatarray
assert_compile_once('[*(1..2)]', result_inspect: '[1, 2]')
end
def test_compile_insn_newhash
assert_compile_once('a = 1; { a: a }', result_inspect: '{:a=>1}')
end
def test_compile_insn_newrange
assert_compile_once('a = 1; 0..a', result_inspect: '0..1')
end
def test_compile_insn_pop
assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1')
begin;
a = false
b = 1
a || b
end;
end
def test_compile_insn_dup
assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '3')
begin;
a = 1
a&.+(2)
end;
end
def test_compile_insn_dupn
assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: 'true')
begin;
klass = Class.new
klass::X ||= true
end;
end
def test_compile_insn_swap_topn
assert_compile_once('{}["true"] = true', result_inspect: 'true')
end
def test_compile_insn_reverse
assert_compile_once('q, (w, e), r = 1, [2, 3], 4; e == 3', result_inspect: 'true')
end
def test_compile_insn_reput
skip "write test"
end
def test_compile_insn_setn
assert_compile_once('[nil][0] = 1', result_inspect: '1')
end
def test_compile_insn_adjuststack
assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: 'true')
begin;
x = [true]
x[0] ||= nil
x[0]
end;
end
def test_compile_insn_defined
assert_compile_once('defined?(a)', result_inspect: 'nil')
end
def test_compile_insn_checkkeyword
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: 'true', success_count: 1)
begin;
def test(x: rand)
x
end
print test(x: true)
end;
end
def test_compile_insn_tracecoverage
skip "write test"
end
def test_compile_insn_defineclass
skip "support this in mjit_compile (low priority)"
end
def test_compile_insn_send
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '1', success_count: 2)
begin;
print proc { yield_self { 1 } }.call
end;
end
def test_compile_insn_opt_str_freeze
assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '"foo"')
begin;
'foo'.freeze
end;
end
def test_compile_insn_opt_str_uminus
assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '"bar"')
begin;
-'bar'
end;
end
def test_compile_insn_opt_newarray_max
assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '2')
begin;
a = 1
b = 2
[a, b].max
end;
end
def test_compile_insn_opt_newarray_min
assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1')
begin;
a = 1
b = 2
[a, b].min
end;
end
def test_compile_insn_opt_send_without_block
assert_compile_once('print', result_inspect: 'nil')
end
def test_compile_insn_invokesuper
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '3', success_count: 4)
begin;
mod = Module.new {
def test
super + 2
end
}
klass = Class.new {
prepend mod
def test
1
end
}
print klass.new.test
end;
end
def test_compile_insn_invokeblock_leave
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '2', success_count: 2)
begin;
def foo
yield
end
print foo { 2 }
end;
end
def test_compile_insn_throw
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '4', success_count: 2)
begin;
def test
proc do
if 1+1 == 1
return 3
else
return 4
end
5
end.call
end
print test
end;
end
def test_compile_insn_jump_branchif
assert_compile_once("#{<<~"begin;"}\n#{<<~'end;'}", result_inspect: 'nil')
begin;
a = false
1 + 1 while false
end;
end
def test_compile_insn_branchunless
assert_compile_once("#{<<~"begin;"}\n#{<<~'end;'}", result_inspect: '1')
begin;
a = true
if a
1
else
2
end
end;
end
def test_compile_insn_branchnil
assert_compile_once("#{<<~"begin;"}\n#{<<~'end;'}", result_inspect: '3')
begin;
a = 2
a&.+(1)
end;
end
def test_compile_insn_checktype
assert_compile_once("#{<<~"begin;"}\n#{<<~'end;'}", result_inspect: '"42"')
begin;
a = '2'
"4#{a}"
end;
end
def test_compile_insn_inlinecache
assert_compile_once('Struct', result_inspect: 'Struct')
end
def test_compile_insn_once
assert_compile_once('/#{true}/o =~ "true" && $~.to_a', result_inspect: '["true"]')
end
def test_compile_insn_checkmatch_opt_case_dispatch
assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '"world"')
begin;
case 'hello'
when /hello/
'world'
end
end;
end
def test_compile_insn_opt_calc
assert_compile_once('4 + 2 - ((2 * 3 / 2) % 2)', result_inspect: '5')
assert_compile_once('4 + 2', result_inspect: '6')
end
def test_compile_insn_opt_cmp
assert_compile_once('(1 == 1) && (1 != 2)', result_inspect: 'true')
end
def test_compile_insn_opt_rel
assert_compile_once('1 < 2 && 1 <= 1 && 2 > 1 && 1 >= 1', result_inspect: 'true')
end
def test_compile_insn_opt_ltlt
assert_compile_once('[1] << 2', result_inspect: '[1, 2]')
end
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 ``` git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@62398 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2018-02-13 10:58:38 -05:00
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 }
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 ``` git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@62398 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2018-02-13 10:58:38 -05:00
(hash['2'] = 2) + (hash[1.to_s] = 3)
end;
end
def test_compile_insn_opt_length_size
assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '4')
begin;
array = [1, 2]
array.length + array.size
end;
end
def test_compile_insn_opt_empty_p
assert_compile_once('[].empty?', result_inspect: 'true')
end
def test_compile_insn_opt_succ
assert_compile_once('1.succ', result_inspect: '2')
end
def test_compile_insn_opt_not
assert_compile_once('!!true', result_inspect: 'true')
end
def test_compile_insn_opt_regexpmatch1
assert_compile_once("/true/ =~ 'true'", result_inspect: '0')
end
def test_compile_insn_opt_regexpmatch2
assert_compile_once("'true' =~ /true/", result_inspect: '0')
end
def test_compile_insn_opt_call_c_function
skip "support this in opt_call_c_function (low priority)"
end
def test_jit_output
out, err = eval_with_jit('5.times { puts "MJIT" }', verbose: 1, min_calls: 5)
assert_equal("MJIT\n" * 5, out)
assert_match(/^#{JIT_SUCCESS_PREFIX}: block in <main>@-e:1 -> .+_ruby_mjit_p\d+u\d+\.c$/, err)
assert_match(/^Successful MJIT finish$/, err)
end
mjit_compile.c: use local variables for stack if catch_except_p is FALSE. If catch_except_p is TRUE, stack values should be on VM's stack when exception is thrown and the JIT-ed frame is re-executed by VM's exception handler. If it's FALSE, the JIT-ed frame won't be re-executed and don't need to keep values on VM's stack. Using local variables allows us to reduce cfp->sp motion. Moving cfp->sp is needed only for insns whose handles_frame? is false. So it improves performance. _mjit_compile_insn.erb: Prepare `stack_size` variable for GET_SP, STACK_ADDR_FROM_TOP, TOPN macros. Share pc and sp motion partial view. Use cancel handler created in mjit_compile.c. _mjit_compile_send.erb: ditto. Also, when iseq->body->catch_except_p is TRUE, this stops to call mjit_exec directly. I described the reason in vm_insnhelper.h's comment for EXEC_EC_CFP. _mjit_compile_pc_and_sp.erb: Shared logic for moving sp and pc. As you can see from thsi file, when status->local_stack_p is TRUE and insn.handles_frame? is false, moving sp is skipped. But if insn.handles_frame? is true, values should be rolled back to VM's stack. common.mk: add dependency for the file _mjit_compile_insn_body.erb: Set sp value before canceling JIT on DISPATCH_ORIGINAL_INSN. Replace GET_SP, STACK_ADDR_FROM_TOP, TOPN macros for the case ocal_stack_p is TRUE and insn.handles_frame? is false. In that case, values are not available on VM's stack and those macros should be replaced. mjit_compile.inc.erb: updated comments of macros which are supported by JIT compiler. All references to `cfp->sp` should be replaced and thus INC_SP, SET_SV, PUSH are no longer supported for now, because they are not used now. vm_exec.h: moved EXEC_EC_CFP definition to vm_insnhelper.h because it's tighly coupled to CALL_METHOD. vm_insnhelper.h: Have revised EXEC_EC_CFP definition moved from vm_exec.h. Now it triggers mjit_exec for VM, and has the guard for catch_except_p on JIT-ed code. See comments for details. CALL_METHOD delegates triggering mjit_exec to EXEC_EC_CFP. insns.def: Stopped using EXEC_EC_CFP for the case we don't want to trigger mjit_exec. Those insns (defineclass, opt_call_c_function) are not supported by JIT and it's safe to use RESTORE_REGS(), NEXT_INSN(). expandarray is changed to pass GET_SP() to replace the macro in _mjit_compile_insn_body.erb. vm_insnhelper.c: change to take sp for the above reason. [close https://github.com/ruby/ruby/pull/1828] This patch resurrects the performance which was attached in [Feature #14235]. * Benchmark Optcarrot (with configuration for benchmark_driver.gem) https://github.com/benchmark-driver/optcarrot $ benchmark-driver benchmark.yml --verbose 1 --rbenv 'before;before+JIT::before,--jit;after;after+JIT::after,--jit' --repeat-count 10 before: ruby 2.6.0dev (2018-03-04 trunk 62652) [x86_64-linux] before+JIT: ruby 2.6.0dev (2018-03-04 trunk 62652) +JIT [x86_64-linux] after: ruby 2.6.0dev (2018-03-04 local-variable.. 62652) [x86_64-linux] last_commit=mjit_compile.c: use local variables for stack after+JIT: ruby 2.6.0dev (2018-03-04 local-variable.. 62652) +JIT [x86_64-linux] last_commit=mjit_compile.c: use local variables for stack Calculating ------------------------------------- before before+JIT after after+JIT optcarrot 53.552 59.680 53.697 63.358 fps Comparison: optcarrot after+JIT: 63.4 fps before+JIT: 59.7 fps - 1.06x slower after: 53.7 fps - 1.18x slower before: 53.6 fps - 1.18x slower git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@62655 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2018-03-04 02:04:40 -05:00
def test_local_stack_on_exception
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '3', success_count: 2)
begin;
def b
raise
rescue
2
end
def a
# Calling #b should be vm_exec, not direct mjit_exec.
# Otherwise `1` on local variable would be purged.
1 + b
end
print a
end;
end
def test_local_stack_with_sp_motion_by_blockargs
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '1', success_count: 2)
begin;
def b(base)
1
end
# This method is simple enough to have false in catch_except_p.
# So local_stack_p would be true in JIT compiler.
def a
m = method(:b)
# ci->flag has VM_CALL_ARGS_BLOCKARG and cfp->sp is moved in vm_caller_setup_arg_block.
# So, for this send insn, JIT-ed code should use cfp->sp instead of local variables for stack.
Module.module_eval(&m)
end
print a
end;
end
def test_catching_deep_exception
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '1', success_count: 4)
begin;
def catch_true(paths, prefixes) # catch_except_p: TRUE
prefixes.each do |prefix| # catch_except_p: TRUE
paths.each do |path| # catch_except_p: FALSE
return path
end
end
end
def wrapper(paths, prefixes)
catch_true(paths, prefixes)
end
print wrapper(['1'], ['2'])
end;
end
def test_attr_reader
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "4nil\nnil\n6", success_count: 2, min_calls: 2)
begin;
class A
attr_reader :a, :b
def initialize
@a = 2
end
def test
a
end
def undefined
b
end
end
a = A.new
print(a.test * a.test)
p(a.undefined)
p(a.undefined)
# redefinition
class A
def test
3
end
end
print(2 * a.test)
end;
end
def test_clean_so
Dir.mktmpdir("jit_test_clean_so_") do |dir|
code = "x = 0; 10.times {|i|x+=i}"
eval_with_jit({"TMPDIR"=>dir}, code)
assert_send([Dir, :empty?, dir])
eval_with_jit({"TMPDIR"=>dir}, code, save_temps: true)
assert_not_send([Dir, :empty?, dir])
end
end
def test_lambda_longjmp
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '5', success_count: 1)
begin;
fib = lambda do |x|
return x if x == 0 || x == 1
fib.call(x-1) + fib.call(x-2)
end
print fib.call(5)
end;
end
private
# The shortest way to test one proc
def assert_compile_once(script, result_inspect:)
if script.match?(/\A\n.+\n\z/m)
script = script.gsub(/^/, ' ')
else
script = " #{script} "
end
assert_eval_with_jit("p proc {#{script}}.call", stdout: "#{result_inspect}\n", success_count: 1)
end
# Shorthand for normal test cases
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 ``` git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@62398 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2018-02-13 10:58:38 -05:00
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
# Debugging on CI
if err.include?("error trying to exec 'cc1': execvp: No such file or directory") && RbConfig::CONFIG['CC'].start_with?('gcc')
$stderr.puts "\ntest/ruby/test_jit.rb: DEBUG OUTPUT:"
cc1 = %x`gcc -print-prog-name=cc1`.rstrip
if $?.success?
$stderr.puts "cc1 path: #{cc1}"
$stderr.puts "executable?: #{File.executable?(cc1)}"
$stderr.puts "ls:\n#{IO.popen(['ls', '-la', File.dirname(cc1)], &:read)}"
else
$stderr.puts 'Failed to fetch cc1 path'
end
end
assert_equal(
success_count, actual,
"Expected #{success_count} times of JIT success, but succeeded #{actual} times.\n\n"\
"script:\n#{code_block(script)}\nstderr:\n#{code_block(err)}",
)
if stdout
assert_equal(stdout, out, "Expected stdout #{out.inspect} to match #{stdout.inspect} with script:\n#{code_block(script)}")
end
end
# Run Ruby script with --jit-wait (Synchronous JIT compilation).
# Returns [stdout, stderr]
def eval_with_jit(env = nil, script, **opts)
stdout, stderr, status = super
assert_equal(true, status.success?, "Failed to run script with JIT:\n#{code_block(script)}\nstdout:\n#{code_block(stdout)}\nstderr:\n#{code_block(stderr)}")
[stdout, stderr]
end
def code_block(code)
"```\n#{code}\n```\n\n"
end
end