[前][次][番号順一覧][スレッド一覧]

ruby-changes:69814

From: Jeremy <ko1@a...>
Date: Fri, 19 Nov 2021 08:10:37 +0900 (JST)
Subject: [ruby-changes:69814] b08dacfea3 (master): Optimize dynamic string interpolation for symbol/true/false/nil/0-9

https://git.ruby-lang.org/ruby.git/commit/?id=b08dacfea3

From b08dacfea39ad8da3f1fd7fdd0e4538cc892ec44 Mon Sep 17 00:00:00 2001
From: Jeremy Evans <code@j...>
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 <e@8...>
Co-authored-by: Alan Wu <XrXr@u...>
Co-authored-by: Yusuke Endoh <mame@r...>
Co-authored-by: Koichi Sasada <ko1@a...>
---
 benchmark/vm_dstr_ary.rb          |  6 ++++
 benchmark/vm_dstr_bool.rb         |  7 +++++
 benchmark/vm_dstr_class_module.rb | 10 ++++++
 benchmark/vm_dstr_digit.rb        |  7 +++++
 benchmark/vm_dstr_int.rb          |  5 +++
 benchmark/vm_dstr_nil.rb          |  6 ++++
 benchmark/vm_dstr_obj.rb          |  6 ++++
 benchmark/vm_dstr_obj_def.rb      |  8 +++++
 benchmark/vm_dstr_str.rb          |  6 ++++
 benchmark/vm_dstr_sym.rb          |  6 ++++
 common.mk                         |  2 ++
 compile.c                         | 21 ++++++-------
 insns.def                         | 20 ++++++++++--
 numeric.c                         | 33 ++++++++++++++++++--
 object.c                          | 20 ++++++------
 test/ruby/test_jit.rb             |  8 ++---
 test/ruby/test_optimization.rb    | 30 ++++++++++++++++++
 test/ruby/test_yjit.rb            |  8 ++---
 vm_insnhelper.c                   | 64 +++++++++++++++++++++++++++++++++++++++
 yjit_codegen.c                    |  4 +--
 20 files changed, 240 insertions(+), 37 deletions(-)
 create mode 100644 benchmark/vm_dstr_ary.rb
 create mode 100644 benchmark/vm_dstr_bool.rb
 create mode 100644 benchmark/vm_dstr_class_module.rb
 create mode 100644 benchmark/vm_dstr_digit.rb
 create mode 100644 benchmark/vm_dstr_int.rb
 create mode 100644 benchmark/vm_dstr_nil.rb
 create mode 100644 benchmark/vm_dstr_obj.rb
 create mode 100644 benchmark/vm_dstr_obj_def.rb
 create mode 100644 benchmark/vm_dstr_str.rb
 create mode 100644 benchmark/vm_dstr_sym.rb

diff --git a/benchmark/vm_dstr_ary.rb b/benchmark/vm_dstr_ary.rb
new file mode 100644
index 00000000000..1d3aa3b97b0
--- /dev/null
+++ b/benchmark/vm_dstr_ary.rb
@@ -0,0 +1,6 @@ https://github.com/ruby/ruby/blob/trunk/benchmark/vm_dstr_ary.rb#L1
+i = 0
+x = y = []
+while i<6_000_000 # benchmark loop 2
+  i += 1
+  str = "foo#{x}bar#{y}baz"
+end
diff --git a/benchmark/vm_dstr_bool.rb b/benchmark/vm_dstr_bool.rb
new file mode 100644
index 00000000000..631ca54755c
--- /dev/null
+++ b/benchmark/vm_dstr_bool.rb
@@ -0,0 +1,7 @@ https://github.com/ruby/ruby/blob/trunk/benchmark/vm_dstr_bool.rb#L1
+i = 0
+x = true
+y = false
+while i<6_000_000 # benchmark loop 2
+  i += 1
+  str = "foo#{x}bar#{y}baz"
+end
diff --git a/benchmark/vm_dstr_class_module.rb b/benchmark/vm_dstr_class_module.rb
new file mode 100644
index 00000000000..becf0861c7d
--- /dev/null
+++ b/benchmark/vm_dstr_class_module.rb
@@ -0,0 +1,10 @@ https://github.com/ruby/ruby/blob/trunk/benchmark/vm_dstr_class_module.rb#L1
+i = 0
+class A; end unless defined?(A)
+module B; end unless defined?(B)
+x = A
+y = B
+while i<6_000_000 # benchmark loop 2
+  i += 1
+  str = "foo#{x}bar#{y}baz"
+end
+
diff --git a/benchmark/vm_dstr_digit.rb b/benchmark/vm_dstr_digit.rb
new file mode 100644
index 00000000000..caaa395192b
--- /dev/null
+++ b/benchmark/vm_dstr_digit.rb
@@ -0,0 +1,7 @@ https://github.com/ruby/ruby/blob/trunk/benchmark/vm_dstr_digit.rb#L1
+i = 0
+x = 0
+y = 9
+while i<6_000_000 # benchmark loop 2
+  i += 1
+  str = "foo#{x}bar#{y}baz"
+end
diff --git a/benchmark/vm_dstr_int.rb b/benchmark/vm_dstr_int.rb
new file mode 100644
index 00000000000..ed380d75953
--- /dev/null
+++ b/benchmark/vm_dstr_int.rb
@@ -0,0 +1,5 @@ https://github.com/ruby/ruby/blob/trunk/benchmark/vm_dstr_int.rb#L1
+i = 0
+while i<6_000_000 # benchmark loop 2
+  i += 1
+  str = "foo#{i}bar#{i}baz"
+end
diff --git a/benchmark/vm_dstr_nil.rb b/benchmark/vm_dstr_nil.rb
new file mode 100644
index 00000000000..ec4f5d6c677
--- /dev/null
+++ b/benchmark/vm_dstr_nil.rb
@@ -0,0 +1,6 @@ https://github.com/ruby/ruby/blob/trunk/benchmark/vm_dstr_nil.rb#L1
+i = 0
+x = y = nil
+while i<6_000_000 # benchmark loop 2
+  i += 1
+  str = "foo#{x}bar#{y}baz"
+end
diff --git a/benchmark/vm_dstr_obj.rb b/benchmark/vm_dstr_obj.rb
new file mode 100644
index 00000000000..fb78637ead6
--- /dev/null
+++ b/benchmark/vm_dstr_obj.rb
@@ -0,0 +1,6 @@ https://github.com/ruby/ruby/blob/trunk/benchmark/vm_dstr_obj.rb#L1
+i = 0
+x = y = Object.new
+while i<6_000_000 # benchmark loop 2
+  i += 1
+  str = "foo#{x}bar#{y}baz"
+end
diff --git a/benchmark/vm_dstr_obj_def.rb b/benchmark/vm_dstr_obj_def.rb
new file mode 100644
index 00000000000..99ff7b98fbb
--- /dev/null
+++ b/benchmark/vm_dstr_obj_def.rb
@@ -0,0 +1,8 @@ https://github.com/ruby/ruby/blob/trunk/benchmark/vm_dstr_obj_def.rb#L1
+i = 0
+o = Object.new
+def o.to_s; -""; end
+x = y = o
+while i<6_000_000 # benchmark loop 2
+  i += 1
+  str = "foo#{x}bar#{y}baz"
+end
diff --git a/benchmark/vm_dstr_str.rb b/benchmark/vm_dstr_str.rb
new file mode 100644
index 00000000000..45fc1078926
--- /dev/null
+++ b/benchmark/vm_dstr_str.rb
@@ -0,0 +1,6 @@ https://github.com/ruby/ruby/blob/trunk/benchmark/vm_dstr_str.rb#L1
+i = 0
+x = y = ""
+while i<6_000_000 # benchmark loop 2
+  i += 1
+  str = "foo#{x}bar#{y}baz"
+end
diff --git a/benchmark/vm_dstr_sym.rb b/benchmark/vm_dstr_sym.rb
new file mode 100644
index 00000000000..484b8f8150f
--- /dev/null
+++ b/benchmark/vm_dstr_sym.rb
@@ -0,0 +1,6 @@ https://github.com/ruby/ruby/blob/trunk/benchmark/vm_dstr_sym.rb#L1
+i = 0
+x = y = :z
+while i<6_000_000 # benchmark loop 2
+  i += 1
+  str = "foo#{x}bar#{y}baz"
+end
diff --git a/common.mk b/common.mk
index c840ec654de..714882a9b0d 100644
--- a/common.mk
+++ b/common.mk
@@ -9522,6 +9522,7 @@ numeric.$(OBJEXT): $(top_srcdir)/internal/hash.h https://github.com/ruby/ruby/blob/trunk/common.mk#L9522
 numeric.$(OBJEXT): $(top_srcdir)/internal/numeric.h
 numeric.$(OBJEXT): $(top_srcdir)/internal/object.h
 numeric.$(OBJEXT): $(top_srcdir)/internal/rational.h
+numeric.$(OBJEXT): $(top_srcdir)/internal/string.h
 numeric.$(OBJEXT): $(top_srcdir)/internal/serial.h
 numeric.$(OBJEXT): $(top_srcdir)/internal/static_assert.h
 numeric.$(OBJEXT): $(top_srcdir)/internal/util.h
@@ -9598,6 +9599,7 @@ numeric.$(OBJEXT): {$(VPATH)}internal/compiler_is/intel.h https://github.com/ruby/ruby/blob/trunk/common.mk#L9599
 numeric.$(OBJEXT): {$(VPATH)}internal/compiler_is/msvc.h
 numeric.$(OBJEXT): {$(VPATH)}internal/compiler_is/sunpro.h
 numeric.$(OBJEXT): {$(VPATH)}internal/compiler_since.h
+numeric.$(OBJEXT): {$(VPATH)}internal/compilers.h
 numeric.$(OBJEXT): {$(VPATH)}internal/config.h
 numeric.$(OBJEXT): {$(VPATH)}internal/constant_p.h
 numeric.$(OBJEXT): {$(VPATH)}internal/core.h
diff --git a/compile.c b/compile.c
index 8a459b25589..2b92893d801 100644
--- a/compile.c
+++ b/compile.c
@@ -3271,13 +3271,13 @@ iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcal https://github.com/ruby/ruby/blob/trunk/compile.c#L3271
 	}
     }
 
-    if (IS_INSN_ID(iobj, tostring)) {
+    if (IS_INSN_ID(iobj, anytostring)) {
 	LINK_ELEMENT *next = iobj->link.next;
 	/*
-	 *  tostring
+         *  anytostring
 	 *  concatstrings 1
 	 * =>
-	 *  tostring
+         *  anytostring
 	 */
 	if (IS_INSN(next) && IS_INSN_ID(next, concatstrings) &&
 	    OPERAND_AT(next, 0) == INT2FIX(1)) {
@@ -7642,17 +7642,14 @@ compile_evstr(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, i https://github.com/ruby/ruby/blob/trunk/compile.c#L7642
     CHECK(COMPILE_(ret, "nd_body", node, popped));
 
     if (!popped && !all_string_result_p(node)) {
-	const int line = nd_line(node);
         const NODE *line_node = node;
 	const unsigned int flag = VM_CALL_FCALL;
-	LABEL *isstr = NEW_LABEL(line);
-	ADD_INSN(ret, line_node, dup);
-	ADD_INSN1(ret, line_node, checktype, INT2FIX(T_STRING));
-	ADD_INSNL(ret, line_node, branchif, isstr);
-	ADD_INSN(ret, line_node, dup);
-	ADD_SEND_R(ret, line_node, idTo_s, INT2FIX(0), NULL, INT2FIX(flag), NULL);
-	ADD_INSN(ret, line_node, tostring);
-	ADD_LABEL(ret, isstr);
+
+        // Note, this dup could be removed if we are willing to change anytostring. It pops
+        // two VALUEs off the stack when it could work by replacing the top most VALUE.
+        ADD_INSN(ret, line_node, dup);
+        ADD_INSN1(ret, line_node, objtostring, new_callinfo(iseq, idTo_s, 0, flag, NULL, FALSE));
+        ADD_INSN(ret, line_node, anytostring);
     }
     return COMPILE_OK;
 }
diff --git a/insns.def b/insns.def
index 371361512f4..f759c1a5c51 100644
--- a/insns.def
+++ b/insns.def
@@ -381,9 +381,10 @@ concatstrings https://github.com/ruby/ruby/blob/trunk/insns.def#L381
     val = rb_str_concat_literals(num, STACK_ADDR_FROM_TOP(num));
 }
 
-/* push the result of to_s. */
+/* Convert the result to string if not already a string.
+   This is used as a backup if t (... truncated)

--
ML: ruby-changes@q...
Info: http://www.atdot.net/~ko1/quickml/

[前][次][番号順一覧][スレッド一覧]