-
Notifications
You must be signed in to change notification settings - Fork 6.1k
8357471: GenShen: Share collector reserves between young and old #25357
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
base: master
Are you sure you want to change the base?
Changes from all commits
6c90312
43c1842
c097513
980110b
7ae16e1
ac39797
b6ef323
a8e40e1
1911e04
7ce1120
2412337
6ddcc4d
437c71f
65eb7b9
77ae0c1
fa28658
7e97937
b489f62
2aa534a
85f03c3
143515d
be05b38
da883d7
eda09d5
577b0cb
3cf1b87
684b9c2
9a2b972
ce897cd
6abbd8b
242bce5
49abfca
f0afae0
3d55a64
babc04e
ae8c83c
8ec6ea0
aea2b92
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -53,93 +53,134 @@ void ShenandoahGlobalHeuristics::choose_global_collection_set(ShenandoahCollecti | |
size_t cur_young_garbage) const { | ||
auto heap = ShenandoahGenerationalHeap::heap(); | ||
size_t region_size_bytes = ShenandoahHeapRegion::region_size_bytes(); | ||
size_t capacity = heap->young_generation()->max_capacity(); | ||
size_t young_capacity = heap->young_generation()->max_capacity(); | ||
size_t old_capacity = heap->old_generation()->max_capacity(); | ||
size_t garbage_threshold = region_size_bytes * ShenandoahGarbageThreshold / 100; | ||
size_t ignore_threshold = region_size_bytes * ShenandoahIgnoreGarbageThreshold / 100; | ||
const uint tenuring_threshold = heap->age_census()->tenuring_threshold(); | ||
|
||
size_t young_evac_reserve = heap->young_generation()->get_evacuation_reserve(); | ||
size_t old_evac_reserve = heap->old_generation()->get_evacuation_reserve(); | ||
size_t max_young_cset = (size_t) (young_evac_reserve / ShenandoahEvacWaste); | ||
size_t young_cur_cset = 0; | ||
size_t max_old_cset = (size_t) (old_evac_reserve / ShenandoahOldEvacWaste); | ||
size_t old_cur_cset = 0; | ||
|
||
// Figure out how many unaffiliated young regions are dedicated to mutator and to evacuator. Allow the young | ||
// collector's unaffiliated regions to be transferred to old-gen if old-gen has more easily reclaimed garbage | ||
// than young-gen. At the end of this cycle, any excess regions remaining in old-gen will be transferred back | ||
// to young. Do not transfer the mutator's unaffiliated regions to old-gen. Those must remain available | ||
// to the mutator as it needs to be able to consume this memory during concurrent GC. | ||
|
||
size_t unaffiliated_young_regions = heap->young_generation()->free_unaffiliated_regions(); | ||
size_t unaffiliated_young_memory = unaffiliated_young_regions * region_size_bytes; | ||
|
||
if (unaffiliated_young_memory > max_young_cset) { | ||
size_t unaffiliated_mutator_memory = unaffiliated_young_memory - max_young_cset; | ||
unaffiliated_young_memory -= unaffiliated_mutator_memory; | ||
unaffiliated_young_regions = unaffiliated_young_memory / region_size_bytes; // round down | ||
unaffiliated_young_memory = unaffiliated_young_regions * region_size_bytes; | ||
size_t unaffiliated_old_regions = heap->old_generation()->free_unaffiliated_regions(); | ||
size_t unaffiliated_old_memory = unaffiliated_old_regions * region_size_bytes; | ||
|
||
// Figure out how many unaffiliated regions are dedicated to Collector and OldCollector reserves. Let these | ||
// be shuffled between young and old generations in order to expedite evacuation of whichever regions have the | ||
// most garbage, regardless of whether these garbage-first regions reside in young or old generation. | ||
// Excess reserves will be transferred back to the mutator after collection set has been chosen. At the end | ||
// of evacuation, any reserves not consumed by evacuation will also be transferred to the mutator free set. | ||
size_t shared_reserve_regions = 0; | ||
if (young_evac_reserve > unaffiliated_young_memory) { | ||
young_evac_reserve -= unaffiliated_young_memory; | ||
shared_reserve_regions += unaffiliated_young_memory / region_size_bytes; | ||
} else { | ||
size_t delta_regions = young_evac_reserve / region_size_bytes; | ||
shared_reserve_regions += delta_regions; | ||
young_evac_reserve -= delta_regions * region_size_bytes; | ||
} | ||
if (old_evac_reserve > unaffiliated_old_memory) { | ||
old_evac_reserve -= unaffiliated_old_memory; | ||
shared_reserve_regions += unaffiliated_old_memory / region_size_bytes; | ||
} else { | ||
size_t delta_regions = old_evac_reserve / region_size_bytes; | ||
shared_reserve_regions += delta_regions; | ||
old_evac_reserve -= delta_regions * region_size_bytes; | ||
} | ||
|
||
// We'll affiliate these unaffiliated regions with either old or young, depending on need. | ||
max_young_cset -= unaffiliated_young_memory; | ||
size_t shared_reserves = shared_reserve_regions * region_size_bytes; | ||
size_t committed_from_shared_reserves = 0; | ||
size_t max_young_cset = (size_t) (young_evac_reserve / ShenandoahEvacWaste); | ||
size_t young_cur_cset = 0; | ||
size_t max_old_cset = (size_t) (old_evac_reserve / ShenandoahOldEvacWaste); | ||
size_t old_cur_cset = 0; | ||
|
||
// Keep track of how many regions we plan to transfer from young to old. | ||
size_t regions_transferred_to_old = 0; | ||
size_t promo_bytes = 0; | ||
size_t old_evac_bytes = 0; | ||
size_t young_evac_bytes = 0; | ||
|
||
size_t free_target = (capacity * ShenandoahMinFreeThreshold) / 100 + max_young_cset; | ||
size_t max_total_cset = (max_young_cset + max_old_cset + | ||
(size_t) (shared_reserve_regions * region_size_bytes) / ShenandoahOldEvacWaste); | ||
size_t free_target = ((young_capacity + old_capacity) * ShenandoahMinFreeThreshold) / 100 + max_total_cset; | ||
size_t min_garbage = (free_target > actual_free) ? (free_target - actual_free) : 0; | ||
|
||
log_info(gc, ergo)("Adaptive CSet Selection for GLOBAL. Max Young Evacuation: %zu" | ||
"%s, Max Old Evacuation: %zu%s, Actual Free: %zu%s.", | ||
"%s, Max Old Evacuation: %zu%s, Discretionary additional evacuation: %zu%s, Actual Free: %zu%s.", | ||
byte_size_in_proper_unit(max_young_cset), proper_unit_for_byte_size(max_young_cset), | ||
byte_size_in_proper_unit(max_old_cset), proper_unit_for_byte_size(max_old_cset), | ||
byte_size_in_proper_unit(shared_reserves), proper_unit_for_byte_size(shared_reserves), | ||
byte_size_in_proper_unit(actual_free), proper_unit_for_byte_size(actual_free)); | ||
|
||
size_t cur_garbage = cur_young_garbage; | ||
for (size_t idx = 0; idx < size; idx++) { | ||
ShenandoahHeapRegion* r = data[idx].get_region(); | ||
assert(!cset->is_preselected(r->index()), "There should be no preselected regions during GLOBAL GC"); | ||
bool add_region = false; | ||
size_t region_garbage = r->garbage(); | ||
size_t new_garbage = cur_garbage + region_garbage; | ||
bool add_regardless = (region_garbage > ignore_threshold) && (new_garbage < min_garbage); | ||
if (r->is_old() || (r->age() >= tenuring_threshold)) { | ||
size_t new_cset = old_cur_cset + r->get_live_data_bytes(); | ||
if ((r->garbage() > garbage_threshold)) { | ||
while ((new_cset > max_old_cset) && (unaffiliated_young_regions > 0)) { | ||
unaffiliated_young_regions--; | ||
regions_transferred_to_old++; | ||
if (add_regardless || (region_garbage > garbage_threshold)) { | ||
size_t live_bytes = r->get_live_data_bytes(); | ||
size_t new_cset = old_cur_cset + r->get_live_data_bytes(); | ||
// May need multiple reserve regions to evacuate a single region, depending on live data bytes and ShenandoahOldEvacWaste | ||
size_t orig_max_old_cset = max_old_cset; | ||
size_t proposed_old_region_consumption = 0; | ||
while ((new_cset > max_old_cset) && (committed_from_shared_reserves < shared_reserves)) { | ||
committed_from_shared_reserves += region_size_bytes; | ||
proposed_old_region_consumption++; | ||
max_old_cset += region_size_bytes / ShenandoahOldEvacWaste; | ||
} | ||
} | ||
if ((new_cset <= max_old_cset) && (r->garbage() > garbage_threshold)) { | ||
add_region = true; | ||
old_cur_cset = new_cset; | ||
// We already know: add_regardless || region_garbage > garbage_threshold | ||
if (new_cset <= max_old_cset) { | ||
add_region = true; | ||
old_cur_cset = new_cset; | ||
cur_garbage = new_garbage; | ||
if (r->is_old()) { | ||
old_evac_bytes += live_bytes; | ||
} else { | ||
promo_bytes += live_bytes; | ||
} | ||
} else { | ||
// We failed to sufficiently expand old, so unwind proposed expansion | ||
max_old_cset = orig_max_old_cset; | ||
committed_from_shared_reserves -= proposed_old_region_consumption * region_size_bytes; | ||
} | ||
} | ||
} else { | ||
assert(r->is_young() && (r->age() < tenuring_threshold), "DeMorgan's law (assuming r->is_affiliated)"); | ||
size_t new_cset = young_cur_cset + r->get_live_data_bytes(); | ||
size_t region_garbage = r->garbage(); | ||
size_t new_garbage = cur_young_garbage + region_garbage; | ||
bool add_regardless = (region_garbage > ignore_threshold) && (new_garbage < min_garbage); | ||
|
||
if (add_regardless || (r->garbage() > garbage_threshold)) { | ||
while ((new_cset > max_young_cset) && (unaffiliated_young_regions > 0)) { | ||
unaffiliated_young_regions--; | ||
if (add_regardless || (region_garbage > garbage_threshold)) { | ||
size_t live_bytes = r->get_live_data_bytes(); | ||
size_t new_cset = young_cur_cset + live_bytes; | ||
// May need multiple reserve regions to evacuate a single region, depending on live data bytes and ShenandoahEvacWaste | ||
size_t orig_max_young_cset = max_young_cset; | ||
size_t proposed_young_region_consumption = 0; | ||
while ((new_cset > max_young_cset) && (committed_from_shared_reserves < shared_reserves)) { | ||
committed_from_shared_reserves += region_size_bytes; | ||
proposed_young_region_consumption++; | ||
max_young_cset += region_size_bytes / ShenandoahEvacWaste; | ||
} | ||
} | ||
if ((new_cset <= max_young_cset) && (add_regardless || (region_garbage > garbage_threshold))) { | ||
add_region = true; | ||
young_cur_cset = new_cset; | ||
cur_young_garbage = new_garbage; | ||
// We already know: add_regardless || region_garbage > garbage_threshold | ||
if (new_cset <= max_young_cset) { | ||
add_region = true; | ||
young_cur_cset = new_cset; | ||
cur_garbage = new_garbage; | ||
young_evac_bytes += live_bytes; | ||
} else { | ||
// We failed to sufficiently expand young, so unwind proposed expansion | ||
max_young_cset = orig_max_young_cset; | ||
committed_from_shared_reserves -= proposed_young_region_consumption * region_size_bytes; | ||
} | ||
} | ||
} | ||
if (add_region) { | ||
cset->add_region(r); | ||
} | ||
} | ||
|
||
if (regions_transferred_to_old > 0) { | ||
heap->generation_sizer()->force_transfer_to_old(regions_transferred_to_old); | ||
heap->young_generation()->set_evacuation_reserve(young_evac_reserve - regions_transferred_to_old * region_size_bytes); | ||
heap->old_generation()->set_evacuation_reserve(old_evac_reserve + regions_transferred_to_old * region_size_bytes); | ||
} | ||
heap->young_generation()->set_evacuation_reserve((size_t) (young_evac_bytes * ShenandoahEvacWaste)); | ||
heap->old_generation()->set_evacuation_reserve((size_t) (old_evac_bytes * ShenandoahOldEvacWaste)); | ||
heap->old_generation()->set_promoted_reserve((size_t) (promo_bytes * ShenandoahPromoEvacWaste)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note that the census from the most recent mark provides both these bits of information, but doesn't account for other criteria (i.e. liveness denseness) that go into the exclusion of certain regions (and their objects) from the collection set. Therein lies the rub, but armed with historical numbers of each and reality, one might be able to predict this well (may be). |
||
} |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So we are using the amount to be evacuated out of young (suitably marked up to account for waste) from the collection set of a specific cycle to predict the same for the next cycle? And similarly for the promotion bytes.
This seems reasonable, but how does that compare with using the live data identified in the most recent marking cycle instead? I can imagine that the former is more accurate under steady state assumptions and the latter is an overestimate to the extent that not all live data will be evacuated because it's in mostly live, i.e. densely live regions. However, it would be interesting to see how they compare and which tracks reality better. Since this is in the nature of a prediction/estimate, once can consider a control algorithm that tries to move the estimate closer based on minimizing some historical deviation between marked vs evacuated.
This need not be done here, but can be considered a future enhancement/experiment.