FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

mruby-array-ext: walk an Array in C for `include?` and `member?` by takumin · Pull Request #7360 · mruby/mruby · GitHub

/ mruby Public

mruby-array-ext: walk an Array in C for include? and member? - #7360

Merged
matz merged 1 commit into
mruby:masterfrom
takumin:array-ext-include-in-c
Aug 26, 2026
Merged

mruby-array-ext: walk an Array in C for include? and member?#7360
matz merged 1 commit into
mruby:masterfrom
takumin:array-ext-include-in-c

Conversation

takumin commented Aug 25, 2026
edited by coderabbitai Bot
Loading

Copy link
Copy Markdown
Contributor

Summary

Enumerable#include? reaches an element through a call to each, a block call and a __svalue send, then compares it with ==. An Array can be walked in place, and the pair compared with mrb_equal(), as Array#index and #delete already compare one. This is the same move Array#max and #min made in 6722d35, applied to the other search Enumerable lends an Array.

Changes

File What
mrbgems/mruby-array-ext/src/array.c adds ary_include(), registered as include? and member?
mrbgems/mruby-array-ext/test/array.rb the walk, an element whose == answers false, and a comparison that empties the array

Where the walk goes

mrb_equal() takes two values for equal where they are the same object and asks == only after, which is the test OP_EQ makes for a == b before it dispatches anything. Enumerable#include? reaches == through OP_EQ too, so the answers are the ones the array gave before, arrived at without a block call per element.

There is no block form to leave behind, so the two names reach the C walk directly rather than through super, which is where Array#max and #min leave theirs.

The walk reads the length and the pointer afresh each turn, because == may run Ruby that grows or shrinks the array under it.

There is no GC arena restore in the loop, and none is needed: what a call leaves in the arena is its return value, and this walk returns at the first one that is true. The answers it walks past are false, which is immediate. Array#index searches without one for the same reason. Array#count is the walk that does need one, since it goes past a true answer, and it has one.

Behaviour

Nothing an array answers moves, apart from a NaN. a and b are two NaNs built at run time as z / z from z = [0.0][0], so that nothing folds them into a single literal. Bold marks a row that differs from CRuby.

expression master this PR CRuby
[a].include?(a) false true true
[a].include?(b) false true false
[a].index(b) 0 0 nil

A NaN is equal to no value, its own operand included, so == can never find one: Enumerable#include? found nothing, while #index looked for the object and found the NaN the array holds. The two give one answer now, and it is #index's. That answer is right about the NaN an array holds and wrong about a second one, because a boxing that keeps a Float in the value normalizes every NaN to one representation, which is what the last row shows #index saying on master and saying still. Making a NaN an object of its own is a change to what a Float is rather than to how an array searches, and it is not in this PR.

Speed

a = (1..200).to_a
i = 0
while i < 20000 do a.include?(200); i += 1 end

Best of 11 interleaved runs against master, with a master-vs-master control to read the noise by, and the instruction counts callgrind reads beside them:

benchmark master this PR control Ir
#include? over 200 elements, 20000 times 342 ms 105 ms (0.31x) +0.2% 6,524,727,384 -> 1,865,200,104

Size

bin/mruby .text for every build in build_config/ci/gcc-clang.rb:

build master this PR delta
ascii-ctype 1,293,718 1,293,862 +144
bintest 1,307,110 1,307,254 +144
byte-string 1,271,494 1,271,638 +144
cxx_abi 1,334,041 1,334,185 +144
full-debug (-O0) 1,913,478 1,913,718 +240

The whole of it is mrbgems/mruby-array-ext/src/array.o, which grows by exactly 144 for the one function; no other object moves.

Testing

rake test passes for the default build, for build_config/host-nofloat.rb, for every build in build_config/ci/gcc-clang.rb including the C++ ABI one, and for a MRB_NO_BOXING build of the full-core gembox, with no compiler warnings beyond the one -Wmaybe-uninitialized about eq in hash.c that master already emits in a MRB_NO_BOXING build. The last of those is there because the rows about a NaN in the table above are ones a boxing decides.

One thing this does not test, and could not before either: the ISO assertions for Enumerable#include? and #member? (15.3.2.2.10 and 15.3.2.2.15) in test/t/enumerable.rb are written with an Array receiver, so in a build carrying this gem they now reach the C walk rather than the Enumerable one. Enumerable#max and #min are in the same position after 6722d35. Giving those assertions a receiver that is not an Array is worth doing and is not in this PR.

Environment

Machine, toolchain and the compile lines these numbers were taken with
OS Ubuntu 24.04.4 LTS
Kernel Linux 7.0.0-30-generic x86_64
CPU AMD Ryzen 9 5950X (16 cores)
C compiler gcc (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0
binutils GNU ld (GNU Binutils) 2.47.20260726
valgrind valgrind-3.27.1 (callgrind)
CRuby 4.0.6 (2026-07-14 revision 03b6d3f889)

The wall clock and instruction counts were taken with the default build:

gcc -std=gnu99 -g -O3 -Wall -Wundef -Werror-implicit-function-declaration -Wwrite-strings -DHAVE_MRUBY_REGEXP_GEM -DMRB_USE_SET -DHAVE_MRUBY_IO_GEM -DMRB_USE_RATIONAL -DMRB_USE_COMPLEX -DMRB_USE_BIGINT -DMRB_USE_DEBUG_HOOK

The ## Size table was taken with build_config/ci/gcc-clang.rb:

bintest      gcc -std=gnu99 -g -O3 -Wall -Wundef -Werror-implicit-function-declaration -Wwrite-strings -DMRB_GC_FIXED_ARENA -DMRB_USE_BIGINT -DMRB_USE_COMPLEX -DHAVE_MRUBY_ENCODING_GEM -DMRB_UTF8_STRING -DHAVE_MRUBY_IO_GEM -DMRB_USE_RATIONAL -DHAVE_MRUBY_REGEXP_GEM -DMRB_USE_SET -DMRB_USE_TASK_SCHEDULER -DMRB_USE_DEBUG_HOOK
ascii-ctype  gcc -std=gnu99 -g -O3 -Wall -Wundef -Werror-implicit-function-declaration -Wwrite-strings -DMRB_USE_ASCII_CTYPE -DMRB_USE_BIGINT -DMRB_USE_COMPLEX -DHAVE_MRUBY_ENCODING_GEM -DMRB_UTF8_STRING -DHAVE_MRUBY_IO_GEM -DMRB_USE_RATIONAL -DHAVE_MRUBY_REGEXP_GEM -DMRB_USE_SET -DMRB_USE_TASK_SCHEDULER
byte-string  gcc -std=gnu99 -g -O3 -Wall -Wundef -Werror-implicit-function-declaration -Wwrite-strings -DMRB_USE_BIGINT -DMRB_USE_COMPLEX -DHAVE_MRUBY_IO_GEM -DMRB_USE_RATIONAL -DHAVE_MRUBY_REGEXP_GEM -DMRB_USE_SET -DMRB_USE_TASK_SCHEDULER
cxx_abi      gcc -g -O3 -Wall -Wundef -Wwrite-strings -x c++ -std=gnu++03 -DMRB_GC_FIXED_ARENA -DMRB_USE_CXX_EXCEPTION -DMRB_USE_CXX_ABI -DMRB_USE_BIGINT -DMRB_USE_COMPLEX -DHAVE_MRUBY_ENCODING_GEM -DMRB_UTF8_STRING -DHAVE_MRUBY_IO_GEM -DMRB_USE_RATIONAL -DHAVE_MRUBY_REGEXP_GEM -DMRB_USE_SET -DMRB_USE_TASK_SCHEDULER
full-debug   gcc -std=gnu99 -g -O3 -Wall -Wundef -Werror-implicit-function-declaration -Wwrite-strings -g3 -O0 -DMRB_GC_STRESS -DMRB_USE_DEBUG_HOOK -DMRB_DEBUG -DMRB_USE_BIGINT -DMRB_USE_COMPLEX -DHAVE_MRUBY_ENCODING_GEM -DMRB_UTF8_STRING -DHAVE_MRUBY_IO_GEM -DMRB_USE_RATIONAL -DHAVE_MRUBY_REGEXP_GEM -DMRB_USE_SET -DMRB_USE_TASK_SCHEDULER

cxx_abi compiles mruby as C++ with gcc -x c++ -std=gnu++03; g++ only links. full-debug is the one build at -O0, enable_debug putting -g3 -O0 after the toolchain default of -g -O3.

Summary by CodeRabbit

  • New Features

    • Added Array#include? and Array#member? for checking whether an array contains a matching element.
    • Matching follows Ruby equality semantics, including support for nil, false, and custom comparisons.
    • Searches remain safe when comparisons modify the array during traversal.
  • Tests

    • Added coverage for empty arrays, successful and unsuccessful matches, identity, equality behavior, and mutation scenarios.

takumin requested a review from matz as a code owner August 25, 2026 14:45

coderabbitai Bot commented Aug 25, 2026
edited
Loading

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info ⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b9c718d1-5d57-4e7f-88d9-2e099ee86ee9

📥 Commits

Reviewing files that changed from the base of the PR and between 1247f93 and 33f8f4c.

📒 Files selected for processing (1)
  • mrbgems/mruby-array-ext/src/array.c

Included review availability: Your plan provides up to 8 included reviews per hour; 2 remain after this review.


📝 Walkthrough

Walkthrough

Added C implementations for Array#include? and Array#member?. The methods use mrb_equal() during traversal and refresh array state after each comparison. Tests cover equality, identity, special values, matching, and mutation.

Changes

Array membership methods

Layer / File(s) Summary
Membership implementation and registration
mrbgems/mruby-array-ext/src/array.c
Added ary_include with mutation-aware traversal and registered it as Array#include? and Array#member?.
Membership behavior tests
mrbgems/mruby-array-ext/test/array.rb
Added tests for matching, equality, identity, nil, false, Array#index consistency, and receiver mutation during comparison.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to 33f8f

This PR adds a localized C-level fast path for Array# include? and member? without introducing an actionable merge-blocking risk; it is merge-ready after normal checks and review.

Suggested reviewers: matz, dearblue

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 6 functions across 2 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: implementing direct C traversal for Array#include? and Array#member? in mruby-array-ext.
✨ Finishing Touches 🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

`Enumerable#include?` reaches an element through a call to `each`, a block call
and a `__svalue` send, then compares it with `==`. An Array can be walked in
place, and the pair compared with `mrb_equal()`, as `Array#index` and `#delete`
already compare one; `mrb_equal()` is also the test `OP_EQ` makes before it
dispatches `==`, so the answers are the ones the array gave before.

```ruby
a = (1..200).to_a
i = 0
while i < 20000 do a.include?(200); i += 1 end
```

reads 6,524,727,384 instructions against master and 1,865,200,104 here.

There is no block form to leave behind, so the two names reach the C walk
directly rather than through `super`, which is where `Array#max` and `#min`
leave theirs.
takumin force-pushed the array-ext-include-in-c branch from 1247f93 to 33f8f4c Compare August 25, 2026 15:28
matz merged commit 5644a9d into mruby:master Aug 26, 2026
21 checks passed
takumin deleted the array-ext-include-in-c branch August 26, 2026 01:00
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants


Back | FazBrowse Home | New Git URL