Procházet zdrojové kódy

feat: 添加 GC 时标记阶段解释

NiceTry12138 před 6 měsíci
rodič
revize
decee2488c
2 změnil soubory, kde provedl 219 přidání a 0 odebrání
  1. 3 0
      UE5/UClass/README.md
  2. 216 0
      UE5/内存/GC/README.md

+ 3 - 0
UE5/UClass/README.md

@@ -0,0 +1,3 @@
+# UObject & UClass
+
+

+ 216 - 0
UE5/内存/GC/README.md

@@ -191,4 +191,220 @@ void CollectGarbageImpl(EObjectFlags KeepFlags)
 
 ### CollectGarbageImpl
 
+#### 可达性分析
+
+标记所有可达对象,使用根集递归遍历
+
+```cpp
+FRealtimeGC GC;
+{
+	// 信息收集
+	GC.PerformReachabilityAnalysis(KeepFlags, Options);
+	// 日志打印
+}
+
+// 二次分析 一般用于调试 检测非法引用(仅调试模式启用)
+if (GC.Stats.bFoundGarbageRef && GGarbageReferenceTrackingEnabled > 0)
+{
+	// 信息收集
+	GC.PerformReachabilityAnalysis(KeepFlags, Options);
+	// 日志打印
+}
+```
+
+`FRealtimeGC::PerformReachabilityAnalysis` 函数分为两个阶段,一个是标记,一个可达性分析
+
+##### 标记
+
+在开始之前,先把 `GGCObjectReferencer` 内容添加到 `InitialObjects` 数组中
+
+> `GGCObjectReferencer` 存储的就是继承自 `FGCObject` 的非 `UObject` 对象
+
+```cpp
+if (FPlatformProperties::RequiresCookedData() && GUObjectArray.IsDisregardForGC(FGCObject::GGCObjectReferencer))
+{
+	InitialObjects.Add(FGCObject::GGCObjectReferencer);
+}
+```
+
+```cpp
+const EGCOptions OptionsForMarkPhase = Options & ~EGCOptions::WithPendingKill;
+(this->*MarkObjectsFunctions[GetGCFunctionIndex(OptionsForMarkPhase)])(KeepFlags);
+```
+
+上述是标记代码,使用 `MarkObjectsFunctions` 函数指针,该函数指针初始在构造函数中
+
+```cpp
+typedef void(FRealtimeGC::*MarkObjectsFn)(EObjectFlags);
+MarkObjectsFn MarkObjectsFunctions[4];
+
+FRealtimeGC()
+{
+	MarkObjectsFunctions[GetGCFunctionIndex(EGCOptions::None)] = &FRealtimeGC::MarkObjectsAsUnreachable<false>;
+	MarkObjectsFunctions[GetGCFunctionIndex(EGCOptions::Parallel | EGCOptions::None)] = &FRealtimeGC::MarkObjectsAsUnreachable<true>;
+
+	// do something else ...
+}
+```
+
+所以,本质上是执行 `FRealtimeGC::MarkObjectsAsUnreachable` 函数,只是根据是否多线程来选择数组中的执行函数罢了
+
+```cpp
+TArray<TArray<UObject*>, TInlineAllocator<32>> ObjectsToSerializeArrays;
+ObjectsToSerializeArrays.SetNum(NumThreads);
+
+ParallelFor( TEXT("GC.MarkUnreachable"),NumThreads,1,
+	[&ObjectsToSerializeArrays, &ClustersToDissolveList, &KeepClusterRefsList,
+		KeepFlags, NumberOfObjectsPerThread, NumThreads, MaxNumberOfObjects, bIsRerun = Stats.bFoundGarbageRef] (int32 ThreadIndex)
+		{}, !bParallel ? EParallelForFlags::ForceSingleThread : EParallelForFlags::None);
+```
+
+- `NumThreads` 是可以运行的线程数量
+- `ObjectsToSerializeArrays` 用于存储各个线程计算出来的 `UObject*`
+
+通过 `ParallelFor` 启动多个线程,通过 `ThreadIndex` 来分块计算 `GUObjectArray` 中的 `UObject*`
+
+```cpp
+int32 FirstObjectIndex = ThreadIndex * NumberOfObjectsPerThread + GUObjectArray.GetFirstGCIndex();
+int32 NumObjects = (ThreadIndex < (NumThreads - 1)) ? NumberOfObjectsPerThread : (MaxNumberOfObjects - (NumThreads - 1) * NumberOfObjectsPerThread);
+int32 LastObjectIndex = FMath::Min(GUObjectArray.GetObjectArrayNum() - 1, FirstObjectIndex + NumObjects - 1);
+
+for (int32 ObjectIndex = FirstObjectIndex; ObjectIndex <= LastObjectIndex; ++ObjectIndex)
+{}
+```
+
+那么对一个 `UObject` 进行进行标记的真正部分就在循环体中,通过 `ObjectIndex` 可以直接从 `GUObjectArray` 获取对应的 `UObject`
+
+```cpp
+FUObjectItem* ObjectItem = &GUObjectArray.GetObjectItemArrayUnsafe()[ObjectIndex];
+UObject* Object = (UObject*)ObjectItem->Object;
+```
+
+得到 `UObject` 第一步就是清除可达性标记
+
+```cpp
+ObjectItem->ClearFlags(EInternalObjectFlags::ReachableInCluster);
+```
+
+- 如果 `UObject` 是一个根对象,直接加入到 `LocalObjectsToSerialize`,如果它属于 **集群** 或者 **集群根** ,也加入到 `KeepClusterRefsList` 
+- 如果 `UObject` 是一个集群对象,并且有 `FastKeepFlags` 也加入到 `LocalObjectsToSerialize` 和 `KeepClusterRefsList`
+
+> `FastKeepFlags = EInternalObjectFlags::GarbageCollectionKeepFlags`
+
+如果 `UObject` 是 **普通对象** 或者 **集群根** 
+
+```cpp
+if (ObjectItem->HasAnyFlags(FastKeepFlags))
+{
+	bMarkAsUnreachable = false;
+}
+else if (!ObjectItem->IsPendingKill() && KeepFlags != RF_NoFlags && Object->HasAnyFlags(KeepFlags))
+{
+	bMarkAsUnreachable = false;
+}
+```
+
+符合上述条件,就不会被标记为 **不可达**
+
+如果是 `ClusterRoot` 也就是 **集群根** 对象,它是 `PendingKill` 或者 `Garbage`,那么这个集群应该被解散,会被加入到 `ClustersToDissolveList` 数组
+
+```cpp
+PRAGMA_DISABLE_DEPRECATION_WARNINGS
+else if (ObjectItem->HasAnyFlags(EInternalObjectFlags::PendingKill | EInternalObjectFlags::Garbage) && ObjectItem->HasAnyFlags(EInternalObjectFlags::ClusterRoot))
+PRAGMA_ENABLE_DEPRECATION_WARNINGS
+{
+	ClustersToDissolveList.Push(ObjectItem);
+}
+```
+
+不可达的对象会被加入到打上不可达的标记,其他会加入到 `LocalObjectsToSerialize` 和 `KeepClusterRefsList` 数组中
+
+```cpp
+if (!bMarkAsUnreachable)
+{
+	LocalObjectsToSerialize.Add(Object);
+	if (ObjectItem->HasAnyFlags(EInternalObjectFlags::ClusterRoot))
+		KeepClusterRefsList.Push(ObjectItem);
+}
+else
+{
+	ObjectItem->SetFlags(EInternalObjectFlags::Unreachable);
+}
+```
+
+前情提要,`LocalObjectsToSerialize` 是 `ObjectsToSerializeArrays` 通过 `ThreadIndex` 得到的
+
+每个线程将自己计算的内容存储到对应的数组中,通过引用的方式存储到 `ObjectsToSerializeArrays` 中
+
+```cpp
+TArray<UObject*>& LocalObjectsToSerialize = ObjectsToSerializeArrays[ThreadIndex];
+```
+
+由于 `ObjectsToSerializeArrays` 是一个 二维数组 `TArray<TArray<UObject*>, TInlineAllocator<32>>`,需要将其转换成 一维数组 然后进行后续计算,这个 一维数组 就是 `InitialObjects`
+
+```cpp
+InitialObjects.Reserve(InitialObjects.Num() + NumTotal + UE::GC::ObjectLookahead);
+for (TArray<UObject*>& Objects : ObjectsToSerializeArrays)
+{
+	InitialObjects.Append(Objects);
+}
+
+ObjectsToSerializeArrays.Empty();
+```
+
+将所有的 `ClustersToDissolveList` 中所有的对象,也就是不可达的 **集群根** 对象,将该集群中所有的对象标记为不可达
+
+```cpp
+TArray<FUObjectItem*> ClustersToDissolve;
+ClustersToDissolveList.PopAll(ClustersToDissolve);
+for (FUObjectItem* ObjectItem : ClustersToDissolve)
+{
+	if (ObjectItem->HasAnyFlags(EInternalObjectFlags::ClusterRoot))
+	{
+		GUObjectClusters.DissolveClusterAndMarkObjectsAsUnreachable(ObjectItem);
+		GUObjectClusters.SetClustersNeedDissolving();
+	}
+}
+```
+
+针对 `KeepClusterRefsList` 列表,该列表存储集群对象
+
+遍历 `KeepClusterRefsList` 列表对象,如果对象可达,但是对象对应集群的根对象是不可达的,需要 **复活** 整个集群
+
+顺便通过 `MarkReferencedClustersAsReachable` 通过遍历,将所有与该**集群**相关的**集群**都保留下来,存储到 `InitialObjects` 中
+
+##### 可达性分析
+
+
+
+```cpp
+FContextPoolScope Pool;
+FWorkerContext* Context = Pool.AllocateFromPool();
+Context->InitialNativeReferences = GetInitialReferences(Options);
+Context->SetInitialObjectsUnpadded(InitialObjects);
+
+// 分析 InitialObjects 数组内的对象,它能达到的对象,去掉不可达标签
+PerformReachabilityAnalysisOnObjects(Context, Options);
+```
+
+```cpp
+virtual void PerformReachabilityAnalysisOnObjects(FWorkerContext* Context, EGCOptions Options) override
+{
+	(this->*ReachabilityAnalysisFunctions[GetGCFunctionIndex(Options)])(*Context);
+}
+
+typedef void(FRealtimeGC::*ReachabilityAnalysisFn)(FWorkerContext&);
+ReachabilityAnalysisFn ReachabilityAnalysisFunctions[8];
+
+ReachabilityAnalysisFunctions[GetGCFunctionIndex(EGCOptions::None)] = &FRealtimeGC::PerformReachabilityAnalysisOnObjectsInternal<EGCOptions::None | EGCOptions::None>;
+ReachabilityAnalysisFunctions[GetGCFunctionIndex(EGCOptions::Parallel | EGCOptions::None)] = &FRealtimeGC::PerformReachabilityAnalysisOnObjectsInternal<EGCOptions::Parallel | EGCOptions::None>;
+
+ReachabilityAnalysisFunctions[GetGCFunctionIndex(EGCOptions::None | EGCOptions::WithPendingKill)] = &FRealtimeGC::PerformReachabilityAnalysisOnObjectsInternal<EGCOptions::None | EGCOptions::WithPendingKill>;
+ReachabilityAnalysisFunctions[GetGCFunctionIndex(EGCOptions::Parallel | EGCOptions::WithPendingKill)] = &FRealtimeGC::PerformReachabilityAnalysisOnObjectsInternal<EGCOptions::Parallel | EGCOptions::WithPendingKill>;
+```
+
+是的, `ReachabilityAnalysisFunctions` 又是函数指针,根据不同的 `EGCOptions` 选择不同的模板函数
+
+无论如何,最后调用的是 `TFastReferenceCollector::ProcessObjectArray`
+