Project

General

Profile

Feature #16600

Optimized opcodes for frozen arrays and hashes literals

Added by byroot (Jean Boussier) 9 months ago. Updated 9 months ago.

Status:
Open
Priority:
Normal
Assignee:
-
Target version:
-
[ruby-core:97022]

Description

Context

A somewhat common pattern when trying to optimize a tight loop is to avoid allocations from some regular idioms such as a parameter default value being an empty hash or array, e.g.

def some_hotspot(foo, options = {})
  # ...
end

Is often translated in:

EMPTY_HASH = {}.freeze
private_constant :EMPTY_HASH
def some_hotspot(foo, options = EMPTY_HASH)
  # ...
end

But there are many variations, such as (something || []).each .., etc.

A search for that pattern on GitHub returns thousands of hits, and more specifically you'll often see it in popular gems such as Rails.

Proposal

I believe that the parser could apply optimizations when freeze is called on a Hash or Array literal, similar to what it does for String literals (minus the deduplication).

I implemented it as a proof of concept for [].freeze specifically, and it's not particularly complex, and I'm confident doing the same for {}.freeze would be just as simple.

Potential followup

I also went a bit farther, and did another proof of concept that reuse that opcode for non empty literal arrays. Most of the logic needed to decided wether a literal array can be directly used already exist for the duparray opcode.

So it short opt_ary_freeze / opt_hash_freeze could substitute duparray / duphash if .freeze is called on the literal, and that would save an allocation. That isn't huge, but could be useful for things such as:

%i(foo bar baz).freeze.include?(value)

Or to avoid allocating hashes and arrays in pattern matching:

case value
in { foo: 42 }.freeze
  # ...
end

Related issues

Related to Ruby master - Feature #15393: Add compilation flags to freeze Array and Hash literalsOpenActions

Also available in: Atom PDF