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/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);
|
||||
|
55
array.rb
55
array.rb
@ -1,4 +1,59 @@
|
||||
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
|
||||
#
|
||||
|
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
|
||||
}
|
||||
|
||||
#define LOCAL_PTR(local) local ## __ptr
|
||||
|
||||
// dump/load
|
||||
|
||||
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
|
||||
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
|
||||
|
@ -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?
|
||||
|
@ -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],
|
||||
|
@ -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}\""
|
||||
|
Loading…
x
Reference in New Issue
Block a user