diff options
author | Takashi Kokubun <[email protected]> | 2024-01-23 12:09:57 -0800 |
---|---|---|
committer | GitHub <[email protected]> | 2024-01-23 20:09:57 +0000 |
commit | c84237f9531aed3b204d3fdacc2dd9d2bd4c7d81 (patch) | |
tree | 924ec7260333bb75d8780e76325d5dd0303e0729 | |
parent | 27c1dd8634d34bfe3592151d66b410f28ca749ce (diff) |
Rewrite Array#each in Ruby using Primitive (#9533)
-rw-r--r-- | array.c | 59 | ||||
-rw-r--r-- | array.rb | 55 | ||||
-rw-r--r-- | benchmark/loop_each.yml | 4 | ||||
-rw-r--r-- | builtin.h | 2 | ||||
-rw-r--r-- | gems/bundled_gems | 2 | ||||
-rw-r--r-- | test/ruby/test_process.rb | 1 | ||||
-rw-r--r-- | test/ruby/test_settracefunc.rb | 6 | ||||
-rw-r--r-- | tool/mk_builtin_loader.rb | 3 |
8 files changed, 82 insertions, 50 deletions
@@ -28,6 +28,7 @@ #include "ruby/encoding.h" #include "ruby/st.h" #include "ruby/util.h" +#include "vm_core.h" #include "builtin.h" #if !ARRAY_DEBUG @@ -2484,50 +2485,19 @@ ary_enum_length(VALUE ary, VALUE args, VALUE eobj) return rb_ary_length(ary); } -/* - * call-seq: - * array.each {|element| ... } -> self - * array.each -> Enumerator - * - * Iterates over array elements. - * - * When a block given, passes each successive array element to the block; - * returns +self+: - * - * a = [:foo, 'bar', 2] - * a.each {|element| puts "#{element.class} #{element}" } - * - * Output: - * - * Symbol foo - * String bar - * Integer 2 - * - * Allows the array to be modified during iteration: - * - * a = [:foo, 'bar', 2] - * a.each {|element| puts element; a.clear if element.to_s.start_with?('b') } - * - * Output: - * - * foo - * bar - * - * When no block given, returns a new Enumerator: - * a = [:foo, 'bar', 2] - * - * e = a.each - * e # => #<Enumerator: [:foo, "bar", 2]:each> - * a1 = e.each {|element| puts "#{element.class} #{element}" } - * - * Output: - * - * Symbol foo - * String bar - * Integer 2 - * - * Related: #each_index, #reverse_each. - */ +// Primitive to avoid a race condition in Array#each. +// Return `true` and write `value` and `index` if the element exists. +static VALUE +ary_fetch_next(VALUE self, VALUE *index, VALUE *value) +{ + long i = NUM2LONG(*index); + if (i >= RARRAY_LEN(self)) { + return Qfalse; + } + *value = RARRAY_AREF(self, i); + *index = LONG2NUM(i + 1); + return Qtrue; +} VALUE rb_ary_each(VALUE ary) @@ -8644,7 +8614,6 @@ Init_Array(void) rb_define_method(rb_cArray, "unshift", rb_ary_unshift_m, -1); rb_define_alias(rb_cArray, "prepend", "unshift"); rb_define_method(rb_cArray, "insert", rb_ary_insert, -1); - rb_define_method(rb_cArray, "each", rb_ary_each, 0); rb_define_method(rb_cArray, "each_index", rb_ary_each_index, 0); rb_define_method(rb_cArray, "reverse_each", rb_ary_reverse_each, 0); rb_define_method(rb_cArray, "length", rb_ary_length, 0); @@ -1,5 +1,60 @@ class Array # call-seq: + # array.each {|element| ... } -> self + # array.each -> Enumerator + # + # Iterates over array elements. + # + # When a block given, passes each successive array element to the block; + # returns +self+: + # + # a = [:foo, 'bar', 2] + # a.each {|element| puts "#{element.class} #{element}" } + # + # Output: + # + # Symbol foo + # String bar + # Integer 2 + # + # Allows the array to be modified during iteration: + # + # a = [:foo, 'bar', 2] + # a.each {|element| puts element; a.clear if element.to_s.start_with?('b') } + # + # Output: + # + # foo + # bar + # + # When no block given, returns a new Enumerator: + # a = [:foo, 'bar', 2] + # + # e = a.each + # e # => #<Enumerator: [:foo, "bar", 2]:each> + # a1 = e.each {|element| puts "#{element.class} #{element}" } + # + # Output: + # + # Symbol foo + # String bar + # Integer 2 + # + # Related: #each_index, #reverse_each. + def each + Primitive.attr! :inline_block + unless defined?(yield) + return to_enum(:each) { self.length } + end + _i = 0 + value = nil + while Primitive.cexpr!(%q{ ary_fetch_next(self, LOCAL_PTR(_i), LOCAL_PTR(value)) }) + yield value + end + self + end + + # call-seq: # array.shuffle!(random: Random) -> array # # Shuffles the elements of +self+ in place. diff --git a/benchmark/loop_each.yml b/benchmark/loop_each.yml new file mode 100644 index 0000000000..1c757185a8 --- /dev/null +++ b/benchmark/loop_each.yml @@ -0,0 +1,4 @@ +prelude: | + arr = [nil] * 30_000_000 +benchmark: + loop_each: arr.each{|e|} @@ -106,6 +106,8 @@ rb_vm_lvar(rb_execution_context_t *ec, int index) #endif } +#define LOCAL_PTR(local) local ## __ptr + // dump/load struct builtin_binary { diff --git a/gems/bundled_gems b/gems/bundled_gems index 4d9e6cbf94..8f2ff3f624 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -19,7 +19,7 @@ matrix 0.4.2 https://github.com/ruby/matrix prime 0.1.2 https://github.com/ruby/prime rbs 3.4.2 https://github.com/ruby/rbs typeprof 0.21.9 https://github.com/ruby/typeprof -debug 1.9.1 https://github.com/ruby/debug +debug 1.9.1 https://github.com/ruby/debug 19b91b14ce814a0eb615abb8d2bef0594c61c5c8 racc 1.7.3 https://github.com/ruby/racc mutex_m 0.2.0 https://github.com/ruby/mutex_m getoptlong 0.2.1 https://github.com/ruby/getoptlong diff --git a/test/ruby/test_process.rb b/test/ruby/test_process.rb index 3d20d6eff5..8fb3a9df0c 100644 --- a/test/ruby/test_process.rb +++ b/test/ruby/test_process.rb @@ -2841,6 +2841,7 @@ EOS def test_low_memory_startup omit "JIT enabled" if %w[YJIT RJIT].any? {|n| RubyVM.const_defined?(n) and RubyVM.const_get(n).enabled?} + omit "flaky on Travis arm32" if /armv8l-linux-eabihf/ =~ RUBY_PLATFORM as = 1<<25 _, _, status = EnvUtil.invoke_ruby(%W'-W0', "", true, :merge_to_stdout, rlimit_as: as) omit sprintf("Crashed with AS: %#x: %s", as, status) if status.signaled? diff --git a/test/ruby/test_settracefunc.rb b/test/ruby/test_settracefunc.rb index dbaf0aaf09..6c17f279d4 100644 --- a/test/ruby/test_settracefunc.rb +++ b/test/ruby/test_settracefunc.rb @@ -504,7 +504,7 @@ class TestSetTraceFunc < Test::Unit::TestCase 1: trace = TracePoint.trace(*trace_events){|tp| next if !target_thread? 2: events << [tp.event, tp.lineno, tp.path, _defined_class.(tp), tp.method_id, tp.self, tp.binding&.eval("_local_var"), _get_data.(tp)] if tp.path == 'xyzzy' 3: } - 4: [1].each{|;_local_var| _local_var = :inner + 4: [1].reverse_each{|;_local_var| _local_var = :inner 5: tap{} 6: } 7: class XYZZY @@ -531,10 +531,10 @@ class TestSetTraceFunc < Test::Unit::TestCase answer_events = [ # [:line, 4, 'xyzzy', self.class, method, self, :outer, :nothing], - [:c_call, 4, 'xyzzy', Array, :each, [1], nil, :nothing], + [:c_call, 4, 'xyzzy', Array, :reverse_each, [1], nil, :nothing], [:line, 4, 'xyzzy', self.class, method, self, nil, :nothing], [:line, 5, 'xyzzy', self.class, method, self, :inner, :nothing], - [:c_return, 4, "xyzzy", Array, :each, [1], nil, [1]], + [:c_return, 4, "xyzzy", Array, :reverse_each, [1], nil, [1]], [:line, 7, 'xyzzy', self.class, method, self, :outer, :nothing], [:c_call, 7, "xyzzy", Module, :const_added, TestSetTraceFunc, nil, :nothing], [:c_return, 7, "xyzzy", Module, :const_added, TestSetTraceFunc, nil, nil], diff --git a/tool/mk_builtin_loader.rb b/tool/mk_builtin_loader.rb index 989c8c5957..07c8291249 100644 --- a/tool/mk_builtin_loader.rb +++ b/tool/mk_builtin_loader.rb @@ -274,7 +274,8 @@ def generate_cexpr(ofile, lineno, line_file, body_lineno, text, locals, func_nam locals&.reverse_each&.with_index{|param, i| next unless Symbol === param next unless local_candidates.include?(param.to_s) - f.puts "MAYBE_UNUSED(const VALUE) #{param} = rb_vm_lvar(ec, #{-3 - i});" + f.puts "VALUE *const #{param}__ptr = (VALUE *)&ec->cfp->ep[#{-3 - i}];" + f.puts "MAYBE_UNUSED(const VALUE) #{param} = *#{param}__ptr;" lineno += 1 } f.puts "#line #{body_lineno} \"#{line_file}\"" |