Skip to content

Show performance warnings for easily avoidable unnecessary implicit splat allocations #13135

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

jeremyevans
Copy link
Contributor

For the following method calls:

m(*ary, kw: kw)
m(*ary, **kw)
m(*ary, &block)
m(**hash, &block)

If kw and block are method calls, Ruby must allocate an array for *ary and a hash for **hash, to avoid an evaluation order issue in the case where kw or block methods modify ary or hash.

These implicit allocations are trivial to avoid by assigning the values to a local variable:

kw = self.kw
block = self.block
m(*ary, kw: kw)
m(*ary, **kw)
m(*ary, &block)
m(**hash, &block)

Ruby does not to allocate an array for *ary or a hash for **kw in these cases, because there is no evaluation order issue.

To help users optimize their code to avoid these unnecessary implicit allocations, this adds performance warnings in these cases, letting users know that they can optimize their code by assinging to a local variable:

$ ruby -W:performance -e 'def kw; {} end; a = []; p(*a, **kw)'
-e: warning: This method call implicitly allocates a potentially unnecessary
array for the positional splat, because a keyword, keyword splat, or block pass
expression could cause an evaluation order issue if an array is not allocated
for the positional splat. You can avoid this allocation by assigning the related
keyword, keyword splat, or block pass expression to a local variable and using
that local variable.

$ ruby -W:performance -e 'def b; ->{} end; h = {}; p(**h, &b)'
-e: warning: This method call implicitly allocates a potentially unnecessary
hash for the keyword splat, because the block pass expression could cause an
evaluation order issue if a hash is not allocated for the keyword splat. You
can avoid this allocation by assigning the block pass expression to a local
variable, and using that local variable.

…plat allocations

For the following method calls:

```ruby
m(*ary, kw: kw)
m(*ary, **kw)
m(*ary, &block)
m(**hash, &block)
```

If `kw` and `block` are method calls, Ruby must allocate an array for `*ary` and a hash for `**hash`, to avoid an evaluation order issue in the case where `kw` or `block` methods modify `ary` or `hash`.

These implicit allocations are trivial to avoid by assigning the values to a local variable:

```ruby
kw = self.kw
block = self.block
m(*ary, kw: kw)
m(*ary, **kw)
m(*ary, &block)
m(**hash, &block)
```

Ruby does not to allocate an array for `*ary` or a hash for `**kw` in these cases, because there is no evaluation order issue.

To help users optimize their code to avoid these unnecessary implicit allocations, this adds performance warnings in these cases, letting users know that they can optimize their code by assinging to a local variable:

```
$ ruby -W:performance -e 'def kw; {} end; a = []; p(*a, **kw)'
-e: warning: This method call implicitly allocates a potentially unnecessary
array for the positional splat, because a keyword, keyword splat, or block pass
expression could cause an evaluation order issue if an array is not allocated
for the positional splat. You can avoid this allocation by assigning the related
keyword, keyword splat, or block pass expression to a local variable and using
that local variable.

$ ruby -W:performance -e 'def b; ->{} end; h = {}; p(**h, &b)'
-e: warning: This method call implicitly allocates a potentially unnecessary
hash for the keyword splat, because the block pass expression could cause an
evaluation order issue if a hash is not allocated for the keyword splat. You
can avoid this allocation by assigning the block pass expression to a local
variable, and using that local variable.
```
MAYBE_UNNECESSARY_ALLOC_KW_SPLAT is unnecessary, since DUP_SINGLE_KW_SPLAT
is only used in cases where the allocation is only necessary to avoid
evaluation order issues.

Only set MAYBE_UNNECESSARY_ALLOC_SPLAT in the case where the block pass
argument is unsafe if we would not be allocating an array for the splat
for another reason.
Copy link

launchable-app bot commented Apr 19, 2025

All Tests passed!

✖️no tests failed ✔️61954 tests passed(1 flake)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

1 participant