Skip to content

Add RUBY_GC_HEAP_INIT_SIZE_%d_SLOTS to pre-init pools granularly#7235

Merged
byroot merged 1 commit into
ruby:masterfrom
Shopify:gc-init-pools
Feb 8, 2023
Merged

Add RUBY_GC_HEAP_INIT_SIZE_%d_SLOTS to pre-init pools granularly#7235
byroot merged 1 commit into
ruby:masterfrom
Shopify:gc-init-pools

Conversation

@casperisfine

Copy link
Copy Markdown
Contributor

The old RUBY_GC_HEAP_INIT_SLOTS isn't really usable anymore as it initialize all the pools by the same factor, but it's unlikely that pools will need similar sizes.

In production our 40B pool is 5 to 6 times bigger than our 80B pool.

cc @peterzhu2118 @eightbitraptor @jasonhl

@peterzhu2118 peterzhu2118 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you also add some tests for this?

Comment thread gc.c
Comment thread gc.c Outdated
Comment thread gc.c Outdated
@casperisfine

Copy link
Copy Markdown
Contributor Author

Can you also add some tests for this?

Yeah definitely, I should have opened this as a draft. I'd like to test this in prod before even considering to merge it.

Comment thread gc.c Outdated
@casperisfine

Copy link
Copy Markdown
Contributor Author

I've cleaned up the logic and added some very basic tests.

I could add some assertions on the pool sizes, but I fear it may be a bit fragile on other arch? @peterzhu2118 if you think it shouldn't be flaky I can add them.

Comment thread test/ruby/test_gc.rb
Comment thread gc.c
@rafaelfranca

Copy link
Copy Markdown
Contributor

How would users know which are the possible values of %d in order to configure?

@casperisfine

Copy link
Copy Markdown
Contributor Author

How would users know which are the possible values of %d in order to configure?

It's exposed via GC.stat_heap

>> GC.stat_heap
=> 
{0=>{:slot_size=>40, ...},
 1=>{:slot_size=>80, ...},
 2=>{:slot_size=>160, ...},
 3=>{:slot_size=>320, ...},
 4=>{:slot_size=>640, ...}}

@rafaelfranca

Copy link
Copy Markdown
Contributor

My question is more about where is this documented. With RUBY_GC_HEAP_INIT_SIZE_%d_SLOTS being exposed, I believe it should be documented, so people know what is the expected values of %d and even where to find them.

@casperisfine
casperisfine force-pushed the gc-init-pools branch 2 times, most recently from 6914a92 to 5ad23a1 Compare February 7, 2023 10:58
@casperisfine

Copy link
Copy Markdown
Contributor Author

My question is more about where is this documented.

Ok, RUBY_GC_HEAP_INIT_SLOTS is only documented in the manpage, so I added these new variables there.

Comment thread man/ruby.1 Outdated
The old RUBY_GC_HEAP_INIT_SLOTS isn't really usable anymore as
it initalize all the pools by the same factor, but it's unlikely
that pools will need similar sizes.

In production our 40B pool is 5 to 6 times bigger than our 80B pool.
@byroot
byroot merged commit 3ab3455 into ruby:master Feb 8, 2023
@mttkay

mttkay commented Apr 3, 2024

Copy link
Copy Markdown

@casperisfine I was wondering, are there any stats available for size pool occupation (i.e. live objects per pool?)

I am not sure how one would now calculate total memory use. Previously this was just the 40B multiple of GC.stat[:heap_live_slots] plus whatever memory was malloc'ed for any objects that did not fit a single slot.

However, now objects are distributed over a number of pools, and GC.stat_heap only returns sizing stats, but does not include information on how many live objects are in each pool (which we could then multiple with the slot size of the respective pool to arrive at total memory use per pool?)

Thank you!

@byroot

byroot commented Apr 3, 2024

Copy link
Copy Markdown
Member

I was wondering, are there any stats available for size pool occupation (i.e. live objects per pool?)

Yes, it's in GC.stat_heap.

@mttkay

mttkay commented Apr 3, 2024

Copy link
Copy Markdown

You mean heap_eden_slots?

@byroot

byroot commented Apr 3, 2024

Copy link
Copy Markdown
Member

Yes.

@mttkay

mttkay commented Apr 3, 2024

Copy link
Copy Markdown

@byroot sorry for my ignorance, but would this actually give me actual memory consumed by the application? Was just looking over https://scoutapm.com/blog/copy-manage-ruby-memory-usage again, and the way I understand eden slots to work is that they may hold a live object, but may also be tagged free. Of course they still consume process memory, but that cannot be attributed to the application if it's unused?

So this is still different from the original heap_live_slots, which are only those slots actually occupied by an instance of RVALUE that represents an actual live object? 🤔 I might be misunderstanding how the GC manages these.

@mttkay

mttkay commented Apr 3, 2024

Copy link
Copy Markdown

Asked differently: I am looking for the per-pool equivalent of:

heap_live_slots

    The total number of slots which contain live objects

heap_free_slots

    The total number of slots which do not contain live objects

https://docs.ruby-lang.org/en/master/GC.html#method-c-stat

Are you saying heap_eden_slots =~ heap_live_slots (just for a given pool)? What is the equivalent of heap_free_slots for a pool in stat_heap?

@mttkay

mttkay commented Apr 3, 2024

Copy link
Copy Markdown

This doesn't sound right @byroot. When booting into an irb:

irb(main):002> GC.stat[:heap_live_slots]
=> 123976
irb(main):005> GC.stat_heap.each_value.sum { |pool| pool[:heap_eden_slots] }
=> 332241

Total live slots across all heaps are about 1/3 of all reported eden slots in pools. So I would conclude these metrics measure different things?

@byroot

byroot commented Apr 3, 2024

Copy link
Copy Markdown
Member

Yeah, sorry. heap_eden_slot include all slots that are allocated (from an OS point of view) regardless of whether the slot is in use.

but would this actually give me actual memory consumed by the application?

If that's the answer you are looking for, then slots aren't really what you should look for. You're better to use ObjectSpace.dump_all and sum all the memsize, that's part of what https://github.com/Shopify/heap-profiler does.

Now of course this is rather slow, so I don't know if you are trying to do this at runtime with a minimal overhead, in which case, I don't have a solution for you. Even if you had the proper number of slots in use, with their respective sizes, that would still overlook all the memory that is allocated via malloc, which is likely an extremely large portion.

@mttkay

mttkay commented Apr 3, 2024

Copy link
Copy Markdown

I see, thank you.

Yes, we are doing this at runtime using an MRI patch that tracks per-thread heap allocations. Previously, it was then trivial (and cheap) to compute per-request memory use like so:

total_memory_used = (total_live_objects * RVALUE_SIZE) + large_obj_malloced_bytes

The last component, malloc'ed bytes for objects that don't fit an RVALUE slot were tracked in the VM directly.

However, this calculation is now wrong (it underestimates live memory), because we don't know anymore how total_live_objects distributes across the GC-managed heap (object space), which now all have different slot sizes. 🤔

@byroot

byroot commented Apr 3, 2024

Copy link
Copy Markdown
Member

using an MRI patch that tracks per-thread heap allocations

Then presumably you should be capable of patching heap_live_slots into GC.stat_heap

@mttkay

mttkay commented Apr 3, 2024

Copy link
Copy Markdown

Presumably.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants