diff options
author | Aaron Patterson <[email protected]> | 2024-07-03 09:45:29 -0700 |
---|---|---|
committer | Aaron Patterson <[email protected]> | 2024-07-03 11:32:40 -0700 |
commit | 4c9134d2b2ab3830dd4cca3b9595c97fc7361392 (patch) | |
tree | 80b49421107ee8df9031056f61e9e37c511168d0 | |
parent | 7fe5f0a1d0e628e9e330169a5c2dedae0d40dedd (diff) |
Move Array#select to Ruby
This speeds up the mail benchmark by about 7% on the interpreter:
```
before: ruby 3.4.0dev (2024-07-03T17:01:41Z master f4b313f733) [arm64-darwin23]
after: ruby 3.4.0dev (2024-07-03T17:45:50Z ruby-select de282cacd5) [arm64-darwin23]
----- ----------- ---------- ---------- ---------- ------------- ------------
bench before (ms) stddev (%) after (ms) stddev (%) after 1st itr before/after
mail 72.9 0.8 68.2 1.0 1.02 1.07
----- ----------- ---------- ---------- ---------- ------------- ------------
Legend:
- after 1st itr: ratio of before/after time for the first benchmarking iteration.
- before/after: ratio of before/after time. Higher is better for after. Above 1 represents a speedup.
```
YJIT is about 13% faster:
```
before: ruby 3.4.0dev (2024-07-03T17:01:41Z master f4b313f733) +YJIT [arm64-darwin23]
after: ruby 3.4.0dev (2024-07-03T17:45:50Z ruby-select de282cacd5) +YJIT [arm64-darwin23]
----- ----------- ---------- ---------- ---------- ------------- ------------
bench before (ms) stddev (%) after (ms) stddev (%) after 1st itr before/after
mail 51.0 0.8 45.2 0.6 1.00 1.13
----- ----------- ---------- ---------- ---------- ------------- ------------
Legend:
- after 1st itr: ratio of before/after time for the first benchmarking iteration.
- before/after: ratio of before/after time. Higher is better for after. Above 1 represents a speedup.
```
-rw-r--r-- | array.c | 44 | ||||
-rw-r--r-- | array.rb | 35 |
2 files changed, 41 insertions, 38 deletions
@@ -3807,42 +3807,6 @@ rb_ary_values_at(int argc, VALUE *argv, VALUE ary) } -/* - * call-seq: - * array.select {|element| ... } -> new_array - * array.select -> new_enumerator - * - * Calls the block, if given, with each element of +self+; - * returns a new +Array+ containing those elements of +self+ - * for which the block returns a truthy value: - * - * a = [:foo, 'bar', 2, :bam] - * a1 = a.select {|element| element.to_s.start_with?('b') } - * a1 # => ["bar", :bam] - * - * Returns a new Enumerator if no block given: - * - * a = [:foo, 'bar', 2, :bam] - * a.select # => #<Enumerator: [:foo, "bar", 2, :bam]:select> - * - */ - -static VALUE -rb_ary_select(VALUE ary) -{ - VALUE result; - long i; - - RETURN_SIZED_ENUMERATOR(ary, 0, 0, ary_enum_length); - result = rb_ary_new2(RARRAY_LEN(ary)); - for (i = 0; i < RARRAY_LEN(ary); i++) { - if (RTEST(rb_yield(RARRAY_AREF(ary, i)))) { - rb_ary_push(result, rb_ary_elt(ary, i)); - } - } - return result; -} - struct select_bang_arg { VALUE ary; long len[2]; @@ -6697,6 +6661,12 @@ ary_sample(rb_execution_context_t *ec, VALUE ary, VALUE randgen, VALUE nv, VALUE } static VALUE +ary_sized_alloc(rb_execution_context_t *ec, VALUE self) +{ + return rb_ary_new2(RARRAY_LEN(self)); +} + +static VALUE ary_sample0(rb_execution_context_t *ec, VALUE ary) { return ary_sample(ec, ary, rb_cRandom, Qfalse, Qfalse); @@ -8702,9 +8672,7 @@ Init_Array(void) rb_define_method(rb_cArray, "collect!", rb_ary_collect_bang, 0); rb_define_method(rb_cArray, "map", rb_ary_collect, 0); rb_define_method(rb_cArray, "map!", rb_ary_collect_bang, 0); - rb_define_method(rb_cArray, "select", rb_ary_select, 0); rb_define_method(rb_cArray, "select!", rb_ary_select_bang, 0); - rb_define_method(rb_cArray, "filter", rb_ary_select, 0); rb_define_method(rb_cArray, "filter!", rb_ary_select_bang, 0); rb_define_method(rb_cArray, "keep_if", rb_ary_keep_if, 0); rb_define_method(rb_cArray, "values_at", rb_ary_values_at, -1); @@ -57,6 +57,41 @@ class Array end # call-seq: + # array.select {|element| ... } -> new_array + # array.select -> new_enumerator + # + # Calls the block, if given, with each element of +self+; + # returns a new +Array+ containing those elements of +self+ + # for which the block returns a truthy value: + # + # a = [:foo, 'bar', 2, :bam] + # a1 = a.select {|element| element.to_s.start_with?('b') } + # a1 # => ["bar", :bam] + # + # Returns a new Enumerator if no block given: + # + # a = [:foo, 'bar', 2, :bam] + # a.select # => #<Enumerator: [:foo, "bar", 2, :bam]:select> + def select + Primitive.attr! :inline_block + Primitive.attr! :use_block + + unless defined?(yield) + return Primitive.cexpr! 'SIZED_ENUMERATOR(self, 0, 0, ary_enum_length)' + end + + _i = 0 + value = nil + result = Primitive.ary_sized_alloc + while Primitive.cexpr!(%q{ ary_fetch_next(self, LOCAL_PTR(_i), LOCAL_PTR(value)) }) + result << value if yield value + end + result + end + + alias filter select + + # call-seq: # array.shuffle!(random: Random) -> array # # Shuffles the elements of +self+ in place. |