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

[Question] What's the relationship of GCHeap, gc_heap, heap_segment, alloc_context and generation? #7275

Closed
303248153 opened this issue Jan 23, 2017 · 8 comments
Assignees
Labels
area-GC-coreclr question Answer questions and provide assistance, not an issue with source code or documentation.

Comments

@303248153
Copy link
Contributor

As I asked in previous issue #7249, now I want to known the relationship of these classes.
I made a graph and several conclusions, please help me determine they're correct or wrong.

dia

  • There one GCHeap instance used for interface between GC and EE, it didn't hold gc_heap instance.
  • There logical cores GCHeap and gc_heap instances hold each other.
  • Each gc_heap instance hold 4 generation instances (gen 0, gen 1, gen 2, gen 3).
    • But why generation_table have 5(NUMBERGENERATIONS+1) elements?
  • Gen 0, Gen 1, Gen 2 point to same segment in the beginning
  • Gen 0 points to the rightmost position
  • Gen 3 use different segment list (large heap segment)
  • Each managed thread have it's own alloc_context, alloc_ptr is 0 in the beginning
    • So first gc_heap::allocate of each managed thread will always call allocate_more_space
  • Gen 0 and Gen 3 have it's own alloc_context
    • They will be used when there no explicit allocation context provided, like GCHeapUtilities::UseAllocationContexts returns false
  • When the allocation context has run out, it will try to allocate more space from free list or segment end

Also I have a other small question, what's alloc_context::home_heap stand for, and what's the purpose of balance_heaps?

cc @swgillespie
Please help me if you have some time, thanks your guys first.

@swgillespie
Copy link
Contributor

There one GCHeap instance used for interface between GC and EE, it didn't hold gc_heap instance.

That's correct. Whenever a gc_heap is constructed (Server GC only - Workstation GC doesn't create a new gc_heap because everything of interest is static), a new GCHeap instance is created to wrap it.

There logical cores GCHeap and gc_heap instances hold each other.

The pGenGCHeap field of a GCHeap class refers to a gc_heap instance, and the vm_heap field of a gc_heap class refers to a GCHeap.

Each gc_heap instance hold 4 generation instances (gen 0, gen 1, gen 2, gen 3).
But why generation_table have 5(NUMBERGENERATIONS+1) elements?

I'm not really sure about that - perhaps there's a historical reason I'm unaware of?

Gen 0, Gen 1, Gen 2 point to same segment in the beginning

The two segments reserved on startup are the ephemeral segment, which contains Gen 0 and Gen 1, and the initial LOH segment. When it comes time to promote things to Gen 2, an additional heap segment will be reserved, which becomes the new ephemeral segment and the existing segment becomes a Gen 2 segment.

This is not to say that there are only ever Gen 0 and Gen 1 objects on the ephemeral segment there still may be situations where Gen 2 objects end up on the ephemeral segment.

Gen 0 points to the rightmost position

I suppose that depends on how you draw up the heap - when the GC is initialized, the ephemeral segment is at the "top" of a heap with regards to memory addresses (i.e. any objects greater than the lower bound of the ephemeral segment must be in the ephemeral segment, because there are no segments past the higher bound of the ephemeral segment).

Though this is the case at startup, it is not always the case during normal execution, as new segments may get reserved that are located at a higher memory address than the ephemeral segment. When this is the case, we have to inform the EE that this has occurred, since we have to switch write barriers. (See WriteBarrierParameters in gcinterface.h for more details on that.)

Gen 3 use different segment list (large heap segment)

Yes, LOH segments are logically separate from SOH segments.

Each managed thread have it's own alloc_context, alloc_ptr is 0 in the beginning
So first gc_heap::allocate of each managed thread will always call allocate_more_space

For Server GC, this ends up calling gc_heap::balance_heaps, which selects a home heap for this allocation context (more on that below).

Gen 0 and Gen 3 have it's own alloc_context
They will be used when there no explicit allocation context provided, like GCHeapUtilities::UseAllocationContexts returns false

GCHeapUtilities::UseAllocationContexts never returns false in CoreCLR, and it only returns false on the full framework CLR when running on a single processor. Also, the LOH (Gen 3) does not use allocation contexts. (Allocation quantums are around ~8k, and by definition objects being allocated on the LOH will exceed that size, so it defeats the purpose.)

When the allocation context has run out, it will try to allocate more space from free list or segment end

Yep! There are many different states that the allocator can be in, but this is the case for the most common state.

Also I have a other small question, what's alloc_context::home_heap stand for, and what's the purpose of balance_heaps?

The point of Server GC is to parallelize GC work across multiple cores, and it's a serious problem if the heaps become unbalanced - by that I mean it's a problem if there exists a heap such that a GC has more work to do than another heap. That's pretty vague, but the idea is that we want every GC on every heap across every processor to take approximately the same amount of time, so that we minimize the amount of time that the other cores spend waiting on a single heap that's taking a long time to do something(*).

In order to remedy this problem, we actively try to balance allocations across heaps. Usually an allocation's alloc heap (the heap it is currently allocating upon, and the heap that gets asked for more allocation quantums) is the same as its home heap (the heap chosen to be the "home" of allocations from this thread). The two concepts are very close to one another, and the alloc heap and home heap are often the same. Whether or not the alloc heap and home heap are the same is decided through heuristics in balance_heaps.

Looking at the code, the GC will freely relocate the home heap to another heap if it's advantageous, but it will only relocate the alloc heap if it finds a heap that's "better" for allocating upon than the current alloc heap.

(*) A footnote: all server GC threads have to synchronize at certain points throughout the process of a GC. These are called "joins" in the code and docs. We want to minimize the time spent joining, since it's wasting processor time.

@303248153
Copy link
Contributor Author

Thanks for your detailed answer! That make me learned a lots.

Also, the LOH (Gen 3) does not use allocation contexts.
It's my mistake, I saw allocation context of gen 3 initialized but yes it never used.

Now i have another question about allocation context and segment.
Assume we have 2 cores and 3 threads
Thread 0 on core 0
Thread 1 on core 0
Thread 2 on core 1
And then we should have 2 ephemeral segment.
Did thread 0 and thread 1's allocation context point to same ephemeral segment? And what's the memory layout?
Also I notice allocate object from context wont make a free object(unused array), so if multi thread point to same ephemeral segment, how can coreclr keep the heap crawlable?

@swgillespie
Copy link
Contributor

When using Workstation GC, all three threads will have allocation contexts that point to the same (the only) ephemeral segment. When using Server GC, threads 0 and 1 might be using the same ephemeral segment, depending on whether or not their alloc heaps have been set to the core they are currently running on. Each heap's allocator is handing out logically separate allocation quantums to each context, so no thread is allocating in any other thread's allocation region.

I'm not quite sure what you mean by your last question - When an allocation quantum is exhausted, all remaining memory that wasn't used to allocate objects gets turned into a free object.

@303248153
Copy link
Contributor Author

Thanks for your response again.
I put my question into a picture, please check this
alloccontext
There 2 questions

  • What's happend after thread 0's allocation context used all space (allocation quantum exhausted)?
  • If it could become State C, why can we say this heap is crawlable?

And one more question

  • If there are thousands of threads, how can they point to the same ephemeral segment? If not, is that mean object allocate from some threads will be Gen 2 at the beginning?

@303248153
Copy link
Contributor Author

303248153 commented Feb 6, 2017

Today I used lldb to trace coreclr process, and found the previous question maybe stupid :(
With workstation gc the ephemeral segment size is 0x10000000 and the allocation quantum is 8K so that can be about 32768 threads use this segment, that mean State A is almost impossible.
And with server gc the ephemeral segment size is 0x100000000 (4G), just so big.

And ephemeral_heap_segment.next is always be nullptr, remain segments are in generation_table.
Anyway I just created two new graphs to determine the relationship of these classes,
please help me check if it is correct if you have some times, then I will close this issue, thank you.

svr_rel
wks_rel

@303248153 303248153 reopened this Feb 6, 2017
@mattwarren
Copy link
Contributor

mattwarren commented Feb 6, 2017

@303248153 On a related note, have you seen the GCSample that's available?

It might give you another way of examining the GC as it allows you to access it outside of the CLR, from the comments in the file:

// This sample demonstrates:
//
// * How to initialize GC without the rest of CoreCLR
// * How to create a type layout information in format that the GC expects
// * How to implement fast object allocator and write barrier
// * How to allocate objects and work with GC handles

Also thanks for the info that you and @swgillespie have provided in this issue, I've learnt lots!!

@Maoni0
Copy link
Member

Maoni0 commented Feb 6, 2017

Debugging through the code is the best way of understanding what the code does. Also I would encourage you to take a look at the GC BotR chapter which answers many of your questions. For example:

for allocation contexts:

Keeping the heap crawlable: The allocator makes sure to make a free object out of left over memory in each allocation quantum. For example, if there is 30 bytes left in an allocation quantum and the next object is 40 bytes, the allocator will make the 30 bytes a free object and get a new allocation quantum

for heap segments:

There’s always only one ephemeral segment in each small object heap, which is where gen0 and gen1 live. This segment may or may not include gen2 objects. In addition to the ephemeral segment, there can be zero, one or more additional segments, which will be gen2 segments since they only contain gen2 objects.

@303248153
Copy link
Contributor Author

Thanks for all your guys.
gc.cpp have 30000+ lines but GC botr chapter only have 300+ lines so I don't think the document is enough to explain how gc works,
plus there very few comments in gc.cpp, understand it without a debugger is almost impossible
Anyway I will keep digging :)

@msftgits msftgits transferred this issue from dotnet/coreclr Jan 31, 2020
@ghost ghost locked as resolved and limited conversation to collaborators Dec 26, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-GC-coreclr question Answer questions and provide assistance, not an issue with source code or documentation.
Projects
None yet
Development

No branches or pull requests

4 participants