Skip to content
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

[NO-TICKET] Optimize CodeProvenance#record_loaded_files to avoid allocations #3757

Merged
merged 1 commit into from
Jul 8, 2024

Commits on Jul 4, 2024

  1. [NO-TICKET] Optimize CodeProvenance#record_loaded_files to avoid allo…

    …cations
    
    **What does this PR do?**
    
    This PR replaces a `Hash#find` with a `Hash#any?` in the Profiler
    `CodeProvenance` collector.
    
    This seemingly innocuous change actually gets rid of a bunch of memory
    allocations.
    
    It turns out that when `Hash#find` is being used, Ruby allocates an
    array to contain the `[key, value]` pair that's passed into the block,
    for each entry in the hash (on top of a few more objects -- but this is
    the biggest offender unless the hash is empty/almost empty).
    
    The VM actually has an optimization for a lot of operations, including
    `Hash#any?` that avoids allocating any extra memory, but because
    `Hash#find` is actually inherited from `Enumerable`, this optimization
    does not kick in.
    
    **Motivation:**
    
    Eliminate unneeded memory allocations.
    
    **Additional Notes:**
    
    Here's a very simple reproducer that shows the issue:
    
    ```
    puts RUBY_DESCRIPTION
    
    def allocated_now; GC.stat(:total_allocated_objects); end
    
    hash = {a: 1, b: 2, c: 3}
    v = nil
    
    before_find = allocated_now
    10.times { _, v = hash.find { |k, _| k == :c } }
    puts "Found value #{v}, allocated #{allocated_now - before_find}"
    
    before_any = allocated_now
    10.times { v = nil; hash.any? { |k, val| (v = val) if k == :c } }
    
    puts "Found value #{v}, allocated #{allocated_now - before_any}"
    ```
    
    and here's how it looks on the latest Ruby version:
    
    ```
    ruby 3.4.0preview1 (2024-05-16 master 9d69619623) [x86_64-linux]
    Found value 3, allocated 64
    Found value 3, allocated 1
    ```
    
    (Note that there's a few more allocations going on with `Hash#find`,
    not only the arrays for the pairs, and we get rid of them ALL!)
    
    **How to test the change?**
    
    This operation already has test coverage. I considered adding some
    counting of allocated objects, but we've had quite a bunch of
    flakiness in some of the profiler specs when counting objects, so
    I've decided to not add any performance-specific tests for this.
    ivoanjo committed Jul 4, 2024
    Configuration menu
    Copy the full SHA
    f0be554 View commit details
    Browse the repository at this point in the history