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

The AllocationTick threshold is computed by a Poisson process with a 100 KB mean. #85750

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 98 additions & 8 deletions src/coreclr/gc/gc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
//

#include "gcpriv.h"
#include <math.h>

#if defined(TARGET_AMD64) && defined(TARGET_WINDOWS)
#define USE_VXSORT
Expand Down Expand Up @@ -1892,7 +1893,7 @@ size_t align_on_segment_hard_limit (size_t add)

#endif //SERVER_GC

const size_t etw_allocation_tick = 100*1024;
const size_t etw_allocation_tick_mean = 100*1024;

const size_t low_latency_alloc = 256*1024;

Expand Down Expand Up @@ -2479,7 +2480,10 @@ uint8_t* gc_heap::last_gen1_pin_end;

gen_to_condemn_tuning gc_heap::gen_to_condemn_reasons;

uint64_t gc_heap::etw_allocationTickMode;
size_t gc_heap::etw_allocation_running_amount[total_oh_count];
size_t gc_heap::etw_allocation_running_threshold[total_oh_count];
size_t gc_heap::etw_allocation_next_threshold[total_oh_count];

uint64_t gc_heap::total_alloc_bytes_soh = 0;

Expand Down Expand Up @@ -14423,7 +14427,13 @@ gc_heap::init_gc_heap (int h_number)
heap_number = h_number;
#endif //MULTIPLE_HEAPS

etw_allocationTickMode = GCConfig::GetAllocationTickMode();
memset (etw_allocation_running_amount, 0, sizeof (etw_allocation_running_amount));
for (int i = 0; i < total_oh_count; i++)
{
etw_allocation_running_threshold[i] = etw_allocation_tick_mean;
etw_allocation_next_threshold[i] = etw_allocation_tick_mean;
}
memset (allocated_since_last_gc, 0, sizeof (allocated_since_last_gc));
memset (&oom_info, 0, sizeof (oom_info));
memset (&fgm_result, 0, sizeof (fgm_result));
Expand Down Expand Up @@ -16171,6 +16181,30 @@ size_t gc_heap::limit_from_size (size_t size, uint32_t flags, size_t physical_li
size_t desired_size_to_allocate = max (padded_size, min_size_to_allocate);
size_t new_physical_limit = min (physical_limit, desired_size_to_allocate);

#ifdef FEATURE_EVENT_TRACE
// If the AllocationTick threshold will be reached, check if the next one
// will be within the currently calculated limit.
// In that case, shrink the limit to the next threshold
if (etw_allocationTickMode == 3)
{
#ifdef FEATURE_NATIVEAOT
if (EVENT_ENABLED(GCAllocationTick_V1))
#else
if (EVENT_ENABLED(GCAllocationTick_V4))
#endif
{
if (is_alloc_beyond_threshold(gen_number, size))
{
size_t nextThreshold = get_alloc_next_threshold(gen_number);
if (nextThreshold <= (new_physical_limit - padded_size))
{
new_physical_limit = size + nextThreshold + Align(min_obj_size, align_const);
}
}
}
}
#endif

size_t new_limit = new_allocation_limit (padded_size,
new_physical_limit,
gen_number);
Expand Down Expand Up @@ -18009,20 +18043,75 @@ void gc_heap::trigger_gc_for_alloc (int gen_number, gc_reason gr,
#endif //BACKGROUND_GC
}

inline
size_t gc_heap::compute_alloc_threshold ()
{
size_t threshold = etw_allocation_tick_mean;

// avoid computing if not needed
#ifdef FEATURE_EVENT_TRACE
#ifdef FEATURE_NATIVEAOT
if (EVENT_ENABLED(GCAllocationTick_V1))
#else
if (EVENT_ENABLED(GCAllocationTick_V4))
#endif
{
if ((etw_allocationTickMode == 2) || (etw_allocationTickMode == 3))
{
// compute the next threshold based on a Poisson process with a etw_allocation_tick_mean average
Copy link
Member

Choose a reason for hiding this comment

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

many of us aren't greatly familiar with statistics so making this explanation not so vague would be helpful. instead of saying "based on a Possion process" it'd be much more helpful to start with something like "we are treating this as a Possion process because each sample we take has no influence on any other sample. the samples are exponentially distributed in a Possion process, meaning that the possibility of the next sample happening is calculated by (1 - e^(-lambda*x)). and then explain what lambda and x would be in this particular context so the readers know how the formula you are using came to be.

also -ln (1 - uniformly_random_number_between_0_and_1), is the same as -ln (uniformly_random_number_between_0_and_1). so I don't think you need the 1 - part.

can you please show the results of running this on some workloads where this is much better compared to the current implementation? also have you tried with just a uniformly random distribution instead of an exponential distribution?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

many of us aren't greatly familiar with statistics so making this explanation not so vague would be helpful. instead of saying "based on a Possion process" it'd be much more helpful to start with something like "we are treating this as a Possion process because each sample we take has no influence on any other sample. the samples are exponentially distributed in a Possion process, meaning that the possibility of the next sample happening is calculated by (1 - e^(-lambda*x)). and then explain what lambda and x would be in this particular context so the readers know how the formula you are using came to be.

I updated the description accordingly with also additional information about the upscaling formula

Copy link
Contributor Author

Choose a reason for hiding this comment

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

can you please show the results of running this on some workloads where this is much better compared to the current implementation? also have you tried with just a uniformly random distribution instead of an exponential distribution?

I'm currently simulating the results based on a web application for which I'm recording ALL allocations using ICorProfilerCallback::ObjectAllocated() and check against the sampled then upscaled sizes. The variance of the results shows almost random results for fixed threshold, much better for variable threshold as in the first commit and a little better if sampling could happen within allocation context.
Since the recorder is available in the Datadog profiler only, it will be complicated to generate the corresponding .balloc files (i.e. list of allocations - type+size) used by the simulation to show result on any application. BTW, is there any sample application that you would like to see used as example?

Copy link
Contributor Author

@chrisnas chrisnas May 7, 2023

Choose a reason for hiding this comment

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

also -ln (1 - uniformly_random_number_between_0_and_1), is the same as -ln (uniformly_random_number_between_0_and_1). so I don't think you need the 1 - part.

This sticks to the mathematical way to derive the formula. Since the result should be the same, I would recommend to keep it as it is but no problem to change it.

threshold = (size_t)(-log((double)gc_rand::get_rand(RAND_MAX)/(double)RAND_MAX) * etw_allocation_tick_mean) + 1;
}
else
if (etw_allocationTickMode == 1)
{
// compute the next threshold as mean +/- mean/2 (i.e. from mean/2 + 1 to mean + mean/2)
threshold = (etw_allocation_tick_mean / 2) + (size_t)(gc_rand::get_rand(etw_allocation_tick_mean) + 1);
}

// nothing to do for fixed mode: threshold is defined as 100 KB by default
}
#endif

return threshold;
}

inline
size_t gc_heap::get_alloc_current_threshold (int gen_number)
{
int oh_index = gen_to_oh (gen_number);
return etw_allocation_running_threshold[oh_index];
}

inline
size_t gc_heap::get_alloc_next_threshold (int gen_number)
{
int oh_index = gen_to_oh (gen_number);
return etw_allocation_next_threshold[oh_index];
}

inline
bool gc_heap::is_alloc_beyond_threshold(int gen_number, size_t size)
{
int oh_index = gen_to_oh (gen_number);
return (etw_allocation_running_amount[oh_index] + size > etw_allocation_running_threshold[oh_index]);
}

inline
bool gc_heap::update_alloc_info (int gen_number, size_t allocated_size, size_t* etw_allocation_amount)
{
bool exceeded_p = false;
bool exceeded_p = is_alloc_beyond_threshold(gen_number, allocated_size);

int oh_index = gen_to_oh (gen_number);
allocated_since_last_gc[oh_index] += allocated_size;
etw_allocation_running_amount[oh_index] += allocated_size;

size_t& etw_allocated = etw_allocation_running_amount[oh_index];
etw_allocated += allocated_size;
if (etw_allocated > etw_allocation_tick)
if (exceeded_p)
{
*etw_allocation_amount = etw_allocated;
exceeded_p = true;
etw_allocated = 0;
*etw_allocation_amount = etw_allocation_running_amount[oh_index];
etw_allocation_running_amount[oh_index] = 0;

etw_allocation_running_threshold[oh_index] = etw_allocation_next_threshold[oh_index];
etw_allocation_next_threshold[oh_index] = compute_alloc_threshold();
}

return exceeded_p;
Expand Down Expand Up @@ -46244,6 +46333,7 @@ int StressRNG(int iMaxValue)
int randValue = (((lHoldrand = lHoldrand * 214013L + 2531011L) >> 16) & 0x7fff);
return randValue % iMaxValue;
}

#endif // STRESS_HEAP
#endif // !FEATURE_NATIVEAOT

Expand Down
4 changes: 3 additions & 1 deletion src/coreclr/gc/gcconfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,9 @@ class GCConfigStringHolder
INT_CONFIG (GCConserveMem, "GCConserveMemory", "System.GC.ConserveMemory", 0, "Specifies how hard GC should try to conserve memory - values 0-9") \
INT_CONFIG (GCWriteBarrier, "GCWriteBarrier", NULL, 0, "Specifies whether GC should use more precise but slower write barrier") \
STRING_CONFIG(GCName, "GCName", "System.GC.Name", "Specifies the path of the standalone GC implementation.") \
INT_CONFIG (GCSpinCountUnit, "GCSpinCountUnit", 0, 0, "Specifies the spin count unit used by the GC.")
INT_CONFIG (GCSpinCountUnit, "GCSpinCountUnit", 0, 0, "Specifies the spin count unit used by the GC.") \
INT_CONFIG (AllocationTickMode, "GCAllocationTickMode", "GCAllocationTickMode", 0, "Specifies the AllocationTick mode (0=fixed, 1=variable, 2=Poisson+AC, 3=Poisson in AC")

// This class is responsible for retreiving configuration information
// for how the GC should operate.
class GCConfig
Expand Down
10 changes: 10 additions & 0 deletions src/coreclr/gc/gcpriv.h
Original file line number Diff line number Diff line change
Expand Up @@ -2967,6 +2967,10 @@ class gc_heap
uint64_t* available_page_file=NULL);
PER_HEAP_METHOD size_t generation_size (int gen_number);
PER_HEAP_ISOLATED_METHOD size_t get_total_survived_size();
PER_HEAP_METHOD size_t get_alloc_current_threshold (int gen_number);
PER_HEAP_METHOD size_t get_alloc_next_threshold (int gen_number);
PER_HEAP_METHOD bool is_alloc_beyond_threshold (int gen_number, size_t size);
PER_HEAP_METHOD size_t compute_alloc_threshold ();
PER_HEAP_METHOD bool update_alloc_info (int gen_number,
size_t allocated_size,
size_t* etw_allocation_amount);
Expand Down Expand Up @@ -3853,7 +3857,13 @@ class gc_heap
#endif //HEAP_ANALYZE

PER_HEAP_FIELD_DIAG_ONLY gen_to_condemn_tuning gen_to_condemn_reasons;
PER_HEAP_FIELD_DIAG_ONLY uint64_t etw_allocationTickMode;
PER_HEAP_FIELD_DIAG_ONLY size_t etw_allocation_running_amount[total_oh_count];
// it is needed to know what will be the next threshold after the running one
// to compute the limit of an allocation context: if it is smaller than this
// next threshold, then the limit is adjusted to the next threshold
PER_HEAP_FIELD_DIAG_ONLY size_t etw_allocation_running_threshold[total_oh_count];
PER_HEAP_FIELD_DIAG_ONLY size_t etw_allocation_next_threshold[total_oh_count];
PER_HEAP_FIELD_DIAG_ONLY uint64_t total_alloc_bytes_soh;
PER_HEAP_FIELD_DIAG_ONLY uint64_t total_alloc_bytes_uoh;

Expand Down