.NET9 PreView7 DATAS原理简析

文摘   2024-09-02 09:06   湖北  

点击上方蓝字 江湖评谈设为关注/星标




前言

之前(.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运行过程非常复杂。

往期精彩回顾

.NET9 Pre3 CLR的优化细节

.NET9异常(CLR)原理(顶阶技术


江湖评谈
记录,分享,自由。
 最新文章