Project

General

Profile

Actions

Bug #20608

closed

Hash#find always allocates each iterated pair

Added by ivoanjo (Ivo Anjo) 5 months ago. Updated 5 months ago.

Status:
Closed
Target version:
-
ruby -v:
ruby 3.4.0preview1 (2024-05-16 master 9d69619623) [x86_64-linux]
[ruby-core:118446]

Description

Hey there!

Recently I ran into this sharp edge in Hash#find:

puts RUBY_DESCRIPTION

def allocated_now = GC.stat(:total_allocated_objects)

dummy_hash = 10_000.times.each_with_index.to_h

before_allocs = allocated_now
dummy_hash.any? { |k, v| }
puts "Allocated #{allocated_now - before_allocs} for #{File.read(__FILE__).lines[__LINE__-2]}"

before_allocs = allocated_now
dummy_hash.each { |k, v| }
puts "Allocated #{allocated_now - before_allocs} for #{File.read(__FILE__).lines[__LINE__-2]}"

before_allocs = allocated_now
dummy_hash.find { |k, v| }
puts "Allocated #{allocated_now - before_allocs} for #{File.read(__FILE__).lines[__LINE__-2]}"

before_allocs = allocated_now
dummy_hash.any? { |k| }
puts "Allocated #{allocated_now - before_allocs} for #{File.read(__FILE__).lines[__LINE__-2]}"

Result:

ruby 3.4.0preview1 (2024-05-16 master 9d69619623) [x86_64-linux]
Allocated 0 for dummy_hash.any? { |k, v| }
Allocated 0 for dummy_hash.each { |k, v| }
Allocated 10002 for dummy_hash.find { |k, v| }
Allocated 10000 for dummy_hash.any? { |k| }

That is, while Hash#any?, Hash#each, etc avoid doing any allocations during iteration, Hash#find does not hit the rb_block_pair_yield_optimizable => each_pair_i_fast fast path, and so is massively costly compared to the others.

This is very surprising, as I'd expect a find to have a comparable cost to any? (and I ended up redoing some code to avoid find).

Also while experimenting a bit, it was surprising to me that the allocation optimization only kicks in when |k, v| are declared, and thus .any? { |k| } is also more expensive.

Actions

Also available in: Atom PDF

Like0
Like0Like0Like1Like0Like0