Skip to content

Commit

Permalink
8311883: [Genshen] Adaptive tenuring threshold
Browse files Browse the repository at this point in the history
Generational Shenandoah currently has the notion of a tenuring threshold but it isn't dynamically adapted, but rather kept fixed at 7.

We now adapt the tenuring threshold based on object demographics as determined by a recent GC that visits objects in the young generation.

We keep track of age-cohort populations at each minor GC epoch and use the historical data to determine if it would be a good idea to tenure or not based on measured mortality rates. A few tunable (experimental) knobs are exposed to play with these to determine some good settings in the future.

The object census is conducted by default at marking, and is subject to noise on account of objects whose age could not be determined because of displaced header. In this case, the computed tenuring threshold is used in the following evacuation of the same cycle. Optionally, the census can be conducted at evacuation time, but sees only objects that are in the collection set. In this case, the tenuring threshold that is computed is used for tenuring decisions in the next evacuation cycle.

Other variants are possible, and may be implemented / tested in the future as opportunity and data permit.

The computed tenuring threshold has not yet been coupled with size budgeting, but will be in a followup PR.

At this time, performance measurements have not shown any benefit, but we believe that with the framework now in place, we may be able to find an adaptive tenuring algorithm that works better than the current one and provide performance benefits. Adaptive tenuring is enabled by default, but can be optionally disabled to mimic previous behavior.

Reviewed-by: kdnilsen
  • Loading branch information
Y. Srinivas Ramakrishna committed Aug 18, 2023
1 parent 867f558 commit ef4b453
Show file tree
Hide file tree
Showing 30 changed files with 949 additions and 118 deletions.
10 changes: 10 additions & 0 deletions src/hotspot/share/gc/shared/ageTable.cpp
Expand Up @@ -71,6 +71,16 @@ void AgeTable::clear() {
}
}

#ifndef PRODUCT
bool AgeTable::is_clear() {
size_t total = 0;
for (size_t* p = sizes; p < sizes + table_size; ++p) {
total += *p;
}
return total == 0;
}
#endif // !PRODUCT

void AgeTable::merge(const AgeTable* subTable) {
for (int i = 0; i < table_size; i++) {
sizes[i]+= subTable->sizes[i];
Expand Down
10 changes: 6 additions & 4 deletions src/hotspot/share/gc/shared/ageTable.hpp
Expand Up @@ -25,6 +25,7 @@
#ifndef SHARE_GC_SHARED_AGETABLE_HPP
#define SHARE_GC_SHARED_AGETABLE_HPP

#include "memory/allocation.hpp"
#include "oops/markWord.hpp"
#include "oops/oop.hpp"
#include "runtime/perfDataTypes.hpp"
Expand All @@ -36,7 +37,7 @@
//
// Note: all sizes are in oops

class AgeTable {
class AgeTable: public CHeapObj<mtGC> {
friend class VMStructs;

public:
Expand All @@ -52,17 +53,18 @@ class AgeTable {

// clear table
void clear();
// check whether it's clear
bool is_clear() PRODUCT_RETURN0;

// add entry
inline void add(oop p, size_t oop_size);

void add(uint age, size_t oop_size) {
assert(age > 0 && age < table_size, "invalid age of object");
assert(age < table_size, "invalid age of object");
sizes[age] += oop_size;
}

// Merge another age table with the current one. Used
// for parallel young generation gc.
// Merge another age table with the current one.
void merge(const AgeTable* subTable);

// Calculate new tenuring threshold based on age information.
Expand Down
Expand Up @@ -64,6 +64,8 @@ void ShenandoahGenerationalHeuristics::choose_collection_set(ShenandoahCollectio
size_t free = 0;
size_t free_regions = 0;

const uint tenuring_threshold = heap->age_census()->tenuring_threshold();

// This counts number of humongous regions that we intend to promote in this cycle.
size_t humongous_regions_promoted = 0;
// This counts bytes of memory used by humongous regions to be promoted in place.
Expand All @@ -82,7 +84,7 @@ void ShenandoahGenerationalHeuristics::choose_collection_set(ShenandoahCollectio
total_garbage += garbage;
if (region->is_empty()) {
free_regions++;
free += ShenandoahHeapRegion::region_size_bytes();
free += region_size_bytes;
} else if (region->is_regular()) {
if (!region->has_live()) {
// We can recycle it right away and put it in the free set.
Expand All @@ -93,13 +95,12 @@ void ShenandoahGenerationalHeuristics::choose_collection_set(ShenandoahCollectio
bool is_candidate;
// This is our candidate for later consideration.
if (collection_set->is_preselected(i)) {
// If !is_generational, we cannot ask if is_preselected. If is_preselected, we know
// region->age() >= InitialTenuringThreshold).
assert(region->age() >= tenuring_threshold, "Preselection filter");
is_candidate = true;
preselected_candidates++;
// Set garbage value to maximum value to force this into the sorted collection set.
garbage = region_size_bytes;
} else if (region->is_young() && (region->age() >= InitialTenuringThreshold)) {
} else if (region->is_young() && (region->age() >= tenuring_threshold)) {
// Note that for GLOBAL GC, region may be OLD, and OLD regions do not qualify for pre-selection

// This region is old enough to be promoted but it was not preselected, either because its garbage is below
Expand Down Expand Up @@ -136,7 +137,7 @@ void ShenandoahGenerationalHeuristics::choose_collection_set(ShenandoahCollectio
immediate_regions++;
immediate_garbage += garbage;
} else {
if (region->is_young() && region->age() >= InitialTenuringThreshold) {
if (region->is_young() && region->age() >= tenuring_threshold) {
oop obj = cast_to_oop(region->bottom());
size_t humongous_regions = ShenandoahHeapRegion::required_regions(obj->size() * HeapWordSize);
humongous_regions_promoted += humongous_regions;
Expand Down Expand Up @@ -230,14 +231,18 @@ void ShenandoahGenerationalHeuristics::choose_collection_set(ShenandoahCollectio
size_t ShenandoahGenerationalHeuristics::add_preselected_regions_to_collection_set(ShenandoahCollectionSet* cset,
const RegionData* data,
size_t size) const {
#ifdef ASSERT
const uint tenuring_threshold = ShenandoahHeap::heap()->age_census()->tenuring_threshold();
#endif

// cur_young_garbage represents the amount of memory to be reclaimed from young-gen. In the case that live objects
// are known to be promoted out of young-gen, we count this as cur_young_garbage because this memory is reclaimed
// from young-gen and becomes available to serve future young-gen allocation requests.
size_t cur_young_garbage = 0;
for (size_t idx = 0; idx < size; idx++) {
ShenandoahHeapRegion* r = data[idx]._region;
if (cset->is_preselected(r->index())) {
assert(r->age() >= InitialTenuringThreshold, "Preselected regions must have tenure age");
assert(r->age() >= tenuring_threshold, "Preselected regions must have tenure age");
// Entire region will be promoted, This region does not impact young-gen or old-gen evacuation reserve.
// This region has been pre-selected and its impact on promotion reserve is already accounted for.

Expand Down
Expand Up @@ -83,6 +83,7 @@ void ShenandoahGlobalHeuristics::choose_global_collection_set(ShenandoahCollecti
size_t capacity = heap->young_generation()->max_capacity();
size_t garbage_threshold = ShenandoahHeapRegion::region_size_bytes() * ShenandoahGarbageThreshold / 100;
size_t ignore_threshold = ShenandoahHeapRegion::region_size_bytes() * ShenandoahIgnoreGarbageThreshold / 100;
const uint tenuring_threshold = heap->age_census()->tenuring_threshold();

size_t max_young_cset = (size_t) (heap->get_young_evac_reserve() / ShenandoahEvacWaste);
size_t young_cur_cset = 0;
Expand All @@ -109,7 +110,7 @@ void ShenandoahGlobalHeuristics::choose_global_collection_set(ShenandoahCollecti
add_region = true;
old_cur_cset = new_cset;
}
} else if (r->age() < InitialTenuringThreshold) {
} else if (r->age() < tenuring_threshold) {
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;
Expand Down
Expand Up @@ -84,6 +84,7 @@ void ShenandoahYoungHeuristics::choose_young_collection_set(ShenandoahCollection
size_t capacity = heap->young_generation()->max_capacity();
size_t garbage_threshold = ShenandoahHeapRegion::region_size_bytes() * ShenandoahGarbageThreshold / 100;
size_t ignore_threshold = ShenandoahHeapRegion::region_size_bytes() * ShenandoahIgnoreGarbageThreshold / 100;
const uint tenuring_threshold = heap->age_census()->tenuring_threshold();

// This is young-gen collection or a mixed evacuation.
// If this is mixed evacuation, the old-gen candidate regions have already been added.
Expand All @@ -92,6 +93,7 @@ void ShenandoahYoungHeuristics::choose_young_collection_set(ShenandoahCollection
size_t free_target = (capacity * ShenandoahMinFreeThreshold) / 100 + max_cset;
size_t min_garbage = (free_target > actual_free) ? (free_target - actual_free) : 0;


log_info(gc, ergo)(
"Adaptive CSet Selection for YOUNG. Max Evacuation: " SIZE_FORMAT "%s, Actual Free: " SIZE_FORMAT "%s.",
byte_size_in_proper_unit(max_cset), proper_unit_for_byte_size(max_cset),
Expand All @@ -102,7 +104,7 @@ void ShenandoahYoungHeuristics::choose_young_collection_set(ShenandoahCollection
if (cset->is_preselected(r->index())) {
continue;
}
if (r->age() < InitialTenuringThreshold) {
if (r->age() < tenuring_threshold) {
size_t new_cset = cur_cset + r->get_live_data_bytes();
size_t region_garbage = r->garbage();
size_t new_garbage = cur_young_garbage + region_garbage;
Expand Down

0 comments on commit ef4b453

Please sign in to comment.