Skip to content

Commit 626ad32

Browse files
authored
Add GC heap hard limit for 32 bit (#101024)
This change enables heap hard limit on 32 bit. Approach is similar to `DOTNET_GCSegmentSize` (`GCConfig::GetSegmentSize`), which allows to set size of segment for SOH/LOH/POH, and guarantees that there's no oveflow during computations (for example, during `size_t initial_heap_size = soh_segment_size + loh_segment_size + poh_segment_size;`). When `DOTNET_GCSegmentSize` is set on 32bit, it's rounded down to power of 2, so largest possible value of provided segment (SOH) is 2 Gb (`4Mb<=soh_segment_size<=2Gb`). For LOH/POH same value is divided by 2 and then also rounded down to power of 2, so largest possible value of LOH/POH segment is 1 Gb (`4Mb<=loh_segment_size<=1Gb, 0<=poh_segment_size<=1Gb`). So, segment size for SOH/LOH/POH never overflows, as well as `initial_heap_size` (except for oveflow of `initial_heap_size` to 0, which will lead to failed allocation later anyway). Similar thing happens when `DOTNET_GCHeapHardLimit` or `DOTNET_GCHeapHardLimitSOH`/`DOTNET_GCHeapHardLimitLOH`/`DOTNET_GCHeapHardLimitPOH` are set on 32bit. There're limits on these values: 1) for heap-specific limits: `0 <= (heap_hard_limit = heap_hard_limit_oh[soh] + heap_hard_limit_oh[loh] + heap_hard_limit_oh[poh]) < 4Gb` `a) 0 <= heap_hard_limit_oh[soh] < 2Gb, 0 <= heap_hard_limit_oh[loh] <= 1Gb, 0 <= heap_hard_limit_oh[poh] <= 1Gb` `b) 0 <= heap_hard_limit_oh[soh] <= 1Gb, 0 <= heap_hard_limit_oh[loh] < 2Gb, 0 <= heap_hard_limit_oh[poh] <= 1Gb` `c) 0 <= heap_hard_limit_oh[soh] <= 1Gb, 0 <= heap_hard_limit_oh[loh] <= 1Gb, 0 <= heap_hard_limit_oh[poh] < 2Gb` 2) for same limit for all heaps: `0 <= heap_hard_limit <= 1Gb` These ranges guarantee that calculation of soh_segment_size, loh_segment_size and poh_segment_size with alignment and round up won't overflow, as well as calculation of sum of them for allocation (overflow to 0 is allowed, same as for `DOTNET_GCSegmentSize`). When values specified by user with env variables don't meet the requirements above, runtime exits with `CLR_E_GC_BAD_HARD_LIMIT`. When allocation (with `mmap` on Linux) fails, runtime exits with same error as for large segment size specified with `DOTNET_GCSegmentSize`. This patch doesn't enable heap hard limit on 32bit in containers (`is_restricted_physical_mem`), because current heap hard limit approach is to reserve one large GC heap segment with size equal to specified heap hard limit, and no new segments are reserved in future. On 64 bit in containers by default heap hard limit is set to 75% of total physical memory available, and this is fine, because virtual address space on 64 bit is much larger than actual physical size on devices. In contrast, on 32 bit virtual address space size might be the same as available physical size on device (e.g. 4 Gb for both). This means that reserving 75% of total physical mem will reserve 75% of whole virtual address space, which might be both undesirable (e.g. process later expects more available memory) and `mmap` on Linux will probably fail anyway. I've ran release CLR tests on armel Linux with this patch on top of f84d33 with different limits, there seems to be no issues with 4Mb/8Mb/16Mb and 512Mb heap hard limits (some tests fail with "Out of memory"/"OOM" and "System.OutOfMemoryException", as expected, or "System.InvalidOperationException: NoGCRegion mode must be set" when NoGC region is larger than limit). Same for GCStresss 0xc and 0x3 with 4 Mb heap hard limit, and same for debug CLR tests on armel Linux with 4Mb heap hard limit. @Maoni0 @cshung please share what you think. Thank you.
1 parent 7ff1082 commit 626ad32

File tree

2 files changed

+128
-17
lines changed

2 files changed

+128
-17
lines changed

src/coreclr/gc/gc.cpp

Lines changed: 126 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1989,7 +1989,16 @@ uint8_t* gc_heap::pad_for_alignment_large (uint8_t* newAlloc, int requiredAlignm
19891989
#endif //BACKGROUND_GC && !USE_REGIONS
19901990

19911991
// This is always power of 2.
1992+
#ifdef HOST_64BIT
19921993
const size_t min_segment_size_hard_limit = 1024*1024*16;
1994+
#else //HOST_64BIT
1995+
const size_t min_segment_size_hard_limit = 1024*1024*4;
1996+
#endif //HOST_64BIT
1997+
1998+
#ifndef HOST_64BIT
1999+
// Max size of heap hard limit (2^31) to be able to be aligned and rounded up on power of 2 and not overflow
2000+
const size_t max_heap_hard_limit = (size_t)2 * (size_t)1024 * (size_t)1024 * (size_t)1024;
2001+
#endif //!HOST_64BIT
19932002

19942003
inline
19952004
size_t align_on_segment_hard_limit (size_t add)
@@ -7442,9 +7451,6 @@ bool gc_heap::virtual_commit (void* address, size_t size, int bucket, int h_numb
74427451
*
74437452
* Note : We never commit into free directly, so bucket != recorded_committed_free_bucket
74447453
*/
7445-
#ifndef HOST_64BIT
7446-
assert (heap_hard_limit == 0);
7447-
#endif //!HOST_64BIT
74487454

74497455
assert(0 <= bucket && bucket < recorded_committed_bucket_counts);
74507456
assert(bucket < total_oh_count || h_number == -1);
@@ -7587,9 +7593,6 @@ bool gc_heap::virtual_decommit (void* address, size_t size, int bucket, int h_nu
75877593
* Case 2: This is for bookkeeping - the bucket will be recorded_committed_bookkeeping_bucket, and the h_number will be -1
75887594
* Case 3: This is for free - the bucket will be recorded_committed_free_bucket, and the h_number will be -1
75897595
*/
7590-
#ifndef HOST_64BIT
7591-
assert (heap_hard_limit == 0);
7592-
#endif //!HOST_64BIT
75937596

75947597
bool decommit_succeeded_p = ((bucket != recorded_committed_bookkeeping_bucket) && use_large_pages_p) ? true : GCToOSInterface::VirtualDecommit (address, size);
75957598

@@ -14488,6 +14491,11 @@ HRESULT gc_heap::initialize_gc (size_t soh_segment_size,
1448814491
return E_OUTOFMEMORY;
1448914492
if (use_large_pages_p)
1449014493
{
14494+
#ifndef HOST_64BIT
14495+
// Large pages are not supported on 32bit
14496+
assert (false);
14497+
#endif //!HOST_64BIT
14498+
1449114499
if (heap_hard_limit_oh[soh])
1449214500
{
1449314501
heap_hard_limit_oh[soh] = soh_segment_size * number_of_heaps;
@@ -21128,12 +21136,12 @@ int gc_heap::joined_generation_to_condemn (BOOL should_evaluate_elevation,
2112821136
gc_data_global.gen_to_condemn_reasons.set_condition(gen_joined_limit_before_oom);
2112921137
full_compact_gc_p = true;
2113021138
}
21131-
else if ((current_total_committed * 10) >= (heap_hard_limit * 9))
21139+
else if (((uint64_t)current_total_committed * (uint64_t)10) >= ((uint64_t)heap_hard_limit * (uint64_t)9))
2113221140
{
2113321141
size_t loh_frag = get_total_gen_fragmentation (loh_generation);
2113421142

2113521143
// If the LOH frag is >= 1/8 it's worth compacting it
21136-
if ((loh_frag * 8) >= heap_hard_limit)
21144+
if (loh_frag >= heap_hard_limit / 8)
2113721145
{
2113821146
dprintf (GTC_LOG, ("loh frag: %zd > 1/8 of limit %zd", loh_frag, (heap_hard_limit / 8)));
2113921147
gc_data_global.gen_to_condemn_reasons.set_condition(gen_joined_limit_loh_frag);
@@ -21144,7 +21152,7 @@ int gc_heap::joined_generation_to_condemn (BOOL should_evaluate_elevation,
2114421152
// If there's not much fragmentation but it looks like it'll be productive to
2114521153
// collect LOH, do that.
2114621154
size_t est_loh_reclaim = get_total_gen_estimated_reclaim (loh_generation);
21147-
if ((est_loh_reclaim * 8) >= heap_hard_limit)
21155+
if (est_loh_reclaim >= heap_hard_limit / 8)
2114821156
{
2114921157
gc_data_global.gen_to_condemn_reasons.set_condition(gen_joined_limit_loh_reclaim);
2115021158
full_compact_gc_p = true;
@@ -44159,6 +44167,15 @@ void gc_heap::init_static_data()
4415944167
);
4416044168
#endif //MULTIPLE_HEAPS
4416144169

44170+
#ifndef HOST_64BIT
44171+
if (heap_hard_limit)
44172+
{
44173+
size_t gen1_max_size_seg = soh_segment_size / 2;
44174+
dprintf (GTC_LOG, ("limit gen1 max %zd->%zd", gen1_max_size, gen1_max_size_seg));
44175+
gen1_max_size = min (gen1_max_size, gen1_max_size_seg);
44176+
}
44177+
#endif //!HOST_64BIT
44178+
4416244179
size_t gen1_max_size_config = (size_t)GCConfig::GetGCGen1MaxBudget();
4416344180

4416444181
if (gen1_max_size_config)
@@ -49291,6 +49308,11 @@ HRESULT GCHeap::Initialize()
4929149308
{
4929249309
if (gc_heap::heap_hard_limit)
4929349310
{
49311+
#ifndef HOST_64BIT
49312+
// Regions are not supported on 32bit
49313+
assert(false);
49314+
#endif //!HOST_64BIT
49315+
4929449316
if (gc_heap::heap_hard_limit_oh[soh])
4929549317
{
4929649318
gc_heap::regions_range = gc_heap::heap_hard_limit;
@@ -49332,12 +49354,32 @@ HRESULT GCHeap::Initialize()
4933249354
{
4933349355
if (gc_heap::heap_hard_limit_oh[soh])
4933449356
{
49357+
// On 32bit we have next guarantees:
49358+
// 0 <= seg_size_from_config <= 1Gb (from max_heap_hard_limit/2)
49359+
// 0 <= (heap_hard_limit = heap_hard_limit_oh[soh] + heap_hard_limit_oh[loh] + heap_hard_limit_oh[poh]) < 4Gb (from gc_heap::compute_hard_limit_from_heap_limits)
49360+
// 0 <= heap_hard_limit_oh[loh] <= 1Gb or < 2Gb
49361+
// 0 <= heap_hard_limit_oh[poh] <= 1Gb or < 2Gb
49362+
// 0 <= large_seg_size <= 1Gb or <= 2Gb (alignment and round up)
49363+
// 0 <= pin_seg_size <= 1Gb or <= 2Gb (alignment and round up)
49364+
// 0 <= soh_segment_size + large_seg_size + pin_seg_size <= 4Gb
49365+
// 4Gb overflow is ok, because 0 size allocation will fail
4933549366
large_seg_size = max (gc_heap::adjust_segment_size_hard_limit (gc_heap::heap_hard_limit_oh[loh], nhp), seg_size_from_config);
4933649367
pin_seg_size = max (gc_heap::adjust_segment_size_hard_limit (gc_heap::heap_hard_limit_oh[poh], nhp), seg_size_from_config);
4933749368
}
4933849369
else
4933949370
{
49371+
// On 32bit we have next guarantees:
49372+
// 0 <= heap_hard_limit <= 1Gb (from gc_heap::compute_hard_limit)
49373+
// 0 <= soh_segment_size <= 1Gb
49374+
// 0 <= large_seg_size <= 1Gb
49375+
// 0 <= pin_seg_size <= 1Gb
49376+
// 0 <= soh_segment_size + large_seg_size + pin_seg_size <= 3Gb
49377+
#ifdef HOST_64BIT
4934049378
large_seg_size = gc_heap::use_large_pages_p ? gc_heap::soh_segment_size : gc_heap::soh_segment_size * 2;
49379+
#else //HOST_64BIT
49380+
assert (!gc_heap::use_large_pages_p);
49381+
large_seg_size = gc_heap::soh_segment_size;
49382+
#endif //HOST_64BIT
4934149383
pin_seg_size = large_seg_size;
4934249384
}
4934349385
if (gc_heap::use_large_pages_p)
@@ -53679,16 +53721,45 @@ int GCHeap::RefreshMemoryLimit()
5367953721
return gc_heap::refresh_memory_limit();
5368053722
}
5368153723

53724+
bool gc_heap::compute_hard_limit_from_heap_limits()
53725+
{
53726+
#ifndef HOST_64BIT
53727+
// need to consider overflows:
53728+
if (! ((heap_hard_limit_oh[soh] < max_heap_hard_limit && heap_hard_limit_oh[loh] <= max_heap_hard_limit / 2 && heap_hard_limit_oh[poh] <= max_heap_hard_limit / 2)
53729+
|| (heap_hard_limit_oh[soh] <= max_heap_hard_limit / 2 && heap_hard_limit_oh[loh] < max_heap_hard_limit && heap_hard_limit_oh[poh] <= max_heap_hard_limit / 2)
53730+
|| (heap_hard_limit_oh[soh] <= max_heap_hard_limit / 2 && heap_hard_limit_oh[loh] <= max_heap_hard_limit / 2 && heap_hard_limit_oh[poh] < max_heap_hard_limit)))
53731+
{
53732+
return false;
53733+
}
53734+
#endif //!HOST_64BIT
53735+
53736+
heap_hard_limit = heap_hard_limit_oh[soh] + heap_hard_limit_oh[loh] + heap_hard_limit_oh[poh];
53737+
return true;
53738+
}
53739+
53740+
// On 32bit we have next guarantees for limits:
53741+
// 1) heap-specific limits:
53742+
// 0 <= (heap_hard_limit = heap_hard_limit_oh[soh] + heap_hard_limit_oh[loh] + heap_hard_limit_oh[poh]) < 4Gb
53743+
// a) 0 <= heap_hard_limit_oh[soh] < 2Gb, 0 <= heap_hard_limit_oh[loh] <= 1Gb, 0 <= heap_hard_limit_oh[poh] <= 1Gb
53744+
// b) 0 <= heap_hard_limit_oh[soh] <= 1Gb, 0 <= heap_hard_limit_oh[loh] < 2Gb, 0 <= heap_hard_limit_oh[poh] <= 1Gb
53745+
// c) 0 <= heap_hard_limit_oh[soh] <= 1Gb, 0 <= heap_hard_limit_oh[loh] <= 1Gb, 0 <= heap_hard_limit_oh[poh] < 2Gb
53746+
// 2) same limit for all heaps:
53747+
// 0 <= heap_hard_limit <= 1Gb
53748+
//
53749+
// These ranges guarantee that calculation of soh_segment_size, loh_segment_size and poh_segment_size with alignment and round up won't overflow,
53750+
// as well as calculation of sum of them (overflow to 0 is allowed, because allocation with 0 size will fail later).
5368253751
bool gc_heap::compute_hard_limit()
5368353752
{
5368453753
heap_hard_limit_oh[soh] = 0;
53685-
#ifdef HOST_64BIT
53754+
5368653755
heap_hard_limit = (size_t)GCConfig::GetGCHeapHardLimit();
5368753756
heap_hard_limit_oh[soh] = (size_t)GCConfig::GetGCHeapHardLimitSOH();
5368853757
heap_hard_limit_oh[loh] = (size_t)GCConfig::GetGCHeapHardLimitLOH();
5368953758
heap_hard_limit_oh[poh] = (size_t)GCConfig::GetGCHeapHardLimitPOH();
5369053759

53760+
#ifdef HOST_64BIT
5369153761
use_large_pages_p = GCConfig::GetGCLargePages();
53762+
#endif //HOST_64BIT
5369253763

5369353764
if (heap_hard_limit_oh[soh] || heap_hard_limit_oh[loh] || heap_hard_limit_oh[poh])
5369453765
{
@@ -53700,8 +53771,10 @@ bool gc_heap::compute_hard_limit()
5370053771
{
5370153772
return false;
5370253773
}
53703-
heap_hard_limit = heap_hard_limit_oh[soh] +
53704-
heap_hard_limit_oh[loh] + heap_hard_limit_oh[poh];
53774+
if (!compute_hard_limit_from_heap_limits())
53775+
{
53776+
return false;
53777+
}
5370553778
}
5370653779
else
5370753780
{
@@ -53729,9 +53802,22 @@ bool gc_heap::compute_hard_limit()
5372953802
heap_hard_limit_oh[soh] = (size_t)(total_physical_mem * (uint64_t)percent_of_mem_soh / (uint64_t)100);
5373053803
heap_hard_limit_oh[loh] = (size_t)(total_physical_mem * (uint64_t)percent_of_mem_loh / (uint64_t)100);
5373153804
heap_hard_limit_oh[poh] = (size_t)(total_physical_mem * (uint64_t)percent_of_mem_poh / (uint64_t)100);
53732-
heap_hard_limit = heap_hard_limit_oh[soh] +
53733-
heap_hard_limit_oh[loh] + heap_hard_limit_oh[poh];
53805+
53806+
if (!compute_hard_limit_from_heap_limits())
53807+
{
53808+
return false;
53809+
}
5373453810
}
53811+
#ifndef HOST_64BIT
53812+
else
53813+
{
53814+
// need to consider overflows
53815+
if (heap_hard_limit > max_heap_hard_limit / 2)
53816+
{
53817+
return false;
53818+
}
53819+
}
53820+
#endif //!HOST_64BIT
5373553821
}
5373653822

5373753823
if (heap_hard_limit_oh[soh] && (!heap_hard_limit_oh[poh]) && (!use_large_pages_p))
@@ -53745,9 +53831,17 @@ bool gc_heap::compute_hard_limit()
5374553831
if ((percent_of_mem > 0) && (percent_of_mem < 100))
5374653832
{
5374753833
heap_hard_limit = (size_t)(total_physical_mem * (uint64_t)percent_of_mem / (uint64_t)100);
53834+
53835+
#ifndef HOST_64BIT
53836+
// need to consider overflows
53837+
if (heap_hard_limit > max_heap_hard_limit / 2)
53838+
{
53839+
return false;
53840+
}
53841+
#endif //!HOST_64BIT
5374853842
}
5374953843
}
53750-
#endif //HOST_64BIT
53844+
5375153845
return true;
5375253846
}
5375353847

@@ -53772,12 +53866,12 @@ bool gc_heap::compute_memory_settings(bool is_initialization, uint32_t& nhp, uin
5377253866
}
5377353867
}
5377453868
}
53869+
#endif //HOST_64BIT
5377553870

5377653871
if (heap_hard_limit && (heap_hard_limit < new_current_total_committed))
5377753872
{
5377853873
return false;
5377953874
}
53780-
#endif //HOST_64BIT
5378153875

5378253876
#ifdef USE_REGIONS
5378353877
{
@@ -53796,9 +53890,24 @@ bool gc_heap::compute_memory_settings(bool is_initialization, uint32_t& nhp, uin
5379653890
seg_size_from_config = (size_t)GCConfig::GetSegmentSize();
5379753891
if (seg_size_from_config)
5379853892
{
53799-
seg_size_from_config = adjust_segment_size_hard_limit_va (seg_size_from_config);
53893+
seg_size_from_config = use_large_pages_p ? align_on_segment_hard_limit (seg_size_from_config) :
53894+
#ifdef HOST_64BIT
53895+
round_up_power2 (seg_size_from_config);
53896+
#else //HOST_64BIT
53897+
round_down_power2 (seg_size_from_config);
53898+
seg_size_from_config = min (seg_size_from_config, max_heap_hard_limit / 2);
53899+
#endif //HOST_64BIT
5380053900
}
5380153901

53902+
// On 32bit we have next guarantees:
53903+
// 0 <= seg_size_from_config <= 1Gb (from max_heap_hard_limit/2)
53904+
// a) heap-specific limits:
53905+
// 0 <= (heap_hard_limit = heap_hard_limit_oh[soh] + heap_hard_limit_oh[loh] + heap_hard_limit_oh[poh]) < 4Gb (from gc_heap::compute_hard_limit_from_heap_limits)
53906+
// 0 <= heap_hard_limit_oh[soh] <= 1Gb or < 2Gb
53907+
// 0 <= soh_segment_size <= 1Gb or <= 2Gb (alignment and round up)
53908+
// b) same limit for all heaps:
53909+
// 0 <= heap_hard_limit <= 1Gb
53910+
// 0 <= soh_segment_size <= 1Gb
5380253911
size_t limit_to_check = (heap_hard_limit_oh[soh] ? heap_hard_limit_oh[soh] : heap_hard_limit);
5380353912
soh_segment_size = max (adjust_segment_size_hard_limit (limit_to_check, nhp), seg_size_from_config);
5380453913
}

src/coreclr/gc/gcpriv.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3476,6 +3476,8 @@ class gc_heap
34763476

34773477
PER_HEAP_ISOLATED_METHOD BOOL dt_high_memory_load_p();
34783478

3479+
PER_HEAP_ISOLATED_METHOD bool compute_hard_limit_from_heap_limits();
3480+
34793481
PER_HEAP_ISOLATED_METHOD bool compute_hard_limit();
34803482

34813483
PER_HEAP_ISOLATED_METHOD bool compute_memory_settings(bool is_initialization, uint32_t& nhp, uint32_t nhp_from_config, size_t& seg_size_from_config,

0 commit comments

Comments
 (0)