Project

General

Profile

« Previous | Next » 

Revision 67d1dd2e

Added by jeremyevans (Jeremy Evans) 4 months ago

Avoid array allocation for *nil, by not calling nil.to_a

The following method call:

a(*nil)

A method call such as a(*nil) previously allocated an array, because
it calls nil.to_a, but I have determined this array allocation is
unnecessary. The instructions in this case are:

0000 putself                                                          (   1)[Li]
0001 putnil
0002 splatarray                             false
0004 opt_send_without_block                 <calldata!mid:a, argc:1, ARGS_SPLAT|FCALL>
0006 leave

The method call uses ARGS_SPLAT without ARGS_SPLAT_MUT, so the
returned array doesn't need to be mutable. I believe all cases where
splatarray false are used allow the returned object to be frozen,
since the false means to not duplicate the array. The optimization
in this case is to have splatarray false push a shared empty frozen
array, instead of calling nil.to_a to return a newly allocated array.

There is a slightly backwards incompatibility with this optimization,
in that nil.to_a is not called. However, I believe the new behavior
of *nil not calling nil.to_a is more consistent with how **nil
does not call nil.to_hash. Also, so much Ruby code would break if
nil.to_a returned something different from the empty hash, that it's
difficult to imagine anyone actually doing that in real code, though
we have a few tests/specs for that.

I think it would be bad for consistency if *nil called nil.to_a
in some cases and not others, so this changes other cases to not
call nil.to_a:

For [*nil], this uses splatarray true, which now allocates a
new array for a nil argument without calling nil.to_a.

For [1, *nil], this uses concattoarray, which now returns
the first array if the second array is nil.

This updates the allocation tests to check that the array allocations
are avoided where possible.

Implements [Feature #21047]