点击上方蓝字 江湖评谈设为关注/星标
前言
之前(.NET9 Pre7 DATAS+Rustc Compile线程续)提到过DATAS的一些概念,本篇简析下它的原理。
.NET9 PreView7 中引入的动态适应应用程序大小(Dynamic Adaptation To Application Sizes,简称DATAS)功能。DATAS旨在根据应用程序的内存需求自动调整堆大小,使其与长期存活数据的大小大致成正比。它不同于SVR GC的呆板僵硬,它对于每个不同机器的不同配置分配的堆是不一样的。
详细
来看下它的原理,实际上所有的dotnet性能上的优化都可以通过配置文件进行解析和设置的。
DATAS的配置文件如下:
INT_CONFIG (GCDynamicAdaptationMode,"GCDynamicAdaptationMode", "System.GC.DynamicAdaptationMode",1, "Enable the GC to dynamically adapt to application sizes.")
INT_CONFIG
#define INT_CONFIG(name, unused_private_key, unused_public_key, default, unused_doc) \
int64_t GCConfig::Get##name() { return s_##name; } \
int64_t GCConfig::Get##name(int64_t defaultValue) \
{ \
return s_##name##Provided ? s_##name : defaultValue; \
} \
void GCConfig::Set##name(int64_t value) { s_Updated##name = value; } \
int64_t GCConfig::s_##name = default; \
bool GCConfig::s_##name##Provided = false; \
int64_t GCConfig::s_Updated##name = default;
我们看到它的default即是1.
这里其实可以扩展下,9 PreView7默认开启了,如果想要禁用:
export DOTNET_GCDynamicAdaptationMode=0
也即是上面的DATAS的配置文件加上dotnet_头即可形成环境变量的控制,比如HeapCount,它的配置如下:
INT_CONFIG (HeapCount,"GCHeapCount","System.GC.HeapCount",0,"Specfies the number of server GC heaps")
我们需要为某个应用程序指定12个堆,可以如下设置:
SET DOTNET_GCHeapCount=0xC
它的DOTNET_前缀添加的是配置文件的第二项,这点需要注意。
下面看下DATAS的实际上的某一个应用,如何控制大对象的压缩:
HRESULT gc_heap::initialize_gc (size_t soh_segment_size,
size_t loh_segment_size,
size_t poh_segment_size
#ifdef MULTIPLE_HEAPS
,int number_of_heaps
#endif //MULTIPLE_HEAPS
)
{
#ifdef DYNAMIC_HEAP_COUNT
dynamic_adaptation_mode = (int)GCConfig::GetGCDynamicAdaptationMode();
if (GCConfig::GetHeapCount() != 0)
{
dynamic_adaptation_mode = 0;
}
if ((dynamic_adaptation_mode == dynamic_adaptation_to_application_sizes) && (conserve_mem_setting == 0))
conserve_mem_setting = 5;
#endif //DYNAMIC_HEAP_COUNT
}
当在进行GC初始化的时候,获取到DATAS的默认值(9 PreView7默认开启为1),如果同时设置了GC_Heap,则可以通过相应的dynamic_adaptation_mode值设置conserve_mem_setting 。conserve_mem_setting 干嘛的呢?它是计算碎片的空间大小,以及标记大对象堆是否压缩。
if ((conserve_mem_setting != 0) && (n == max_generation))
{
float frag_limit = 1.0f - conserve_mem_setting / 10.0f;
size_t loh_size = get_total_gen_size (loh_generation);
size_t gen2_size = get_total_gen_size (max_generation);
float loh_frag_ratio = 0.0f;
float combined_frag_ratio = 0.0f;
if (loh_size != 0)
{
size_t loh_frag = get_total_gen_fragmentation (loh_generation);
size_t gen2_frag = get_total_gen_fragmentation (max_generation);
loh_frag_ratio = (float)loh_frag / (float)loh_size;
combined_frag_ratio = (float)(gen2_frag + loh_frag) / (float)(gen2_size + loh_size);
}
if (combined_frag_ratio > frag_limit)
{
dprintf (GTC_LOG, ("combined frag: %f > limit %f, loh frag: %f", combined_frag_ratio, frag_limit, loh_frag_ratio));
gc_data_global.gen_to_condemn_reasons.set_condition (gen_max_high_frag_p);
n = max_generation;
*blocking_collection_p = TRUE;
if (loh_frag_ratio > frag_limit)
{
settings.loh_compaction = TRUE;
dprintf (GTC_LOG, ("compacting LOH due to GCConserveMem setting"));
}
}
}
我们可以通过中文描述下上面的代码,如果通过DATAS设置的conserve_mem_setting 不为零,且当前回收代是2代(比如GC.Collect()),那么有以下计算公式
如果大对象堆!=0
{
大对象堆碎片率=大对象堆的碎片大小/大对象堆的总大小;
大对象和二代碎片率=(大对象堆碎片大小+二代堆碎片大小)/(大对象堆大小+二代堆大小);
}
如果(大对象和二代碎片率>(1.0f - conserve_mem_setting / 10.0f))&&(大对象堆碎片率>(1.0f - conserve_mem_setting / 10.0f))
{
标记大对象堆可以进行压缩了;
}
这是一个基本原理用法的地方,还有很多地方的此种类型设置堆GC进行丝滑控制,以达到动态SVR GC堆的目的。
结尾
.NET9 PreView7里面引入的DATAS,在很久之前就已经进行了实验。本篇简略分析下,实际上GC运行过程非常复杂。
往期精彩回顾