8000 Rewrite Array#each in Ruby using Primitive · ruby/ruby@3aaa6de · GitHub
[go: up one dir, main page]

Skip to content

Commit 3aaa6de

Browse files
committed
Rewrite Array#each in Ruby using Primitive
1 parent 99d6e2f commit 3aaa6de

File tree

8 files changed

+81
-51
lines changed

8 files changed

+81
-51
lines changed

array.c

Lines changed: 14 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include "ruby/encoding.h"
2929
#include "ruby/st.h"
3030
#include "ruby/util.h"
31+
#include "vm_core.h"
3132
#include "builtin.h"
3233

3334
#if !ARRAY_DEBUG
@@ -2484,50 +2485,19 @@ ary_enum_length(VALUE ary, VALUE args, VALUE eobj)
24842485
return rb_ary_length(ary);
24852486
}
24862487

2487-
/*
2488-
* call-seq:
2489-
* array.each {|element| ... } -> self
2490-
* array.each -> Enumerator
2491-
*
2492-
* Iterates over array elements.
2493-
*
2494-
* When a block given, passes each successive array element to the block;
2495-
* returns +self+:
2496-
*
2497-
* a = [:foo, 'bar', 2]
2498-
* a.each {|element| puts "#{element.class} #{element}" }
2499-
*
2500-
* Output:
2501-
*
2502-
* Symbol foo
2503-
* String bar
2504-
* Integer 2
2505-
*
2506-
* Allows the array to be modified during iteration:
2507-
*
2508-
* a = [:foo, 'bar', 2]
2509-
* a.each {|element| puts element; a.clear if element.to_s.start_with?('b') }
2510-
*
2511-
* Output:
2512-
*
2513-
* foo
2514-
* bar
2515-
*
2516-
* When no block given, returns a new Enumerator:
2517-
* a = [:foo, 'bar', 2]
2518-
*
2519-
* e = a.each
2520-
* e # => #<Enumerator: [:foo, "bar", 2]:each>
2521-
* a1 = e.each {|element| puts "#{element.class} #{element}" }
2522-
*
2523-
* Output:
2524-
*
2525-
* Symbol foo
2526-
* String bar
2527-
* Integer 2
2528-
*
2529-
* Related: #each_index, #reverse_each.
2530-
*/
2488+
// Primitive to avoid a race condition in Array#each.
2489+
// Return `true` and write `value` and `index` if the element exists.
2490+
static VALUE
2491+
ary_fetch_next(VALUE self, VALUE *index, VALUE *value)
2492+
{
2493+
long i = NUM2LONG(*index);
2494+
if (i >= RARRAY_LEN(self)) {
2495+
return Qfalse;
2496+
}
2497+
*value = RARRAY_AREF(self, i);
2498+
*index = LONG2NUM(i + 1);
2499+
return Qtrue;
2500+
}
25312501

25322502
VALUE
25332503
rb_ary_each(VALUE ary)
@@ -8644,7 +8614,6 @@ Init_Array(void)
86448614
rb_define_method(rb_cArray, "unshift", rb_ary_unshift_m, -1);
86458615
rb_define_alias(rb_cArray, "prepend", "unshift");
86468616
rb_define_method(rb_cArray, "insert", rb_ary_insert, -1);
8647-
rb_define_method(rb_cArray, "each", rb_ary_each, 0);
86488617
rb_define_method(rb_cArray, "each_index", rb_ary_each_index, 0);
86498618
rb_define_method(rb_cArray, "reverse_each", rb_ary_reverse_each, 0);
86508619
rb_define_method(rb_cArray, "length", rb_ary_length, 0);

array.rb

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,58 @@
11
class Array
2+
# call-seq:
3+
# array.each {|element| ... } -> self
4+
# array.each -> Enumerator
5+
#
6+
# Iterates over array elements.
7+
#
8+
# When a block given, passes each successive array element to the block;
9+
# returns +self+:
10+
#
11+
# a = [:foo, 'bar', 2]
12+
# a.each {|element| puts "#{element.class} #{element}" }
13+
#
14+
# Output:
15+
#
16+
# Symbol foo
17+
# String bar
18+
# Integer 2
19+
#
20+
# Allows the array to be modified during iteration:
21+
#
22+
# a = [:foo, 'bar', 2]
23+
# a.each {|element| puts element; a.clear if element.to_s.start_with?('b') }
24+
#
25+
# Output:
26+
#
27+
# foo
28+
# bar
29+
#
30+
# When no block given, returns a new Enumerator:
31+
# a = [:foo, 'bar', 2]
32+
#
33+
# e = a.each
34+
# e # => #<Enumerator: [:foo, "bar", 2]:each>
35+
# a1 = e.each {|element| puts "#{element.class} #{element}" }
36+
#
37+
# Output:
38+
#
39+
# Symbol foo
40+
# String bar
41+
# Integer 2
42+
#
43+
# Related: #each_index, #reverse_each.
44+
def each
45+
unless defined?(yield)
46+
return to_enum(:each) { self.length }
47+
end
48+
_i = 0
49+
value = nil
50+
while Primitive.cexpr!(%q{ ary_fetch_next(self, LOCAL_PTR(_i), LOCAL_PTR(value)) })
51+
yield value
52+
end
53+
self
54+
end
55+
256
# call-seq:
357
# array.shuffle!(random: Random) -> array
458
#

benchmark/loop_each.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
prelude: |
2+
arr = [nil] * 30_000_000
3+
benchmark:
4+
loop_each: arr.each{|e|}

builtin.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ rb_vm_lvar(rb_execution_context_t *ec, int index)
106106
#endif
107107
}
108108

109+
#define LOCAL_PTR(local) local ## __ptr
110+
109111
// dump/load
110112

111113
struct builtin_binary {

gems/bundled_gems

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ matrix 0.4.2 https://github.com/ruby/matrix
1919
prime 0.1.2 https://github.com/ruby/prime
2020
rbs 3.4.2 https://github.com/ruby/rbs
2121
typeprof 0.21.9 https://github.com/ruby/typeprof
22-
debug 1.9.1 https://github.com/ruby/debug
22+
debug 1.9.1 https://github.com/ruby/debug 19b91b14ce814a0eb615abb8d2bef0594c61c5c8
2323
racc 1.7.3 https://github.com/ruby/racc
2424
mutex_m 0.2.0 https://github.com/ruby/mutex_m
2525
getoptlong 0.2.1 https://github.com/ruby/getoptlong

test/ruby/test_gc_compact.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,7 @@ def add_ivars
373373
374374
Fiber.new {
375375
$ary = OBJ_COUNT.times.map { Foo.new }
376-
$ary.each(&:add_ivars)
376+
$ary.reverse_each(&:add_ivars)
377377
378378
GC.start
379379
Foo.new.add_ivars

test/ruby/test_settracefunc.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -504,7 +504,7 @@ def trace_by_tracepoint *trace_events
504504
1: trace = TracePoint.trace(*trace_events){|tp| next if !target_thread?
505505
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'
506506
3: }
507-
4: [1].each{|;_local_var| _local_var = :inner
507+
4: [1].reverse_each{|;_local_var| _local_var = :inner
508508
5: tap{}
509509
6: }
510510
7: class XYZZY
@@ -531,10 +531,10 @@ def trace_by_tracepoint *trace_events
531531
answer_events = [
532532
#
533533
[:line, 4, 'xyzzy', self.class, method, self, :outer, :nothing],
534-
[:c_call, 4, 'xyzzy', Array, :each, [1], nil, :nothing],
534+
[:c_call, 4, 'xyzzy', Array, :reverse_each, [1], nil, :nothing],
535535
[:line, 4, 'xyzzy', self.class, method, self, nil, :nothing],
536536
[:line, 5, 'xyzzy', self.class, method, self, :inner, :nothing],
537-
[:c_return, 4, "xyzzy", Array, :each, [1], nil, [1]],
537+
[:c_return, 4, "xyzzy", Array, :reverse_each, [1], nil, [1]],
538538
[:line, 7, 'xyzzy', self.class, method, self, :outer, :nothing],
539539
[:c_call, 7, "xyzzy", Module, :const_added, TestSetTraceFunc, nil, :nothing],
540540
[:c_return, 7, "xyzzy", Module, :const_added, TestSetTraceFunc, nil, nil],

tool/mk_builtin_loader.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,8 @@ def generate_cexpr(ofile, lineno, line_file, body_lineno, text, locals, func_nam
274274
locals&.reverse_each&.with_index{|param, i|
275275
next unless Symbol === param
276276
next unless local_candidates.include?(param.to_s)
277-
f.puts "MAYBE_UNUSED(const VALUE) #{param} = rb_vm_lvar(ec, #{-3 - i});"
277+
f.puts "VALUE *const #{param}__ptr = (VALUE *)&ec->cfp->ep[#{-3 - i}];"
278+
f.puts "MAYBE_UNUSED(const VALUE) #{param} = *#{param}__ptr;"
278279
lineno += 1
279280
}
280281
f.puts "#line #{body_lineno} \"#{line_file}\""

0 commit comments

Comments
 (0)
0