From b08dacfea39ad8da3f1fd7fdd0e4538cc892ec44 Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Thu, 18 Nov 2021 15:10:20 -0800 Subject: Optimize dynamic string interpolation for symbol/true/false/nil/0-9 This provides a significant speedup for symbol, true, false, nil, and 0-9, class/module, and a small speedup in most other cases. Speedups (using included benchmarks): :symbol :: 60% 0-9 :: 50% Class/Module :: 50% nil/true/false :: 20% integer :: 10% [] :: 10% "" :: 3% One reason this approach is faster is it reduces the number of VM instructions for each interpolated value. Initial idea, approach, and benchmarks from Eric Wong. I applied the same approach against the master branch, updating it to handle the significant internal changes since this was first proposed 4 years ago (such as CALL_INFO/CALL_CACHE -> CALL_DATA). I also expanded it to optimize true/false/nil/0-9/class/module, and added handling of missing methods, refined methods, and RUBY_DEBUG. This renames the tostring insn to anytostring, and adds an objtostring insn that implements the optimization. This requires making a few functions non-static, and adding some non-static functions. This disables 4 YJIT tests. Those tests should be reenabled after YJIT optimizes the new objtostring insn. Implements [Feature #13715] Co-authored-by: Eric Wong Co-authored-by: Alan Wu Co-authored-by: Yusuke Endoh Co-authored-by: Koichi Sasada --- test/ruby/test_jit.rb | 8 ++++---- test/ruby/test_optimization.rb | 30 ++++++++++++++++++++++++++++++ test/ruby/test_yjit.rb | 8 ++++---- 3 files changed, 38 insertions(+), 8 deletions(-) (limited to 'test/ruby') diff --git a/test/ruby/test_jit.rb b/test/ruby/test_jit.rb index f7ea67c023..dcbe694d7a 100644 --- a/test/ruby/test_jit.rb +++ b/test/ruby/test_jit.rb @@ -243,8 +243,8 @@ class TestJIT < Test::Unit::TestCase end; end - def test_compile_insn_putstring_concatstrings_tostring - assert_compile_once('"a#{}b" + "c"', result_inspect: '"abc"', insns: %i[putstring concatstrings tostring]) + def test_compile_insn_putstring_concatstrings_objtostring + assert_compile_once('"a#{}b" + "c"', result_inspect: '"abc"', insns: %i[putstring concatstrings objtostring]) end def test_compile_insn_toregexp @@ -482,8 +482,8 @@ class TestJIT < Test::Unit::TestCase end; end - def test_compile_insn_checktype - assert_compile_once("#{<<~"begin;"}\n#{<<~'end;'}", result_inspect: '"42"', insns: %i[checktype]) + def test_compile_insn_objtostring + assert_compile_once("#{<<~"begin;"}\n#{<<~'end;'}", result_inspect: '"42"', insns: %i[objtostring]) begin; a = '2' "4#{a}" diff --git a/test/ruby/test_optimization.rb b/test/ruby/test_optimization.rb index cbae6d5e8c..a834996788 100644 --- a/test/ruby/test_optimization.rb +++ b/test/ruby/test_optimization.rb @@ -903,4 +903,34 @@ class TestRubyOptimization < Test::Unit::TestCase raise "END" end; end + + class Objtostring + end + + def test_objtostring + assert_raise(NoMethodError){"#{BasicObject.new}"} + assert_redefine_method('Symbol', 'to_s', <<-'end') + assert_match %r{\A#\z}, "#{:foo}" + end + assert_redefine_method('NilClass', 'to_s', <<-'end') + assert_match %r{\A#\z}, "#{nil}" + end + assert_redefine_method('TrueClass', 'to_s', <<-'end') + assert_match %r{\A#\z}, "#{true}" + end + assert_redefine_method('FalseClass', 'to_s', <<-'end') + assert_match %r{\A#\z}, "#{false}" + end + assert_redefine_method('Integer', 'to_s', <<-'end') + (-1..10).each { |i| + assert_match %r{\A#\z}, "#{i}" + } + end + assert_equal "TestRubyOptimization::Objtostring", "#{Objtostring}" + assert_match %r{\A#\z}, "#{Class.new}" + assert_match %r{\A#\z}, "#{Module.new}" + o = Object.new + def o.to_s; 1; end + assert_match %r{\A#\z}, "#{o}" + end end diff --git a/test/ruby/test_yjit.rb b/test/ruby/test_yjit.rb index f0d899e908..9cb727eae1 100644 --- a/test/ruby/test_yjit.rb +++ b/test/ruby/test_yjit.rb @@ -215,7 +215,7 @@ class TestYJIT < Test::Unit::TestCase def test_compile_tostring assert_no_exits('"i am a string #{true}"') - end + end if false # Until objtostring supported def test_compile_opt_aset assert_compiles('[1,2,3][2] = 4', insns: %i[opt_aset]) @@ -240,7 +240,7 @@ class TestYJIT < Test::Unit::TestCase def test_compile_regexp assert_no_exits('/#{true}/') - end + end if false # Until objtostring supported def test_getlocal_with_level assert_compiles(<<~RUBY, insns: %i[getlocal opt_plus], result: [[7]]) @@ -385,7 +385,7 @@ class TestYJIT < Test::Unit::TestCase make_str("foo", "bar") make_str("foo", "bar") RUBY - end + end if false # Until objtostring supported def test_string_interpolation_cast assert_compiles(<<~'RUBY', insns: %i[checktype concatstrings tostring], result: "123") @@ -395,7 +395,7 @@ class TestYJIT < Test::Unit::TestCase make_str(1, 23) RUBY - end + end if false # Until objtostring supported def test_checkkeyword assert_compiles(<<~'RUBY', insns: %i[checkkeyword], result: [2, 5]) -- cgit v1.2.3