1381 Commits
Author SHA1 Message Date
BurdetteLamar 5080152122 [DOC] Tweaks for Array[] 2026-10-26 08:51:10 +09:00
Steven Webb 28dec96d04 Fix Array#find and Array#rfind arity check
Ruby 4 introduced native implementations of Array#find and Array#rfind
(Feature #21678). However, the new implementations changed how they
behave with multiple arguments:

ruby 3.4.8:

```
$ ruby -e "[].find(1, 2)"
-e:1:in 'Enumerable#find': wrong number of arguments (given 2, expected 0..1) (ArgumentError)
	from -e:1:in '<main>'
```

ruby 4:

```
$ ruby -e "[].find(1, 2)" # returns an instance of Enumerable and doesn't generate any output
```

This patch restores the original behaviour:

```
$ ruby -e "[].find(1, 2)"
-e:1:in 'Array#find': wrong number of arguments (given 2, expected 0..1) (ArgumentError)
	from -e:1:in '<main>'
```
2026-10-24 11:03:54 -04:00
Matt Valentine-House 0d83a93784 Unshare arrays only when they become shareable
obj_traverse_i called rb_ary_cancel_sharing on every array it visited,
before the traversal knew whether the object graph was shareable.

So Ractor.shareable? modified frozen arrays even when it returned false.

This commit moves rb_ary_cancel_sharing into mark_shareable, which
runs only after the traversal succeeds.

[Bug #22460]
2026-10-22 13:01:48 +01:00
Shourya Singh cfa50b0093 [DOC] Fix outdated results in examples
Some example results in the docs no longer match what Ruby prints:

* Array#flatten and Array#flatten!: Set#inspect now shows Set[6, 7]
* Module: Mod.constants also includes Math::DomainError
* Module#const_get and Module#const_set: Floats are shown with full
  precision
* Complex#imag: sin(PI/4) is 0.7071067811865475
* String#<<: missing # in #<Encoding:UTF-8>
* Enumerable#inject: typo #=>>
2026-10-21 17:52:28 +09:00
Hiroshi SHIBATAandClaude Opus 5.5 fdd39ad45a Check a random index against the limit before converting it
NUM2LEN raised its own RangeError for a value past rb_len_t, so a broken random generator was reported as a conversion failure instead of a number too big.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 16:07:39 +09:00
Hiroshi SHIBATAandClaude Opus 5.5 6ae6d91d6d Return the element for a Bignum index from Array#bsearch
Fixnum stays 31 bits on mswin, so bsearch_index gives a Bignum for an index of 2**30 or more, and bsearch returned that index instead of the element.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 16:07:39 +09:00
Nobuyoshi Nakada a430ab4e96 Refactor small Array#sample handling 2026-10-17 23:19:25 +09:00
Nobuyoshi Nakada 67cd6aa51a Dispatch Array#sample in array.rb 2026-10-16 17:56:35 +09:00
Erik Berlin 224726a9f5 Skip rb_eql() for identical elements in Array#eql?
Array#== walks both element buffers and only calls rb_equal() for a
pair whose VALUEs differ. Array#eql? called rb_ary_elt() twice and
rb_eql() once for every element, even though rb_eql() answers true
for identical VALUEs before doing anything else. For arrays of
Fixnums, Symbols, and nil, which are identical whenever they are
equal, that made eql? several times slower than ==.

recursive_eql() now walks the buffers the way recursive_equal() does.
A pair that is not identical still goes through rb_eql(), which can
run user code that resizes either array, so the length and both
buffers are re-read after every such call. Arrays whose lengths came
apart compare unequal, as they do under ==. Before, the shorter one
read as nil past its end, so the result depended on the remaining
elements of the longer one.

The new tests cover an element whose eql? empties both arrays,
shortens one of them, or grows both past their original capacity
mid-compare.
2026-10-16 10:09:20 +02:00
Nobuyoshi Nakada 88ffa9a300 [Bug #22449] Fix out-of-bounds in Array#sample when array modified
Get the length after the conversion using `to_int` is complete.
2026-10-16 16:24:37 +09:00
Hiroshi SHIBATAandClaude Opus 5.5 cb86c6d7ba Count the Array#product positions in rb_len_t
An int counter overflows once an array has more than INT_MAX elements. On LP64, product with a block then yielded the element at index 1 after the one at INT_MAX, instead of the one at 2**31.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 09:46:06 +09:00
Hiroshi SHIBATAandClaude Opus 5.5 29c5e0bc45 Check the Array#product size against the rb_len_t range
MUL_OVERFLOW_LONG_P falls back to LONG_MIN and LONG_MAX without the overflow builtin, so once rb_len_t widens on mswin it would reject a product of 2**31 or more that the result array can hold.

https://bugs.ruby-lang.org/issues/22400

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 16:31:01 +09:00
Hiroshi SHIBATAandClaude Opus 5.5 9511e08d9e Compute the size of Array#repeated_permutation with rb_int_pow
rb_int_positive_pow takes the base as long and the exponent as unsigned long, so read the count with NUM2LEN and raise the Integer instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 16:45:25 +09:00
Hiroshi SHIBATAandClaude Opus 5.5 ee3d6c987b Use rb_len_t for the lengths and indices left in long
These still held String and Array lengths and indices in long, or cast them to long. rb_len_t is still long everywhere, so the generated code does not change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 16:45:25 +09:00
Hiroshi SHIBATAandClaude Opus 5.5 a351359bea Pick Array#shuffle and #sample indices beyond unsigned long
rb_random_ulong_limited takes an unsigned long, which stays 32 bits on mswin, so once rb_len_t widens an Array of 2**32 elements or more would never yield its tail. A larger limit goes through rand with an Integer, as rb_random_ulong_limited already does for objects that are not Random.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 14:00:15 +09:00
Hiroshi SHIBATAandClaude Opus 5 62f0eec147 Use rb_len_t for lengths and indices in array.c
ARY_MAX_SIZE follows rb_len_t, so the index errors report the value with PRIdLEN. The repeated_permutation exponent stays `long`, because rb_int_positive_pow() takes an `unsigned long` and the value is the user's Integer rather than an array length.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-09 14:00:15 +09:00
Hiroshi SHIBATAandClaude Opus 5 cc3ba11012 Use rb_len_t in the C API for lengths and indices
Once rb_len_t widens this changes the `long *` output parameters of rb_range_beg_len(), rb_str_subpos(), rb_arithmetic_sequence_beg_len_step(), ruby_enc_find_basename() and ruby_enc_find_extname(), so extensions passing `long *` to them will need to be updated. The extension guides list these signatures verbatim and follow along.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-09 14:00:15 +09:00
Peter Zhu 87e9ab0410 Fix loss of initial value in Array#sum
When the not_exact case is hit, the initial value could be lost if it is
a float. For example, the following code incorrectly outputs 1.5 but should
output 11.5:

    puts [1.5].sum(10.0)
2026-10-07 08:11:13 +09:00
Peter Zhu 6b1add1e70 Fix Array#sum when init is float but array contains non-float
If the initial value is a float but an element of the array is not a float,
then it could be lost and an error won't be raised. For example, the following
code should raise a TypeError but currently does not:

    ["hello"].sum(10.0)
2026-10-06 22:57:24 +09:00
Peter Zhu 76aa225e9a Do not embed arrays that will overflow length bits
rb_ary_embeddable_p does not check that the capacity of the array is within
the number of bits used for embedded lengths, so it can cause overflows
on GC that support larger object sizes.
2026-10-04 19:47:58 +09:00
Max Bernstein d9f4075f7b ZJIT: Support out-of-bounds reads in Array#[] (#18989)
All of our current operations on Array guarantee in-bounds reads using guards. This works +/- fine for `expandarray` and `Array#[]` but at the end of the day, it is pretty common to read out-of-bounds because it's not an error! It just returns `nil`. So in cases where we can't guarantee the array is in-bounds, add a new HIR instruction `ArrayArefChecked`. It pairs with `AdjustBounds`.

This uses `jb` to do the `idx < 0 || idx >= len` check in one instruction.
2026-10-02 23:36:20 +00:00
Peter Zhu 5266c78875 Fix out-of-bounds crash in Array#repeated_permutation
If the array is modified in the call to to_int in Array#repeated_permutation,
it can trigger an out-of-bounds because the length is cached from before
the call to to_int.

The following script demonstrates teh crash:

    ary = (1..1000).to_a
    obj = Object.new
    obj.define_singleton_method(:to_int) do
      ary.replace(Array.new(5) { :x })
      2
    end
    ary.repeated_permutation(obj) { }
2026-09-29 10:34:52 +09:00
Nobuyoshi Nakada 31f1446a14 Extract ary_make_hidden_shared_copy
Make hidden shape copy of an array.
2026-09-29 01:02:08 +09:00
Nobuyoshi Nakada 679c0ca2dc Hidden objects class is cleared 2026-09-29 01:02:08 +09:00
Peter Zhu d939272e7d Fix out-of-bounds in Array#sample when array modified
The fix in commit 11f57c6 contained a bug where the calculated index should
be `len - i` and not `len - 1`. This script reproduces the crash:

    ary = (1..100).to_a
    gen = Object.new
    gen.define_singleton_method(:rand) { |lim| 8 }
    nv = Object.new
    nv.define_singleton_method(:to_int) { ary.replace(Array.new(10) { :x }); 10 }
    p ary.sample(nv, random: gen)
2026-09-28 18:36:41 +09:00
Hiroshi SHIBATAandClaude Opus 5.5 177587f5e1 Fix cycle(n).size for n beyond the Fixnum range
LONG2FIX does not check the range, so `[1].cycle(2**30).size` returned -1073741824 on mswin. 64-bit platforms hit the same with 2**62.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 16:20:31 +09:00
aadi da3e1fcb68 Fix init_is_float bug 2026-09-26 13:24:02 +09:00
Peter Zhu 1007084edc Fix out-of-bounds in Array#permutation when array modified
Calling to_int on the count may run Ruby code that modifies the array.
This can cause an out-of-bounds in Array#permutation. For example, the
following code crashes:

    ary = (1..1_000).to_a
    obj = Object.new
    obj.define_singleton_method(:to_int) do
      ary.replace(Array.new(5) { :x })
      2
    end
    ary.permutation(obj) { }
2026-09-22 12:10:17 +09:00
Matt Valentine-House 8befacf9e6 Guard against oob read in rb_ary_aref1
When the array is shrunk during rb_arithmetic_sequence_beg_len_step,
ary_subseq_len returns -1. Return an empty array instead of passing the
negative length to ary_make_partial or ary_make_partial_step.

[Bug #22325]
2026-09-21 10:14:27 +01:00
Peter Zhu 0cdc4562e5 Fix assertion error when calling Array#initialize
[Bug #22333]

If we call Array#initialize again, it may trigger an assertion error since
the length is not reset before the capacity is changed.
2026-09-18 22:08:01 +09:00
Peter Zhu 11f57c63e7 Fix out-of-bounds in Array#sample when array modified
The call to rand (through RAND_UPTO) or to_int (through NUM2LONG) can run
aribtrary Ruby code, which can modify the source array. The size of the
array may shrink, which could cause an out-of-bounds read on the array.
We do check again that all the random numbers are within the new array
size, but the check is incorrect because it checks against len but should
check against len-i (which is the random number requested). The following
script will sometimes crash and other times output corrupted results:

    ary = (1..1_000).to_a
    obj = Object.new
    obj.define_singleton_method(:to_int) do
      ary.replace((1..50).to_a)
      10
    end
    gen = Object.new
    def gen.rand(lim) = 49
    p ary.sample(obj, random: gen)

For example, it can output:

    [50, nil, nil, nil, nil, nil, nil, 367219703829, 40, 13294122074]
2026-09-17 16:26:31 +09:00
Peter Zhu 3f6143715a Fix buffer overflow in Array#values_at
Array#values_at can execute arbitrary code during execution which may
modify the source array. This can cause a buffer overflow if the new length
of the array has decreased. The following script crashes:

    a = (0..100_000).to_a
    obj = Object.new
    obj.define_singleton_method(:begin) do
      a.clear
      0
    end
    obj.define_singleton_method(:end) { 10_000 }
    obj.define_singleton_method(:exclude_end?) { false }
    puts a.values_at(obj).length
2026-09-17 09:01:17 +09:00
Peter Zhu 0c9f3f1e68 Fix use-after-free when clearing array during zip
[Bug #22319]

Array#zip triggers an use-after-free it does not account for when the call
to rb_check_array_type modifies the source array. The following script crashes:

    a = (1..100_000).to_a
    evil = Object.new; $a = a
    def evil.to_ary; $a.clear; [1,2,3]; end
    a.zip(evil)
2026-09-16 15:53:29 +09:00
Peter Zhu 5eeaedc3d3 Fix use-after-free when clearing array during flatten
[Bug #22318]

Array#flatten triggers an use-after-free it does not account for when the
call to rb_check_array_type modifies the source array. The following script
crashes:

    a = (1..100_000).to_a
    $a = a
    bad = Object.new
    def bad.to_ary; $a.clear; [1,2,3]; end
    a << bad
    a.flatten
2026-09-16 12:28:30 +09:00
Peter Zhu 73b7ffeb85 Add ary_to_set 2026-09-13 11:52:30 +09:00
Peter Zhu a4613a13fd Use Set instead of Hash for Array#uniq!
This commit changes Array#uniq! to use a temporary Set instead of Hash for
performance and memory savings. We can see in the following benchmark:

    a = (0...10_000).to_a + (0...10_000).to_a

    1_000.times { a.dup.uniq! }

Results:

    Benchmark 1: master
      Time (mean ± σ):     242.1 ms ±   3.9 ms    [User: 229.6 ms, System: 9.6 ms]
      Range (min … max):   235.7 ms … 248.3 ms    12 runs

    Benchmark 2: branch
      Time (mean ± σ):     197.4 ms ±   6.1 ms    [User: 184.2 ms, System: 10.4 ms]
      Range (min … max):   185.9 ms … 204.5 ms    14 runs

    Summary
      branch ran
        1.23 ± 0.04 times faster than master
2026-09-13 11:52:30 +09:00
Peter Zhu 890a879343 Use Set instead of Hash for Array#uniq
This commit changes Array#uniq to use a temporary Set instead of Hash for
performance and memory savings. We can see in the following benchmark:

    a = (0...10_000).to_a + (0...10_000).to_a

    1_000.times { a.uniq }

Results:

    Benchmark 1: master
      Time (mean ± σ):     194.6 ms ±   4.9 ms    [User: 183.2 ms, System: 8.7 ms]
      Range (min … max):   187.5 ms … 205.1 ms    15 runs

    Benchmark 2: branch
      Time (mean ± σ):     147.2 ms ±   4.0 ms    [User: 134.9 ms, System: 9.5 ms]
      Range (min … max):   139.2 ms … 153.4 ms    20 runs

    Summary
      branch ran
        1.32 ± 0.05 times faster than master
2026-09-13 11:52:30 +09:00
Peter Zhu cde29a360d Use Set instead of Hash for Array#intersect?
This commit changes Array#intersect? to use a temporary Set instead of Hash for
performance and memory savings. We can see in the following benchmark:

    a = (0...10_000).to_a
    b = (5_000...15_000).to_a

    1_000.times { a.intersect?(b) }

Results:

    Benchmark 1: master
      Time (mean ± σ):     126.8 ms ±   3.8 ms    [User: 115.5 ms, System: 8.9 ms]
      Range (min … max):   120.9 ms … 134.2 ms    22 runs

    Benchmark 2: branch
      Time (mean ± σ):      99.3 ms ±   3.5 ms    [User: 89.4 ms, System: 7.6 ms]
      Range (min … max):    94.1 ms … 109.7 ms    29 runs

    Summary
      branch ran
        1.28 ± 0.06 times faster than master
2026-09-13 11:52:30 +09:00
Peter Zhu 41cf062956 Use Set instead of Hash for Array#&
This commit changes Array#& to use a temporary Set instead of Hash for
performance and memory savings. We can see in the following benchmark:

    a = (0...10_000).to_a
    b = (5_000...15_000).to_a

    1_000.times { a & b }

Results:

    Benchmark 1: master
      Time (mean ± σ):     196.4 ms ±   9.1 ms    [User: 182.9 ms, System: 9.5 ms]
      Range (min … max):   186.3 ms … 214.7 ms    13 runs

    Benchmark 2: branch
      Time (mean ± σ):     163.4 ms ±   3.3 ms    [User: 152.7 ms, System: 7.8 ms]
      Range (min … max):   157.7 ms … 169.9 ms    17 runs

    Summary
      branch ran
        1.20 ± 0.06 times faster than master
2026-09-13 11:52:30 +09:00
Peter Zhu f79fdf303c Use Set instead of Hash for Array#difference
This commit changes Array#difference to use a temporary Set instead of Hash for
performance and memory savings. We can see in the following benchmark:

    a = (0...10_000).to_a
    b = (5_000...15_000).to_a

    1_000.times { a.difference(b) }

Results:

    Benchmark 1: master
      Time (mean ± σ):     192.2 ms ±   7.1 ms    [User: 179.7 ms, System: 9.0 ms]
      Range (min … max):   182.4 ms … 208.9 ms    15 runs

    Benchmark 2: branch
      Time (mean ± σ):     168.1 ms ±   4.7 ms    [User: 156.5 ms, System: 8.5 ms]
      Range (min … max):   160.2 ms … 174.6 ms    17 runs

    Summary
      branch ran
        1.14 ± 0.05 times faster than master
2026-09-13 11:52:30 +09:00
Peter Zhu f792a06811 Use Set instead of Hash for Array#-
This commit changes Array#- to use a temporary Set instead of Hash for
performance and memory savings. We can see in the following benchmark:

    a = (0...10_000).to_a
    b = (5_000...15_000).to_a

    1_000.times { a - b }

Results:

    Benchmark 1: master
      Time (mean ± σ):     200.3 ms ±   3.8 ms    [User: 188.0 ms, System: 9.4 ms]
      Range (min … max):   194.1 ms … 206.8 ms    14 runs

    Benchmark 2: branch
      Time (mean ± σ):     170.7 ms ±   6.6 ms    [User: 158.7 ms, System: 9.3 ms]
      Range (min … max):   160.9 ms … 183.7 ms    17 runs

    Summary
      branch ran
        1.17 ± 0.05 times faster than master
2026-09-13 11:52:30 +09:00
Peter Zhu 1a78389785 Use Set instead of Hash for Array#union
This commit changes Array#union to use a temporary Set instead of Hash for
performance and memory savings. We can see in the following benchmark:

    a = (0...10_000).to_a
    b = (5_000...15_000).to_a

    1_000.times { a.union(b) }

Results:

    Benchmark 1: master
      Time (mean ± σ):     239.9 ms ±   3.5 ms    [User: 224.9 ms, System: 11.7 ms]
      Range (min … max):   234.8 ms … 246.0 ms    12 runs

    Benchmark 2: branch
      Time (mean ± σ):     165.5 ms ±   4.1 ms    [User: 150.9 ms, System: 11.5 ms]
      Range (min … max):   158.9 ms … 174.0 ms    18 runs

    Summary
      branch ran
        1.45 ± 0.04 times faster than master
2026-09-13 11:52:30 +09:00
Peter Zhu 078196c12d Use Set instead of Hash for Array#|
This commit changes Array#| to use a temporary Set instead of Hash for
performance and memory savings. We can see in the following benchmark:

    a = (0...10_000).to_a
    b = (5_000...15_000).to_a

    1_000.times { a | b }

Results:

    Benchmark 1: master
      Time (mean ± σ):     237.5 ms ±   2.0 ms    [User: 223.4 ms, System: 10.9 ms]
      Range (min … max):   234.6 ms … 240.7 ms    12 runs

    Benchmark 2: branch
      Time (mean ± σ):     165.2 ms ±   3.8 ms    [User: 151.2 ms, System: 10.8 ms]
      Range (min … max):   159.8 ms … 174.2 ms    18 runs

    Summary
      branch ran
        1.44 ± 0.04 times faster than master
2026-09-13 11:52:30 +09:00
Peter Zhu 35f46c051f Create internal/set.h for set.c function definitions 2026-09-12 20:07:25 +09:00
Sampo Kuokkanen 07ef97df22 Fetch the execution context once in Array#each (#18610)
rb_yield() calls GET_EC() on every element, which on arm64 is an
out-of-line call to rb_current_ec() (RB_THREAD_CURRENT_EC_NOINLINE).
Hoist it out of the loop and use rb_ec_yield(), as ff831eb057 does
for Mutex#synchronize.

benchmark/loop_each.yml: 1.16x on arm64-darwin, 1.05x on aarch64-linux.
2026-09-03 17:08:20 -07:00
ydah ed55edaa9e Avoid freezing nested array in Array#flatten! 2026-08-28 14:47:11 +09:00
Jeremy Evans 17026737cc Replace Array#flatten{,!} internal ident hash with set
This used a hash with all true values, with is simple to convert
to a set to save on memory when flattening array with a large
number of nested elements.
2026-08-25 07:03:04 -07:00
Yusuke EndohandClaude Opus 5 72a511da24 Split rb_ary_splice() into a pointer version and an array version
Most callers have the replacement as an array object; let them pass it
directly.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 12:10:20 +09:00
Yusuke EndohandClaude Opus 5 46c287e2ca Fix splicing an array that shares its buffer with a longer array
Arrays sharing one buffer have the same data pointer, so rb_ary_splice()
took `a` in `b = a.dup; b.pop; b.concat(a)` for `b` itself and rebased
the source onto `b`'s shorter storage, copying uninitialized memory.
Have the callers tell whether the replacement really is `ary`.

[Bug #22259]

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 12:10:20 +09:00
Jean Boussier 37325e9f7a Array#flatten: optimize the common *args.flatten pattern
A common Ruby idiom for variadic methods is to flatten the argument
list so that it can be called with either variadic arguments or an
Array.

e.g. from the sqlite3 gem:

```ruby
def bind_params(*bind_vars)
  bind_vars.flatten.each do |var|
  # ...
```

However, as soon as `flatten` encounter another array, it has to
protect against recursion, which requires allocating an expensive
identity Hash.
So this pattern has a relatively high cost when called with
a single array argument (e.g. `bind_params [1, 2, 3]`).

We can specialize for that common case without noticeably
impacting other usages of `Array#flatten`:

|                              |compare-ruby|built-ruby|
|:-----------------------------|-----------:|---------:|
|small_flat_ary.flatten        |      7.547M|    7.974M|
|                              |           -|     1.06x|
|small_flat_ary.flatten!       |      6.031M|    6.105M|
|                              |           -|     1.01x|
|large_flat_ary.flatten        |    465.896k|  485.861k|
|                              |           -|     1.04x|
|large_flat_ary.flatten!       |    455.872k|  478.103k|
|                              |           -|     1.05x|
|small_pairs_ary.flatten       |      1.327M|    1.407M|
|                              |           -|     1.06x|
|small_pairs_ary.flatten!      |      1.165M|    1.153M|
|                              |       1.01x|         -|
|large_pairs_ary.flatten       |     96.612k|   96.976k|
|                              |           -|     1.00x|
|large_pairs_ary.flatten!      |     94.108k|   97.289k|
|                              |           -|     1.03x|
|mostly_flat_ary.flatten       |    399.648k|  417.327k|
|                              |           -|     1.04x|
|mostly_flat_ary.flatten!      |    378.315k|  395.946k|
|                              |           -|     1.05x|
|small_nested_ary.flatten      |      2.513M|    7.788M|
|                              |           -|     3.10x|
|small_nested_ary.flatten!     |      2.024M|    5.914M|
|                              |           -|     2.92x|
|large_nested_ary.flatten      |    341.635k|  485.319k|
|                              |           -|     1.42x|
|large_nested_ary.flatten!     |    329.837k|  475.014k|
|                              |           -|     1.44x|
|small_nested_ary.flatten(1)   |      9.497M|   61.350M|
|                              |           -|     6.46x|
|small_nested_ary.flatten!(1)  |      5.136M|   17.668M|
|                              |           -|     3.44x|
|large_nested_ary.flatten(1)   |      1.575M|   60.241M|
|                              |           -|    38.26x|
|large_nested_ary.flatten!(1)  |      1.369M|   16.835M|
|                              |           -|    12.30x|
2026-08-22 22:13:33 +02:00