Repository navigation
Add fixnums array stride knowledge for frozen arrays - #1081
Draft
AlexaCampusano wants to merge 1 commit into
Draft
AlexaCampusano wants to merge 1 commit into
AlexaCampusano wants to merge 1 commit into
Conversation
AlexaCampusano
force-pushed
the
nc/add-array-element-stride
branch
from
October 8, 2026 21:00
e73608f to
99fce74
Compare
tenderlove
reviewed
Oct 9, 2026
| * also safe where RARRAY_CONST_PTR is not (opt_aref, GC callbacks). | ||
| * | ||
| * Not PURE: it is still a pure function of (ary, i) in the narrow case, but | ||
| * the wide case reads through RARRAY_CONST_PTR, which is not pure either. */ |
There was a problem hiding this comment.
I'm not sure if we need comments about purity. But I think this function doesn't have any side-effects (it is pure). It's true that RARRAY_CONST_PTR could widen the array, but it seems like the early return would prevent that from ever happening.
| * this point in parallel through RARRAY_CONST_PTR. Widening swaps and | ||
| * frees the buffer, which must happen exactly once. Take the VM lock and | ||
| * re-check: whoever loses the race finds the array already widened. */ | ||
| RB_VM_LOCKING() { |
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
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Related read Storage Strategies for Collectionsin Dynamically Typed Languages
What are we trying to do?
This PR is a proposal to add fixnums stride information into the array object, in order for heap arrays to have more information about the data it's storing and we can optimize it.
In this PR I decided to scope it to only frozen heap arrays that contain fixnums. The reason behind it is, this is a good first step, and we can tackle some of the problem in a safe way.
How does it work?
1. Lifecycle of one array
flowchart LR A["Array created<br/>8 B per element"] --> T{"trigger?"} T -- "Array#freeze" --> N T -- "[...].freeze literal<br/>(compile time)" --> N T -- "neither" --> A N{"ary_try_narrow<br/>heap-backed?<br/>all Fixnum?<br/>fits 32 bit?"} N -- no --> A N -- yes --> P["packed<br/>1 / 2 / 4 B per element"] P -- "element reads<br/>[] each sum == include?" --> P P -- "write · raw buffer ·<br/>dup / slice / C ext" --> W["rb_ary_widen<br/>back to 8 B, for good"]2. e.g.: TABLE = [0, 1, …, 199].freeze (200 elements)
flowchart LR subgraph before["before"] S1["slot 40 B<br/>stride VALUE"] --> B1["malloc 1,600 B<br/>200 × VALUE"] end subgraph after["after narrowing"] S2["slot 40 B<br/>stride W8 · unsigned"] --> B2["malloc 200 B<br/>200 × uint8_t"] end before -- "opt_ary_freeze →<br/>rb_ary_narrow" --> after3. Who touches a narrow array, and how
Readers, stay narrow.
RARRAY_AREF(internal) decodes elements in place, so[],each,map,sum,include?,==,index,count,join,packandMarshal.dumpnever allocate a wide copy and never change the representation. InternallyRARRAY_AREFgoes throughRARRAY_CONST_PTR, which hands out aVALUE *and would therefore widen; we gave it a stride-aware decode path instead.GC. Mark: nothing to trace (no object references in a packed buffer). Compaction: nothing to re-point, and
rb_ary_embeddable_prefuses narrow arrays so they're never re-embedded. Free:ARY_HEAP_SIZEis stride awareRactor.
make_shareableskips the element loop (every element is an immediate).rb_ary_widentakes the VM lock and re-checks, so two Ractors widening the same shareable array do it exactly once.Widen, permanently Raw-buffer readers widen. For C extensions that's unavoidable,
RARRAY_PTRpromises aVALUE *the caller may hold or write through. For Ruby's own dup/slices it's a deliberate trade because it creates shared copies.Why am I not tackling embedded arrays?
In some of the stats we collected in https://shopify.slack.com/archives/C0BSQ56F7HD/p1789753762082559, majority of array literals containing fixnums in Core/SFR are very tiny in size (< 3 elements hold, at least for SFR ~93% of all fixnums arrays, meaning they are embedded). Embedded arrays are allocated in Ruby heap slots they can fit, while they fit in there they don't malloc, and we cannot change the size of the slots an object is already in outside of compaction, so basically we can't really do much with them by narrowing VALUE to fit the Fixnum with a smaller stride.
I do have an idea where we COULD, and I have not tested this or how much work would it be, but see if we can reduce the amount of Arrays that end up being malloc'ed because the slots are using VALUE to measure capacity. In theory we could potentially fit bigger arrays of smaller stride in one slot, and prevent that array from allocating C heap memory.
Why only frozen?
I chose to do frozen arrays because it's one layer of protection against widening-churn. Some other reasons I had in mind:
RARRAY_CONST_PTRthat expects a collection of VALUE, that can be used by native extensions.It also keeps the blast radius of changes minimal, and we can iterate on it slowly to teach all the different parts of Ruby how to be stride aware.
Some numbers!!!
Reading narrowed arrays:
ruby-bench