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

region memory limit #48691

Merged
merged 1 commit into from
Feb 26, 2021
Merged

region memory limit #48691

merged 1 commit into from
Feb 26, 2021

Conversation

Maoni0
Copy link
Member

@Maoni0 Maoni0 commented Feb 24, 2021

  • implemented the logic to handle when we are really running out of memory.
    We need to be resilient to it during a GC. We might need an empty region
    per heap so we try to get it up front and if we can't get it and find out
    that we do need it, we resort to a special sweep mode.
  • also take virtual memory load into consideration
  • fixed the logic to decide whether we should compact based on space.
  • fixed various places when we can't get a region, we need to make sure we
    don't thread 0 into the region list.
  • made allocated_since_last_gc per more_space_lock otherwise we are not doing
    the accounting correctly
  • moved the pinning for STRESS_REGIONS into a blocking GC otherwise we can have
    AV from BGC trying to mark the pinned object while it's being constructed
  • got rid of the places that use the hard coded REGION_SIZE
  • included one perf change in reset_memory

@ghost
Copy link

ghost commented Feb 24, 2021

Tagging subscribers to this area: @dotnet/gc
See info in area-owners.md if you want to be subscribed.

Issue Details
  • implemented the logic to handle when we are really running out of memory.
    We need to be resilient to it during a GC. We might need an empty region
    per heap so we try to get it up front and if we can't get it and find out
    that we do need it, we resort to a special sweep mode.
  • also take virtual memory load into consideration
  • fixed the logic to decide whether we should compact based on space.
  • fixed various places when we can't get a region, we need to make sure we
    don't thread 0 into the region list.
  • made allocated_since_last_gc per more_space_lock otherwise we are not doing
    the accounting correctly
  • moved the pinning for STRESS_REGIONS into a blocking GC otherwise we can have
    AV from BGC trying to mark the pinned object while it's being constructed
  • got rid of the places that use the hard coded REGION_SIZE
  • included one perf change in reset_memory
Author: Maoni0
Assignees: -
Labels:

area-GC-coreclr

Milestone: -

src/coreclr/gc/gc.cpp Outdated Show resolved Hide resolved
src/coreclr/gc/gc.cpp Outdated Show resolved Hide resolved
num_heaps = n_heaps;
#endif //MULTIPLE_HEAPS
left_in_commit /= num_heaps;
if (left_in_commit <= space_required)
Copy link
Member

Choose a reason for hiding this comment

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

should this just be < ?

Copy link
Member Author

Choose a reason for hiding this comment

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

yes, will change it.

@@ -3571,7 +3571,7 @@ uint8_t* region_allocator::allocate (uint32_t num_units)
uint32_t* current_index = region_map_start;
uint32_t* end_index = region_map_end;

dprintf (1, ("\nsearcing %d->%d", (int)(current_index - region_map_start), (int)(end_index - region_map_start)));
dprintf (1, ("searching %d->%d", (int)(current_index - region_map_start), (int)(end_index - region_map_start)));
Copy link
Contributor

Choose a reason for hiding this comment

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

Should the dprintf level be REGIONS_LOG here?

Copy link
Member Author

Choose a reason for hiding this comment

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

I will change all of the dprintf's in region_allocator to REGIONS_LOG.

from = align_lower_segment (from);
end = align_on_segment (end);
dprintf (1, ("from: %Ix, end: %Ix, size: %Ix", from, end, sizeof (seg_mapping)*(((size_t)(end - from) >> gc_heap::min_segment_size_shr))));
dprintf (1, ("from: %Ix, end: %Ix, size: %Ix", from, end,
Copy link
Contributor

Choose a reason for hiding this comment

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

Use dprintf level REGIONS_LOG here as well?

region = allocate_new_region (__this, 0, false);
if (region)
{
init_table_for_region (0, region);
Copy link
Contributor

Choose a reason for hiding this comment

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

How about the case where init_table_for_region fails because the mark array cannot be committed? It seems that we would return true in this case, but likely still fail when we later try to get the region.

Copy link
Member Author

Choose a reason for hiding this comment

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

good catch! I did check for this (I set region to 0 in init_table_for_region when we can't commit) but I made a mistake when I refactored it out to its own method. should have init_table_for_region return false if we can't commit.


if (gen_number <= max_generation)
{
size_t first_brick = brick_of (heap_segment_mem (region));
Copy link
Contributor

Choose a reason for hiding this comment

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

Would this AV if commit_mark_array_new_seg failed above?

Copy link
Member Author

Choose a reason for hiding this comment

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

it wouldn't - right now the GC bookkeeping datastructure is completely committed for the region's range except for the mark array part.

Copy link
Contributor

Choose a reason for hiding this comment

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

but above, you set region to 0, so as heap_segment_mem dereferences it, you would AV before you even get to the brick_of part.

Copy link
Member Author

Choose a reason for hiding this comment

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

oh I misunderstood your intention for the question...absolutely, I shouldn't call this when the region is already set to 0

}
else
{
assert (brick_table[brick_of (heap_segment_mem (region))] == 0);
Copy link
Contributor

Choose a reason for hiding this comment

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

Ditto.

@Maoni0
Copy link
Member Author

Maoni0 commented Feb 25, 2021

I found a problem with aging handles for the special sweep case. will fix it later today.

@@ -3364,7 +3377,7 @@ class gc_heap
PER_HEAP
int pinning_seg_interval;
PER_HEAP
int num_gen0_segs;
int num_gen0_regions;
Copy link
Contributor

Choose a reason for hiding this comment

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

It looks like this could be a local variable instead of a field.

Copy link
Member Author

Choose a reason for hiding this comment

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

I specifically wanted this to be a global so it introduces some variation instead of always pinning the 1st, 3rd, etc gen0 region. but I changed it to size_t instead of int.


if ((num_gen0_regions % pinning_seg_interval) == 0)
{
int align_const = get_alignment_constant (TRUE);
Copy link
Contributor

Choose a reason for hiding this comment

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

It would be helpful to put a comment, e.g. "Pin one object at the beginning".

Copy link
Member Author

Choose a reason for hiding this comment

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

added.

uint8_t* obj_to_pin = heap_segment_mem (gen0_region);
pin_by_gc (obj_to_pin);

obj_to_pin += Align (size (obj_to_pin), align_const);
Copy link
Contributor

Choose a reason for hiding this comment

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

Ditto: "Pin one object near the middle".

Copy link
Member Author

Choose a reason for hiding this comment

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

added.

@@ -18218,6 +18243,14 @@ int gc_heap::generation_to_condemn (int n_initial,
}
}

#ifdef USE_REGIONS
if (!try_get_new_free_region())
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't understand why we need a new free region here - could you explain?

Copy link
Member Author

Choose a reason for hiding this comment

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

it's to prepare for possibly needing to get a new region for gen0 in the later phases.

#ifdef USE_REGIONS
if (!try_get_new_free_region())
{
dprintf (GTC_LOG, ("can't get an empty region -> full compacting"));
Copy link
Contributor

Choose a reason for hiding this comment

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

In your comments for the PR, you said in this case we will do a special kind of sweep. The dprintf says we do a full compacting GC. Is there a contradiction, or what am I missing?

Copy link
Member Author

Choose a reason for hiding this comment

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

it's the "and find out that we do need it," part - we try to get a new region here just to prepare for if we need it, as long as we don't actually need it, we will be doing a full compacting GC. but if we can't get it here and later find that we actually need it that's when we absolute run out of memory and we only do the special sweep then.

Copy link
Contributor

Choose a reason for hiding this comment

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

Got it, thanks!

}
}

void gc_heap::process_remaining_regions (int current_plan_gen_num, generation* consing_gen)
{
assert ((current_plan_gen_num == 0) || (!settings.promotion && (current_plan_gen_num == -1)));

if (special_sweep_p)
{
assert (pinned_plug_que_empty_p());
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you put a comment why this should be true?

{
dprintf (REGIONS_LOG, ("h%d couldn't get a region to plan gen0, special sweep on",
heap_number));
special_sweep_p = true;
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it would be good to put a comment explaining how the special sweep mode works, i.e. what's special about it.

+ implemented the logic to handle when we are really running out of memory.
  We need to be resilient to it during a GC. We might need an empty region
  per heap so we try to get it up front and if we can't get it and find out
  that we do need it, we resort to a special sweep mode.
+ also take virtual memory load into consideration
+ fixed the logic to decide whether we should compact based on space.
+ fixed various places when we can't get a region, we need to make sure we
  don't thread 0 into the region list.
+ made allocated_since_last_gc per more_space_lock otherwise we are not doing
  the accounting correctly
+ moved the pinning for STRESS_REGIONS into a blocking GC otherwise we can have
  AV from BGC trying to mark the pinned object while it's being constructed
+ got rid of the places that use the hard coded REGION_SIZE
+ included one perf change in reset_memory
@Maoni0 Maoni0 merged commit c41894d into dotnet:master Feb 26, 2021
@ghost ghost locked as resolved and limited conversation to collaborators Mar 30, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants