Skip to content

Array#rfind#15189

Merged
kddnewton merged 1 commit intoruby:masterfrom
kddnewton:array-rfind
Dec 12, 2025
Merged

Array#rfind#15189
kddnewton merged 1 commit intoruby:masterfrom
kddnewton:array-rfind

Conversation

@kddnewton
Copy link
Copy Markdown
Contributor

Implement Array#rfind, which is the same as find except from the other side of the Array. Also implemented Array#find (as opposed to the generic one on Enumerable because it is significantly faster and to keep the implementations together.

[Feature #21678]

Comment thread array.c Outdated
* of the collection; returns the last element for which the block returns a
* truthy value:
*
* (0..9).find {|element| element > 2} # => 3
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The examples need to be updated

Suggested change
* (0..9).find {|element| element > 2} # => 3
* (0..9).rfind {|element| element < 5} # => 4

Comment thread array.c Outdated
Comment on lines +2143 to +2145
* (0..9).find(proc {false}) {|element| element > 12} # => false
* {foo: 0, bar: 1, baz: 2}.find {|key, value| key.start_with?('b') } # => [:bar, 1]
* {foo: 0, bar: 1, baz: 2}.find(proc {[]}) {|key, value| key.start_with?('c') } # => []
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be better to provide examples more specific to rfind, but here's a temporary fix:

Suggested change
* (0..9).find(proc {false}) {|element| element > 12} # => false
* {foo: 0, bar: 1, baz: 2}.find {|key, value| key.start_with?('b') } # => [:bar, 1]
* {foo: 0, bar: 1, baz: 2}.find(proc {[]}) {|key, value| key.start_with?('c') } # => []
* (0..9).rfind(proc {false}) {|element| element > 12} # => false
* {foo: 0, bar: 1, baz: 2}.rfind {|key, value| key.start_with?('b') } # => [:baz, 2]
* {foo: 0, bar: 1, baz: 2}.rfind(proc {[]}) {|key, value| key.start_with?('c') } # => []

Comment thread test/ruby/test_array.rb Outdated

def test_rfind
ary = [1, 2, 3, 1, 2]
assert_equal(2, ary.rfind {|x| x % 2 == 0 })
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see this test was ported from test_enum.rb, but shouldn't we add tests to verify behavior specific to rfind? This test doesn't distinguish which 2 is returned and doesn't express the difference from find.

Comment thread array.c Outdated
*
* Returns the first element for which the block returns a truthy value.
*
* With a block given, calls the block with successive elements of the collection;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpicking: in Enumerable, it seems the word "collection" refers to the objects yielded by each method. However, I feel this word slightly off in Array. In fact, the word "collection" hardly appears at all in array.c.

As defined below, "elements" seems like a better choice.

ruby/array.c

Lines 8264 to 8265 in bec6961

* An \Array object is an ordered, integer-indexed collection of objects,
* called _elements_;

Well, since it says "collection of objects", you could say it's not incorrect, though. Also, "successive elements of the elements" may be awkward. I cannot think of a better phrase. Just for your reference.

@luke-gruber
Copy link
Copy Markdown
Contributor

I don't think you can cache idx without a bounds check in rfind because the array could be modified by the block and resized, andRARRAY_AREF doesn't do bounds checking.

Implement Array#rfind, which is the same as find except from the
other side of the Array. Also implemented Array#find (as opposed to
the generic one on Enumerable because it is significantly faster
and to keep the implementations together.

[Feature #21678]
@kddnewton
Copy link
Copy Markdown
Contributor Author

Thanks @mame and @luke-gruber — I believe I've addressed both your comments.

Copy link
Copy Markdown
Member

@mame mame left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Matz said go ahead

@kddnewton kddnewton merged commit 6147b69 into ruby:master Dec 12, 2025
88 checks passed
@kddnewton kddnewton deleted the array-rfind branch December 12, 2025 18:35
@eregon
Copy link
Copy Markdown
Member

eregon commented Dec 15, 2025

@kddnewton Nice! Could you also add an entry for this in NEWS.md?

@eregon
Copy link
Copy Markdown
Member

eregon commented Dec 15, 2025

I pushed bbc10ed to make sure we don't forget, feel free to improve it/tweak it.

@kddnewton
Copy link
Copy Markdown
Contributor Author

Thanks that looks good.

headius added a commit to jruby/jruby that referenced this pull request Dec 31, 2025
swebb added a commit to swebb/ruby that referenced this pull request Jan 11, 2026
Inspired by https://bugs.ruby-lang.org/issues/20182 and ruby#9533.

This PR provides a performance boost to Array#find when run using JIT
compilation. This is achieved by implementing Array#find in Ruby, which
the JIT compiler can optimise.

[PR#15189](ruby#15189) added a C
implementation for Array#find instead of relying on Enumerable#find.
This PR extends this by adding a Ruby implementation.

I used the so_fasta benchmark to measure performance. No change in
interpreted performance before/after:

  $ benchmark-driver -e "~/.rubies/ruby-master/bin/ruby; ~/.rubies/ruby-array-find-native/bin/ruby" ../benchmark/so_fasta.rb
  Calculating -------------------------------------
                       ~/.rubies/ruby-master/bin/ruby   ~/.rubies/ruby-array-find-native/bin/ruby
              so_fasta                          0.393                                       0.393 i/s -       1.000 times in 2.543209s 2.545514s

  Comparison:
                           so_fasta
  ~/.rubies/ruby-master/bin/ruby:         0.4 i/s
  ~/.rubies/ruby-array-find-native/bin/ruby:         0.4 i/s - 1.00x  slower

With YJIT enabled the speed is almost twice as fast:

  $ benchmark-driver -e "~/.rubies/ruby-array-find-native/bin/ruby; ~/.rubies/ruby-array-find-native/bin/ruby --yjit" ../benchmark/so_fasta.rb
  Calculating -------------------------------------
                       ~/.rubies/ruby-array-find-native/bin/ruby   ~/.rubies/ruby-array-find-native/bin/ruby --yjit
              so_fasta                                     0.393                                              0.770 i/s -       1.000 times in 2.547550s 1.298371s

  Comparison:
                           so_fasta
  ~/.rubies/ruby-array-find-native/bin/ruby --yjit:         0.8 i/s
  ~/.rubies/ruby-array-find-native/bin/ruby:         0.4 i/s - 1.96x  slower
swebb added a commit to swebb/ruby that referenced this pull request Jan 11, 2026
Inspired by https://bugs.ruby-lang.org/issues/20182 and ruby#9533.

This PR provides a performance boost to Array#find when run using JIT
compilation. This is achieved by implementing Array#find in Ruby, which
the JIT compiler can optimise.

[PR#15189](ruby#15189) added a C
implementation for Array#find instead of relying on Enumerable#find.
This PR extends this by adding a Ruby implementation.

I used the so_fasta benchmark to measure performance. No change in
interpreted performance before/after:

  $ benchmark-driver -e "~/.rubies/ruby-master/bin/ruby; ~/.rubies/ruby-array-find-native/bin/ruby" ../benchmark/so_fasta.rb
  Calculating -------------------------------------
                       ~/.rubies/ruby-master/bin/ruby   ~/.rubies/ruby-array-find-native/bin/ruby
              so_fasta                          0.393                                       0.393 i/s -       1.000 times in 2.543209s 2.545514s

  Comparison:
                           so_fasta
  ~/.rubies/ruby-master/bin/ruby:         0.4 i/s
  ~/.rubies/ruby-array-find-native/bin/ruby:         0.4 i/s - 1.00x  slower

With YJIT enabled Array#find is almost twice as fast:

  $ benchmark-driver -e "~/.rubies/ruby-array-find-native/bin/ruby; ~/.rubies/ruby-array-find-native/bin/ruby --yjit" ../benchmark/so_fasta.rb
  Calculating -------------------------------------
                       ~/.rubies/ruby-array-find-native/bin/ruby   ~/.rubies/ruby-array-find-native/bin/ruby --yjit
              so_fasta                                     0.393                                              0.770 i/s -       1.000 times in 2.547550s 1.298371s

  Comparison:
                           so_fasta
  ~/.rubies/ruby-array-find-native/bin/ruby --yjit:         0.8 i/s
  ~/.rubies/ruby-array-find-native/bin/ruby:         0.4 i/s - 1.96x  slower
nobu pushed a commit to swebb/ruby that referenced this pull request Feb 2, 2026
Inspired by https://bugs.ruby-lang.org/issues/20182 and ruby#9533.

This PR provides a performance boost to Array#find when run using JIT
compilation. This is achieved by implementing Array#find in Ruby, which
the JIT compiler can optimise.

[PR#15189](ruby#15189) added a C
implementation for Array#find instead of relying on Enumerable#find.
This PR extends this by adding a Ruby implementation.

I used the so_fasta benchmark to measure performance. No change in
interpreted performance before/after:

  $ benchmark-driver -e "~/.rubies/ruby-master/bin/ruby; ~/.rubies/ruby-array-find-native/bin/ruby" ../benchmark/so_fasta.rb
  Calculating -------------------------------------
                       ~/.rubies/ruby-master/bin/ruby   ~/.rubies/ruby-array-find-native/bin/ruby
              so_fasta                          0.393                                       0.393 i/s -       1.000 times in 2.543209s 2.545514s

  Comparison:
                           so_fasta
  ~/.rubies/ruby-master/bin/ruby:         0.4 i/s
  ~/.rubies/ruby-array-find-native/bin/ruby:         0.4 i/s - 1.00x  slower

With YJIT enabled Array#find is almost twice as fast:

  $ benchmark-driver -e "~/.rubies/ruby-array-find-native/bin/ruby; ~/.rubies/ruby-array-find-native/bin/ruby --yjit" ../benchmark/so_fasta.rb
  Calculating -------------------------------------
                       ~/.rubies/ruby-array-find-native/bin/ruby   ~/.rubies/ruby-array-find-native/bin/ruby --yjit
              so_fasta                                     0.393                                              0.770 i/s -       1.000 times in 2.547550s 1.298371s

  Comparison:
                           so_fasta
  ~/.rubies/ruby-array-find-native/bin/ruby --yjit:         0.8 i/s
  ~/.rubies/ruby-array-find-native/bin/ruby:         0.4 i/s - 1.96x  slower
nobu pushed a commit that referenced this pull request Feb 2, 2026
Inspired by https://bugs.ruby-lang.org/issues/20182 and #9533.

This PR provides a performance boost to Array#find when run using JIT
compilation. This is achieved by implementing Array#find in Ruby, which
the JIT compiler can optimise.

[PR#15189](#15189) added a C
implementation for Array#find instead of relying on Enumerable#find.
This PR extends this by adding a Ruby implementation.

I used the so_fasta benchmark to measure performance. No change in
interpreted performance before/after:

  $ benchmark-driver -e "~/.rubies/ruby-master/bin/ruby; ~/.rubies/ruby-array-find-native/bin/ruby" ../benchmark/so_fasta.rb
  Calculating -------------------------------------
                       ~/.rubies/ruby-master/bin/ruby   ~/.rubies/ruby-array-find-native/bin/ruby
              so_fasta                          0.393                                       0.393 i/s -       1.000 times in 2.543209s 2.545514s

  Comparison:
                           so_fasta
  ~/.rubies/ruby-master/bin/ruby:         0.4 i/s
  ~/.rubies/ruby-array-find-native/bin/ruby:         0.4 i/s - 1.00x  slower

With YJIT enabled Array#find is almost twice as fast:

  $ benchmark-driver -e "~/.rubies/ruby-array-find-native/bin/ruby; ~/.rubies/ruby-array-find-native/bin/ruby --yjit" ../benchmark/so_fasta.rb
  Calculating -------------------------------------
                       ~/.rubies/ruby-array-find-native/bin/ruby   ~/.rubies/ruby-array-find-native/bin/ruby --yjit
              so_fasta                                     0.393                                              0.770 i/s -       1.000 times in 2.547550s 1.298371s

  Comparison:
                           so_fasta
  ~/.rubies/ruby-array-find-native/bin/ruby --yjit:         0.8 i/s
  ~/.rubies/ruby-array-find-native/bin/ruby:         0.4 i/s - 1.96x  slower
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants