Skip to content
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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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
Copy Markdown
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
Copy Markdown
ContributorAuthor

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
Copy Markdown
ContributorAuthor

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?

@chrisnaschrisnasMay 7, 2023

Copy link
Copy Markdown
ContributorAuthor

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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
The AllocationTick threshold is computed by a Poisson process with a 100 KB mean. by chrisnas · Pull Request #85750 · dotnet/runtime · GitHub
Skip to content
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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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
Copy Markdown
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
Copy Markdown
ContributorAuthor

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
Copy Markdown
ContributorAuthor

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?

@chrisnaschrisnasMay 7, 2023

Copy link
Copy Markdown
ContributorAuthor

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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' The AllocationTick threshold is computed by a Poisson process with a 100 KB mean. by chrisnas · Pull Request #85750 · dotnet/runtime · GitHub
Skip to content
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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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
Copy Markdown
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
Copy Markdown
ContributorAuthor

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
Copy Markdown
ContributorAuthor

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?

@chrisnaschrisnasMay 7, 2023

Copy link
Copy Markdown
ContributorAuthor

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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' The AllocationTick threshold is computed by a Poisson process with a 100 KB mean. by chrisnas · Pull Request #85750 · dotnet/runtime · GitHub
Skip to content
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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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
Copy Markdown
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
Copy Markdown
ContributorAuthor

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
Copy Markdown
ContributorAuthor

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?

@chrisnaschrisnasMay 7, 2023

Copy link
Copy Markdown
ContributorAuthor

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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' The AllocationTick threshold is computed by a Poisson process with a 100 KB mean. by chrisnas · Pull Request #85750 · dotnet/runtime · GitHub
Skip to content
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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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
Copy Markdown
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
Copy Markdown
ContributorAuthor

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
Copy Markdown
ContributorAuthor

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?

@chrisnaschrisnasMay 7, 2023

Copy link
Copy Markdown
ContributorAuthor

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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' The AllocationTick threshold is computed by a Poisson process with a 100 KB mean. by chrisnas · Pull Request #85750 · dotnet/runtime · GitHub
Skip to content
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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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
Copy Markdown
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
Copy Markdown
ContributorAuthor

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
Copy Markdown
ContributorAuthor

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?

@chrisnaschrisnasMay 7, 2023

Copy link
Copy Markdown
ContributorAuthor

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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' The AllocationTick threshold is computed by a Poisson process with a 100 KB mean. by chrisnas · Pull Request #85750 · dotnet/runtime · GitHub
Skip to content
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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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
Copy Markdown
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
Copy Markdown
ContributorAuthor

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
Copy Markdown
ContributorAuthor

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?

@chrisnaschrisnasMay 7, 2023

Copy link
Copy Markdown
ContributorAuthor

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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); The AllocationTick threshold is computed by a Poisson process with a 100 KB mean. by chrisnas · Pull Request #85750 · dotnet/runtime · GitHub
Skip to content
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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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
Copy Markdown
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
Copy Markdown
ContributorAuthor

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
Copy Markdown
ContributorAuthor

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?

@chrisnaschrisnasMay 7, 2023

Copy link
Copy Markdown
ContributorAuthor

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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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