Rewrite Array#each in Ruby using Primitive (#9533)
This commit is contained in:
parent
27c1dd8634
commit
c84237f953
59
array.c
59
array.c
@ -28,6 +28,7 @@
|
|||||||
#include "ruby/encoding.h"
|
#include "ruby/encoding.h"
|
||||||
#include "ruby/st.h"
|
#include "ruby/st.h"
|
||||||
#include "ruby/util.h"
|
#include "ruby/util.h"
|
||||||
|
#include "vm_core.h"
|
||||||
#include "builtin.h"
|
#include "builtin.h"
|
||||||
|
|
||||||
#if !ARRAY_DEBUG
|
#if !ARRAY_DEBUG
|
||||||
@ -2484,50 +2485,19 @@ ary_enum_length(VALUE ary, VALUE args, VALUE eobj)
|
|||||||
return rb_ary_length(ary);
|
return rb_ary_length(ary);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Primitive to avoid a race condition in Array#each.
|
||||||
* call-seq:
|
// Return `true` and write `value` and `index` if the element exists.
|
||||||
* array.each {|element| ... } -> self
|
static VALUE
|
||||||
* array.each -> Enumerator
|
ary_fetch_next(VALUE self, VALUE *index, VALUE *value)
|
||||||
*
|
{
|
||||||
* Iterates over array elements.
|
long i = NUM2LONG(*index);
|
||||||
*
|
if (i >= RARRAY_LEN(self)) {
|
||||||
* When a block given, passes each successive array element to the block;
|
return Qfalse;
|
||||||
* returns +self+:
|
}
|
||||||
*
|
*value = RARRAY_AREF(self, i);
|
||||||
* a = [:foo, 'bar', 2]
|
*index = LONG2NUM(i + 1);
|
||||||
* a.each {|element| puts "#{element.class} #{element}" }
|
return Qtrue;
|
||||||
*
|
}
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
VALUE
|
VALUE
|
||||||
rb_ary_each(VALUE ary)
|
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_method(rb_cArray, "unshift", rb_ary_unshift_m, -1);
|
||||||
rb_define_alias(rb_cArray, "prepend", "unshift");
|
rb_define_alias(rb_cArray, "prepend", "unshift");
|
||||||
rb_define_method(rb_cArray, "insert", rb_ary_insert, -1);
|
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, "each_index", rb_ary_each_index, 0);
|
||||||
rb_define_method(rb_cArray, "reverse_each", rb_ary_reverse_each, 0);
|
rb_define_method(rb_cArray, "reverse_each", rb_ary_reverse_each, 0);
|
||||||
rb_define_method(rb_cArray, "length", rb_ary_length, 0);
|
rb_define_method(rb_cArray, "length", rb_ary_length, 0);
|
||||||
|
55
array.rb
55
array.rb
@ -1,4 +1,59 @@
|
|||||||
class Array
|
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:
|
# call-seq:
|
||||||
# array.shuffle!(random: Random) -> array
|
# array.shuffle!(random: Random) -> array
|
||||||
#
|
#
|
||||||
|
4
benchmark/loop_each.yml
Normal file
4
benchmark/loop_each.yml
Normal file
@ -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
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define LOCAL_PTR(local) local ## __ptr
|
||||||
|
|
||||||
// dump/load
|
// dump/load
|
||||||
|
|
||||||
struct builtin_binary {
|
struct builtin_binary {
|
||||||
|
@ -19,7 +19,7 @@ matrix 0.4.2 https://github.com/ruby/matrix
|
|||||||
prime 0.1.2 https://github.com/ruby/prime
|
prime 0.1.2 https://github.com/ruby/prime
|
||||||
rbs 3.4.2 https://github.com/ruby/rbs
|
rbs 3.4.2 https://github.com/ruby/rbs
|
||||||
typeprof 0.21.9 https://github.com/ruby/typeprof
|
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
|
racc 1.7.3 https://github.com/ruby/racc
|
||||||
mutex_m 0.2.0 https://github.com/ruby/mutex_m
|
mutex_m 0.2.0 https://github.com/ruby/mutex_m
|
||||||
getoptlong 0.2.1 https://github.com/ruby/getoptlong
|
getoptlong 0.2.1 https://github.com/ruby/getoptlong
|
||||||
|
@ -2841,6 +2841,7 @@ EOS
|
|||||||
|
|
||||||
def test_low_memory_startup
|
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 "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
|
as = 1<<25
|
||||||
_, _, status = EnvUtil.invoke_ruby(%W'-W0', "", true, :merge_to_stdout, rlimit_as: as)
|
_, _, 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?
|
omit sprintf("Crashed with AS: %#x: %s", as, status) if status.signaled?
|
||||||
|
@ -504,7 +504,7 @@ class TestSetTraceFunc < Test::Unit::TestCase
|
|||||||
1: trace = TracePoint.trace(*trace_events){|tp| next if !target_thread?
|
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'
|
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: }
|
3: }
|
||||||
4: [1].each{|;_local_var| _local_var = :inner
|
4: [1].reverse_each{|;_local_var| _local_var = :inner
|
||||||
5: tap{}
|
5: tap{}
|
||||||
6: }
|
6: }
|
||||||
7: class XYZZY
|
7: class XYZZY
|
||||||
@ -531,10 +531,10 @@ class TestSetTraceFunc < Test::Unit::TestCase
|
|||||||
answer_events = [
|
answer_events = [
|
||||||
#
|
#
|
||||||
[:line, 4, 'xyzzy', self.class, method, self, :outer, :nothing],
|
[: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, 4, 'xyzzy', self.class, method, self, nil, :nothing],
|
||||||
[:line, 5, 'xyzzy', self.class, method, self, :inner, :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],
|
[:line, 7, 'xyzzy', self.class, method, self, :outer, :nothing],
|
||||||
[:c_call, 7, "xyzzy", Module, :const_added, TestSetTraceFunc, nil, :nothing],
|
[:c_call, 7, "xyzzy", Module, :const_added, TestSetTraceFunc, nil, :nothing],
|
||||||
[:c_return, 7, "xyzzy", Module, :const_added, TestSetTraceFunc, nil, nil],
|
[:c_return, 7, "xyzzy", Module, :const_added, TestSetTraceFunc, nil, nil],
|
||||||
|
@ -274,7 +274,8 @@ def generate_cexpr(ofile, lineno, line_file, body_lineno, text, locals, func_nam
|
|||||||
locals&.reverse_each&.with_index{|param, i|
|
locals&.reverse_each&.with_index{|param, i|
|
||||||
next unless Symbol === param
|
next unless Symbol === param
|
||||||
next unless local_candidates.include?(param.to_s)
|
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
|
lineno += 1
|
||||||
}
|
}
|
||||||
f.puts "#line #{body_lineno} \"#{line_file}\""
|
f.puts "#line #{body_lineno} \"#{line_file}\""
|
||||||
|
Loading…
x
Reference in New Issue
Block a user