UE4对象系统_垃圾回收

UE中的Object对象的回收不需要我们手动去delete,而是经过垃圾回收流程来销毁不再被引用的对象。GC使用的常用的Mark-Sweep算法(顺藤摸瓜)。一个对象Obj将在如下几种情况下被回收掉:

  • Obj没有被标记为RootSet,并且没有被其它对象引用
  • Obj在程序中被标记为RF_PendingKill(通过MarkPendingKill()接口)。
  • Obj没有在void AddReferencedObjects( FReferenceCollector& Collector )中登记, 相关源码在Engine\Source\Runtime\CoreUObject\Public\UObject\GCObject.h

一般地在UObject类体系的定义中,加入UPROPERTY标记的对象指针都会被GC在Mark-Sweep时进行考察引用,例如下面的代码片段:

    /** Sound to play each time we fire */
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Gameplay)
    class USoundBase* FireSound;

    /** AnimMontage to play each time we fire */
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Gameplay)
    class UAnimMontage* FireAnimation;

GC模块相关源码:
Engine\Source\Runtime\CoreUObject\Public\UObject\GarbageCollection.h
Engine\Source\Runtime\CoreUObject\Private\UObject\GarbageCollection.cpp
Engine\Source\Runtime\CoreUObject\Public\UObject\FastReferenceCollector.h
Engine\Source\Runtime\CoreUObject\Private\UObject\ReferenceChainSearch.cpp

ReferenceToken

在UObject体系中,每个类都有一个UClass实例用于描述该类的反射信息,使用UProperty来描述每个类成员变量,但是在GC中如果直接遍历UProperty来进行扫描对象引用的话,效率会比较低(因为有许多非Object引用型的Property),所以ReferenceToken就运用而生了,ReferenceToken是一组token流,描述了类中的对象引用情况。

/**
 * Enum of different supported reference type tokens. 
 */
enum EGCReferenceType
{
    GCRT_None           = 0,
    GCRT_Object,
    GCRT_PersistentObject,
    GCRT_ArrayObject,
    GCRT_ArrayStruct,
    GCRT_FixedArray,
    GCRT_AddStructReferencedObjects,
    GCRT_AddReferencedObjects,
    GCRT_AddTMapReferencedObjects,
    GCRT_AddTSetReferencedObjects,
    GCRT_EndOfPointer,
    GCRT_EndOfStream,
};
/**
 * Reference token stream class. Used for creating and parsing stream of object references.
 */
struct FGCReferenceTokenStream
{
    /** Initialization value to ensure that we have the right skip index index */
    enum EGCArrayInfoPlaceholder { E_GCSkipIndexPlaceholder = 0xDEADBABE }; 

    /** Constructor */
    FGCReferenceTokenStream()
    {
        check( sizeof(FGCReferenceInfo) == sizeof(uint32) );
    }

    /**
     * Shrinks the token stream, removing array slack.
     */
    void Shrink()
    {
        Tokens.Shrink();
    }

    /** Empties the token stream entirely */
    void Empty()
    {
        Tokens.Empty();
    }

    /**
     * Returns the size ofthe reference token stream.
     *
     * @returns Size of the stream.
     */
    int32 Size() const
    {
        return Tokens.Num();
    }

    /**
     * return true if this is empty
     */
    bool IsEmpty() const
    {
        return Tokens.Num() == 0;
    }

    /**
     * Prepends passed in stream to existing one.
     *
     * @param Other stream to concatenate
     */
    void PrependStream( const FGCReferenceTokenStream& Other );

    /**
     * Emit reference info.
     *
     * @param ReferenceInfo reference info to emit.
     *
     * @return Index of the reference info in the token stream.
     */
    int32 EmitReferenceInfo(FGCReferenceInfo ReferenceInfo);

    /**
     * Emit placeholder for aray skip index, updated in UpdateSkipIndexPlaceholder
     *
     * @return the index of the skip index, used later in UpdateSkipIndexPlaceholder
     */
    uint32 EmitSkipIndexPlaceholder();

    /**
     * Updates skip index place holder stored and passed in skip index index with passed
     * in skip index. The skip index is used to skip over tokens in the case of an emtpy 
     * dynamic array.
     * 
     * @param SkipIndexIndex index where skip index is stored at.
     * @param SkipIndex index to store at skip index index
     */
    void UpdateSkipIndexPlaceholder( uint32 SkipIndexIndex, uint32 SkipIndex );

    /**
     * Emit count
     *
     * @param Count count to emit
     */
    void EmitCount( uint32 Count );

    /**
     * Emit a pointer
     *
     * @param Ptr pointer to emit
     */
    void EmitPointer( void const* Ptr );

    /**
     * Emit stride
     *
     * @param Stride stride to emit
     */
    void EmitStride( uint32 Stride );

    /**
     * Increase return count on last token.
     *
     * @return index of next token
     */
    uint32 EmitReturn();

    /**
     * Helper function to quickly replace or add ARO call.
     */
    void ReplaceOrAddAddReferencedObjectsCall(void (*AddReferencedObjectsPtr)(UObject*, class FReferenceCollector&));

    /**
     * Reads count and advances stream.
     *
     * @return read in count
     */
    FORCEINLINE uint32 ReadCount( uint32& CurrentIndex )
    {
        return Tokens[CurrentIndex++];
    }

    /**
     * Reads stride and advances stream.
     *
     * @return read in stride
     */
    FORCEINLINE uint32 ReadStride( uint32& CurrentIndex )
    {
        return Tokens[CurrentIndex++];
    }

    /**
     * Reads pointer and advance stream
     *
     * @return read in pointer
     */
    FORCEINLINE void* ReadPointer( uint32& CurrentIndex )
    {
        UPTRINT Result = UPTRINT(Tokens[CurrentIndex++]);
#if PLATFORM_64BITS // warning C4293: '<<' : shift count negative or too big, undefined behavior, so we needed the ifdef
        static_assert(sizeof(void*) == 8, "Pointer size mismatch.");
        Result |= UPTRINT(Tokens[CurrentIndex++]) << 32;
#else
        static_assert(sizeof(void*) == 4, "Pointer size mismatch.");
#endif
        return (void*)Result;
    }

    /**
     * Reads in reference info and advances stream.
     *
     * @return read in reference info
     */
    FORCEINLINE FGCReferenceInfo ReadReferenceInfo( uint32& CurrentIndex )
    {
        return Tokens[CurrentIndex++];
    }

    /**
     * Access reference info at passed in index. Used as helper to eliminate LHS.
     *
     * @return Reference info at passed in index
     */
    FORCEINLINE FGCReferenceInfo AccessReferenceInfo( uint32 CurrentIndex ) const
    {
        return Tokens[CurrentIndex];
    }

    /**
     * Read in skip index and advances stream.
     *
     * @return read in skip index
     */
    FORCEINLINE FGCSkipInfo ReadSkipInfo( uint32& CurrentIndex )
    {
        FGCSkipInfo SkipInfo = Tokens[CurrentIndex];
        SkipInfo.SkipIndex += CurrentIndex;
        CurrentIndex++;
        return SkipInfo;
    }

    /**
     * Read return count stored at the index before the skip index. This is required 
     * to correctly return the right amount of levels when skipping over an empty array.
     *
     * @param SkipIndex index of first token after array
     */
    FORCEINLINE uint32 GetSkipReturnCount( FGCSkipInfo SkipInfo )
    {
        check( SkipInfo.SkipIndex > 0 && SkipInfo.SkipIndex <= (uint32)Tokens.Num() );      
        FGCReferenceInfo ReferenceInfo = Tokens[SkipInfo.SkipIndex-1];
        check( ReferenceInfo.Type != GCRT_None );
        return ReferenceInfo.ReturnCount - SkipInfo.InnerReturnCount;       
    }

    /**
     * Queries the stream for an end of stream condition
     *
     * @return true if the end of the stream has been reached, false otherwise
     */
    FORCEINLINE bool EndOfStream( uint32 CurrentIndex )
    {
        return CurrentIndex >= (uint32)Tokens.Num();
    }

private:

    /**
     * Helper function to store a pointer into a preallocated token stream.
     *
     * @param Stream Preallocated token stream.
     * @param Ptr pointer to store
     */
    FORCEINLINE void StorePointer( uint32* Stream, void const* Ptr )
    {
    #if PLATFORM_64BITS // warning C4293: '<<' : shift count negative or too big, undefined behavior, so we needed the ifdef
        static_assert(sizeof(void*) == 8, "Pointer size mismatch.");
        Stream[0] = UPTRINT(Ptr) & 0xffffffff;
        Stream[1] = UPTRINT(Ptr) >> 32;
    #else
        static_assert(sizeof(void*) == 4, "Pointer size mismatch.");
        Stream[0] = PTRINT(Ptr);
    #endif
    }

    /** Token array */
    TArray<uint32>  Tokens;
};

TokenStream的生成

void UClass::AssembleReferenceTokenStream(bool bForce)
{
    // Lock for non-native classes
    struct FScopeLockIfNotNative
    {
        FCriticalSection& ScopeCritical;
        const bool bNotNative;
        FScopeLockIfNotNative(FCriticalSection& InScopeCritical, bool bIsNotNative)
            : ScopeCritical(InScopeCritical)
            , bNotNative(bIsNotNative)
        {
            if (bNotNative)
            {
                ScopeCritical.Lock();
            }
        }
        ~FScopeLockIfNotNative()
        {
            if (bNotNative)
            {
                ScopeCritical.Unlock();
            }
        }
    } ReferenceTokenStreamLock(ReferenceTokenStreamCritical, !(ClassFlags & CLASS_Native));

    UE_CLOG(!IsInGameThread() && !IsGarbageCollectionLocked(), LogGarbage, Fatal, TEXT("AssembleReferenceTokenStream for %s called on a non-game thread while GC is not locked."), *GetFullName());

    if (!HasAnyClassFlags(CLASS_TokenStreamAssembled) || bForce)
    {
        if (bForce)
        {
            ReferenceTokenStream.Empty();
#if !(UE_BUILD_TEST || UE_BUILD_SHIPPING)
            DebugTokenMap.Empty();
#endif
            ClassFlags &= ~CLASS_TokenStreamAssembled;
        }
        TArray<const UStructProperty*> EncounteredStructProps;

        // Iterate over properties defined in this class
        for( TFieldIterator<UProperty> It(this,EFieldIteratorFlags::ExcludeSuper); It; ++It)
        {
            UProperty* Property = *It;
            Property->EmitReferenceInfo(*this, 0, EncounteredStructProps);  // 每个数UProperty负责指定Token
        }

        if (GetSuperClass())
        {
            // Make sure super class has valid token stream.
            GetSuperClass()->AssembleReferenceTokenStream();
            if (!GetSuperClass()->ReferenceTokenStream.IsEmpty())
            {
                // Prepend super's stream. This automatically handles removing the EOS token.
                PrependStreamWithSuperClass(*GetSuperClass());  // 在前面添加父类的TokenStream
            }
        }
        else
        {
            UObjectBase::EmitBaseReferences(this);
        }

#if !WITH_EDITOR
        // In no-editor builds UObject::ARO is empty, thus only classes
        // which implement their own ARO function need to have the ARO token generated.
        if (ClassAddReferencedObjects != &UObject::AddReferencedObjects)
#endif
        {
            check(ClassAddReferencedObjects != NULL);
            ReferenceTokenStream.ReplaceOrAddAddReferencedObjectsCall(ClassAddReferencedObjects);
        }
        if (ReferenceTokenStream.IsEmpty())
        {
            return;
        }

        // Emit end of stream token.
        static const FName EOSDebugName("EOS");
        EmitObjectReference(0, EOSDebugName, GCRT_EndOfStream);

        // Shrink reference token stream to proper size.
        ReferenceTokenStream.Shrink();

        check(!HasAnyClassFlags(CLASS_TokenStreamAssembled)); // recursion here is probably bad
        ClassFlags |= CLASS_TokenStreamAssembled;
    }
}

AssembleReferenceTokenStreams的调用情形一:


TokenStream Build

CollectGarbage的执行

通过调用GC模块提供的void CollectGarbage(EObjectFlags KeepFlags, bool bPerformFullPurge);接口进行垃圾回收。在标记阶段是一次性标记完的,但是在清除阶段可以采用增量式清除(也就是一次调用里不必销毁掉所有垃圾对象)

/** 
 * Deletes all unreferenced objects, keeping objects that have any of the passed in KeepFlags set
 *
 * @param   KeepFlags           objects with those flags will be kept regardless of being referenced or not
 * @param   bPerformFullPurge   if true, perform a full purge after the mark pass
 */
void CollectGarbageInternal(EObjectFlags KeepFlags, bool bPerformFullPurge)
SnapCode_01
Paste_Image.png
Paste_Image.png
Paste_Image.png

IncrementalPurgeGarbage

Paste_Image.png
Paste_Image.png

Paste_Image.png

Paste_Image.png
Paste_Image.png

FRealtimeGC

负责Object是否被直接或间接引用分析。

Paste_Image.png

在TFastReferenceCollector::CollectReferences(FGCArrayStruct& ArrayStruct)中:

Paste_Image.png

ProcessObjectArray()函数进行了TokenStream解析和引用标记

    /**
     * Traverses UObject token stream to find existing references
     *
     * @param InObjectsToSerializeArray Objects to process
     * @param MyCompletionGraphEvent Task graph event
     */
    void ProcessObjectArray(FGCArrayStruct& InObjectsToSerializeStruct, const FGraphEventRef& MyCompletionGraphEvent)
    {
        DECLARE_SCOPE_CYCLE_COUNTER(TEXT("TFastReferenceCollector::ProcessObjectArray"), STAT_FFastReferenceCollector_ProcessObjectArray, STATGROUP_GC);

        UObject* CurrentObject = nullptr;

        const int32 MinDesiredObjectsPerSubTask = ReferenceProcessor.GetMinDesiredObjectsPerSubTask(); // sometimes there will be less, a lot less

        /** Growing array of objects that require serialization */
        FGCArrayStruct& NewObjectsToSerializeStruct = *ArrayPool.GetArrayStructFromPool();

        // Ping-pong between these two arrays if there's not enough objects to spawn a new task
        TArray<UObject*>& ObjectsToSerialize = InObjectsToSerializeStruct.ObjectsToSerialize;
        TArray<UObject*>& NewObjectsToSerialize = NewObjectsToSerializeStruct.ObjectsToSerialize;

        // Presized "recursion" stack for handling arrays and structs.
        TArray<FStackEntry> Stack;
        Stack.AddUninitialized(128); //@todo rtgc: need to add code handling more than 128 layers of recursion or at least assert

        // it is necessary to have at least one extra item in the array memory block for the iffy prefetch code, below
        ObjectsToSerialize.Reserve(ObjectsToSerialize.Num() + 1);

        // Keep serializing objects till we reach the end of the growing array at which point
        // we are done.
        int32 CurrentIndex = 0;
        do
        {
            CollectorType ReferenceCollector(ReferenceProcessor, NewObjectsToSerializeStruct);
            while (CurrentIndex < ObjectsToSerialize.Num()) // 逐个对象遍历
            {
#if PERF_DETAILED_PER_CLASS_GC_STATS
                uint32 StartCycles = FPlatformTime::Cycles();
#endif
                CurrentObject = ObjectsToSerialize[CurrentIndex++];  // 当前对象

                // GetData() used to avoiding bounds checking (min and max)
                // FMath::Min used to avoid out of bounds (without branching) on last iteration. Though anything can be passed into PrefetchBlock, 
                // reading ObjectsToSerialize out of bounds is not safe since ObjectsToSerialize[Num()] may be an unallocated/unsafe address.
                const UObject * const NextObject = ObjectsToSerialize.GetData()[FMath::Min<int32>(CurrentIndex, ObjectsToSerialize.Num() - 1)];

                // Prefetch the next object assuming that the property size of the next object is the same as the current one.
                // This allows us to avoid a branch here.
                FPlatformMisc::PrefetchBlock(NextObject, CurrentObject->GetClass()->GetPropertiesSize());

                //@todo rtgc: we need to handle object references in struct defaults

                // Make sure that token stream has been assembled at this point as the below code relies on it.
                if (!bParallel && bAutoGenerateTokenStream)
                {
                    UClass* ObjectClass = CurrentObject->GetClass();
                    if (!ObjectClass->HasAnyClassFlags(CLASS_TokenStreamAssembled))
                    {
                        ObjectClass->AssembleReferenceTokenStream();
                    }
                }
#if DO_CHECK
                if (!CurrentObject->GetClass()->HasAnyClassFlags(CLASS_TokenStreamAssembled))
                {
                    UE_LOG(LogGarbage, Fatal, TEXT("%s does not yet have a token stream assembled."), *GetFullNameSafe(CurrentObject->GetClass()));
                }
#endif

                // Get pointer to token stream and jump to the start.
                FGCReferenceTokenStream* RESTRICT TokenStream = &CurrentObject->GetClass()->ReferenceTokenStream;
                uint32 TokenStreamIndex = 0;
                // Keep track of index to reference info. Used to avoid LHSs.
                uint32 ReferenceTokenStreamIndex = 0;

                // Create stack entry and initialize sane values.
                FStackEntry* RESTRICT StackEntry = Stack.GetData();  
                uint8* StackEntryData = (uint8*)CurrentObject;
                StackEntry->Data = StackEntryData; // 当前对象内存地址
                StackEntry->Stride = 0;
                StackEntry->Count = -1;
                StackEntry->LoopStartIndex = -1;

                // Keep track of token return count in separate integer as arrays need to fiddle with it.
                int32 TokenReturnCount = 0;

                // Parse the token stream.
                while (true)
                {
                    // Cache current token index as it is the one pointing to the reference info.
                    ReferenceTokenStreamIndex = TokenStreamIndex;

                    // Handle returning from an array of structs, array of structs of arrays of ... (yadda yadda)
                    // TokenRenturnCount表示了array嵌套层数
                    for (int32 ReturnCount = 0; ReturnCount<TokenReturnCount; ReturnCount++)
                    {
                        // Make sure there's no stack underflow.
                        check(StackEntry->Count != -1);

                        // We pre-decrement as we're already through the loop once at this point.
                        if (--StackEntry->Count > 0)
                        {
                            // Point data to next entry.
                            StackEntryData = StackEntry->Data + StackEntry->Stride;   // 数组元素位置(迭代前进)
                            StackEntry->Data = StackEntryData;

                            // Jump back to the beginning of the loop.
                            TokenStreamIndex = StackEntry->LoopStartIndex;   // 定位到当前数据结构的token位置
                            ReferenceTokenStreamIndex = StackEntry->LoopStartIndex;
                            // We're not done with this token loop so we need to early out instead of backing out further.
                            break;
                        }
                        else
                        {
                            StackEntry--;
                            StackEntryData = StackEntry->Data;
                        }
                    }

                    TokenStreamIndex++;
                    FGCReferenceInfo ReferenceInfo = TokenStream->AccessReferenceInfo(ReferenceTokenStreamIndex);  // 读取token

                    switch(ReferenceInfo.Type)
                    {
                    case GCRT_Object:
                    {
                        // We're dealing with an object reference.
                        UObject**   ObjectPtr = (UObject**)(StackEntryData + ReferenceInfo.Offset); // 成员变量的地址
                        UObject*&   Object = *ObjectPtr;
                        TokenReturnCount = ReferenceInfo.ReturnCount;
                        ReferenceProcessor.HandleTokenStreamObjectReference(NewObjectsToSerialize, CurrentObject, Object, ReferenceTokenStreamIndex, true);
                    }
                    break;
                    case GCRT_ArrayObject:
                    {
                        // We're dealing with an array of object references.
                        TArray<UObject*>& ObjectArray = *((TArray<UObject*>*)(StackEntryData + ReferenceInfo.Offset));
                        TokenReturnCount = ReferenceInfo.ReturnCount;
                        for (int32 ObjectIndex = 0, ObjectNum = ObjectArray.Num(); ObjectIndex < ObjectNum; ++ObjectIndex)
                        {
                            ReferenceProcessor.HandleTokenStreamObjectReference(NewObjectsToSerialize, CurrentObject, ObjectArray[ObjectIndex], ReferenceTokenStreamIndex, true);
                        }
                    }
                    break;
                    case GCRT_ArrayStruct:
                    {
                        // We're dealing with a dynamic array of structs.
                        const FScriptArray& Array = *((FScriptArray*)(StackEntryData + ReferenceInfo.Offset));
                        StackEntry++;  // 分配一个栈slot,用于深入数组元素
                        StackEntryData = (uint8*)Array.GetData();
                        StackEntry->Data = StackEntryData;
                        StackEntry->Stride = TokenStream->ReadStride(TokenStreamIndex);
                        StackEntry->Count = Array.Num();

                        const FGCSkipInfo SkipInfo = TokenStream->ReadSkipInfo(TokenStreamIndex);
                        StackEntry->LoopStartIndex = TokenStreamIndex;

                        if (StackEntry->Count == 0)
                        {
                            // Skip empty array by jumping to skip index and set return count to the one about to be read in.
                            TokenStreamIndex = SkipInfo.SkipIndex;
                            TokenReturnCount = TokenStream->GetSkipReturnCount(SkipInfo);
                        }
                        else
                        {
                            // Loop again.
                            check(StackEntry->Data);
                            TokenReturnCount = 0;
                        }
                    }
                    break;
                    case GCRT_PersistentObject:
                    {
                        // We're dealing with an object reference.
                        UObject**   ObjectPtr = (UObject**)(StackEntryData + ReferenceInfo.Offset);
                        UObject*&   Object = *ObjectPtr;
                        TokenReturnCount = ReferenceInfo.ReturnCount;
                        ReferenceProcessor.HandleTokenStreamObjectReference(NewObjectsToSerialize, CurrentObject, Object, ReferenceTokenStreamIndex, false);
                    }
                    break;
                    case GCRT_FixedArray:
                    {
                        // We're dealing with a fixed size array
                        uint8* PreviousData = StackEntryData;
                        StackEntry++;
                        StackEntryData = PreviousData;
                        StackEntry->Data = PreviousData;
                        StackEntry->Stride = TokenStream->ReadStride(TokenStreamIndex);
                        StackEntry->Count = TokenStream->ReadCount(TokenStreamIndex);
                        StackEntry->LoopStartIndex = TokenStreamIndex;
                        TokenReturnCount = 0;
                    }
                    break;
                    case GCRT_AddStructReferencedObjects:
                    {
                        // We're dealing with a function call
                        void const* StructPtr = (void*)(StackEntryData + ReferenceInfo.Offset);
                        TokenReturnCount = ReferenceInfo.ReturnCount;
                        UScriptStruct::ICppStructOps::TPointerToAddStructReferencedObjects Func = (UScriptStruct::ICppStructOps::TPointerToAddStructReferencedObjects) TokenStream->ReadPointer(TokenStreamIndex);
                        Func(StructPtr, ReferenceCollector);
                    }
                    break;
                    case GCRT_AddReferencedObjects:
                    {
                        // Static AddReferencedObjects function call.
                        void(*AddReferencedObjects)(UObject*, FReferenceCollector&) = (void(*)(UObject*, FReferenceCollector&))TokenStream->ReadPointer(TokenStreamIndex);
                        TokenReturnCount = ReferenceInfo.ReturnCount;
                        AddReferencedObjects(CurrentObject, ReferenceCollector);
                    }
                    break;
                    case GCRT_AddTMapReferencedObjects:
                    {
                        void*         Map = StackEntryData + ReferenceInfo.Offset;
                        UMapProperty* MapProperty = (UMapProperty*)TokenStream->ReadPointer(TokenStreamIndex);
                        TokenReturnCount = ReferenceInfo.ReturnCount;
                        FSimpleObjectReferenceCollectorArchive CollectorArchive(CurrentObject, ReferenceCollector);
                        MapProperty->SerializeItem(CollectorArchive, Map, nullptr);
                    }
                    break;
                    case GCRT_AddTSetReferencedObjects:
                    {
                        void*         Set = StackEntryData + ReferenceInfo.Offset;
                        USetProperty* SetProperty = (USetProperty*)TokenStream->ReadPointer(TokenStreamIndex);
                        TokenReturnCount = ReferenceInfo.ReturnCount;
                        FSimpleObjectReferenceCollectorArchive CollectorArchive(CurrentObject, ReferenceCollector);
                        SetProperty->SerializeItem(CollectorArchive, Set, nullptr);
                    }
                    break;
                    case GCRT_EndOfPointer:
                    {
                        TokenReturnCount = ReferenceInfo.ReturnCount;
                    }
                    break;
                    case GCRT_EndOfStream:
                    {
                        // Break out of loop.
                        goto EndLoop;
                    }
                    break;
                    default:
                    {
                        UE_LOG(LogGarbage, Fatal, TEXT("Unknown token"));
                        break;
                    }
                }
                }
EndLoop:
                check(StackEntry == Stack.GetData());

                if (bParallel && NewObjectsToSerialize.Num() >= MinDesiredObjectsPerSubTask)
                {
                    // This will start queueing task with objects from the end of array until there's less objects than worth to queue
                    const int32 ObjectsPerSubTask = FMath::Max<int32>(MinDesiredObjectsPerSubTask, NewObjectsToSerialize.Num() / FTaskGraphInterface::Get().GetNumWorkerThreads());
                    while (NewObjectsToSerialize.Num() >= MinDesiredObjectsPerSubTask)
                    {
                        const int32 StartIndex = FMath::Max(0, NewObjectsToSerialize.Num() - ObjectsPerSubTask);
                        const int32 NumThisTask = NewObjectsToSerialize.Num() - StartIndex;
                        if (MyCompletionGraphEvent.GetReference())
                        {
                            MyCompletionGraphEvent->DontCompleteUntil(TGraphTask< FCollectorTask >::CreateTask().ConstructAndDispatchWhenReady(this, &NewObjectsToSerialize, StartIndex, NumThisTask, ArrayPool));
                        }
                        else
                        {
                            TaskQueue.AddTask(&NewObjectsToSerialize, StartIndex, NumThisTask);
                        }
                        NewObjectsToSerialize.SetNumUnsafeInternal(StartIndex);
                    }
                }

#if PERF_DETAILED_PER_CLASS_GC_STATS
                // Detailed per class stats should not be performed when parallel GC is running
                check(!bParallel);
                ReferenceProcessor.UpdateDetailedStats(CurrentObject, FPlatformTime::Cycles() - StartCycles);
#endif
            }

            if (bParallel && NewObjectsToSerialize.Num() >= MinDesiredObjectsPerSubTask)
            {
                const int32 ObjectsPerSubTask = FMath::Max<int32>(MinDesiredObjectsPerSubTask, NewObjectsToSerialize.Num() / FTaskGraphInterface::Get().GetNumWorkerThreads());
                int32 StartIndex = 0;
                while (StartIndex < NewObjectsToSerialize.Num())
                {
                    const int32 NumThisTask = FMath::Min<int32>(ObjectsPerSubTask, NewObjectsToSerialize.Num() - StartIndex);
                    if (MyCompletionGraphEvent.GetReference())
                    {
                        MyCompletionGraphEvent->DontCompleteUntil(TGraphTask< FCollectorTask >::CreateTask().ConstructAndDispatchWhenReady(this, &NewObjectsToSerialize, StartIndex, NumThisTask, ArrayPool));
                    }
                    else
                    {
                        TaskQueue.AddTask(&NewObjectsToSerialize, StartIndex, NumThisTask);
                    }
                    StartIndex += NumThisTask;
                }
                NewObjectsToSerialize.SetNumUnsafeInternal(0);
            }
            else if (NewObjectsToSerialize.Num())
            {
                // Don't spawn a new task, continue in the current one
                // To avoid allocating and moving memory around swap ObjectsToSerialize and NewObjectsToSerialize arrays
                Exchange(ObjectsToSerialize, NewObjectsToSerialize);
                // Empty but don't free allocated memory
                NewObjectsToSerialize.SetNumUnsafeInternal(0);

                CurrentIndex = 0;
            }
        }
        while (CurrentIndex < ObjectsToSerialize.Num());

#if PERF_DETAILED_PER_CLASS_GC_STATS
        // Detailed per class stats should not be performed when parallel GC is running
        check(!bParallel);
        ReferenceProcessor.LogDetailedStatsSummary();
#endif

        ArrayPool.ReturnToPool(&NewObjectsToSerializeStruct);
    }

注意在标记ObjectReferecen时:


Paste_Image.png

ReferenceToken案例

USTRUCT()
struct FIRSTPERSONCPP_API FRoundHero
{
    GENERATED_BODY()

    UPROPERTY()
    TArray<UObject*>    Skills;

    UPROPERTY()
    UObject*            Weapon[2];
};

USTRUCT()
struct FIRSTPERSONCPP_API FRoundInfo
{
    GENERATED_BODY()

    int32       Index;
    float       TimeLength;

    UPROPERTY()
    TArray<FRoundHero>  Heros;

    UPROPERTY()
    UObject*        RecordData;
};

/**
 * 
 */
UCLASS()
class FIRSTPERSONCPP_API UPlayerData : public UObject
{
    GENERATED_BODY()
    
    
public:
    UPlayerData();

    FString     PlayerName;
    uint32_t    BulletNum;

    UPROPERTY()
    AActor      *pHero[4];

    UPROPERTY()
    TArray<UObject*>    HelperData;

    UPROPERTY()
    TArray<FRoundInfo>  Rounds;
};

Token序列如下(这里忽略基类的token):

// 发射步骤和token stream如下:
1. 字段 AActor *pHero[4]
    EmitObjectReference                   0 {GCRT_FixedArray, Offset}
    EmitStride                            1 {Element Size }
    EmitCount                             2 {Count=4}
    EmitObjectReference                   3 {GCRT_Object, Offset, ReturnCount=0}
    EmitReturn                              修改Token 3的ReturnCount, => {GCRT_Object, Offset, ReturnCount=1}

2. 字段 TArray<UObject*> HelperData
    EmitObjectReference                   4 {GCRT_ArrayObject, Offset}

3. 字段 TArray<FRoundInfo>    Rounds
    EmitObjectReference                   5 {GCRT_ArrayStruct, Offset}
    EmitStride                            6 {Element Size}
    EmitSkipIndexPlaceholder              7 {E_GCSkipIndexPlaceholder}
    
    /////////////{{
    UStructProperty::EmitReferenceInfo, 结构FRoundInfo
    3.1 字段 TArray<FRoundHero>   Heros
         EmitObjectReference              8 {GCRT_ArrayStruct, Offset}
         EmitStride                       9 {Element Size}
         EmitSkipIndexPlaceholder        10 {E_GCSkipIndexPlaceholder}
         
         ////////////{{
         UStructProperty::EmitReferenceInfo, 结构FRoundHero
         3.1.1 字段 TArray<UObject*> Skills
               EmitObjectReference       11 {GCRT_ArrayObject, Offset}
         3.1.2 字段 UObject* Weapon
               EmitObjectReference       12 {GCRT_FixedArray, Offset}
               EmitStride                13 {Element Size}
               EmitCount                 14 {Count=2}
               EmitObjectReference       15 {GCRT_Object, Offset, ReturnCount=0}
               EmitReturn                   修改Token 15的ReturnCount, => {GCRT_Object, Offset, ReturnCount=1}
         ////////////}}    
        
         EmitReturn                         修改Token 15的ReturnCount, => {GCRT_Object, Offset, ReturnCount=2} 
         UpdateSkipIndexPlaceholder         修改Token 10的FGCSkipInfo  => {SkipIndex=6, InnerReturnCount=1}
    3.2 字段 UObject* RecordData   
         EmitObjectReference             16 {GCRT_Object, Offset, ReturnCount=0}
    ///////////////}}

    EmitReturn                              修改Token 16的ReturnCount, => {GCRT_Object, Offset, ReturnCount=1}
    UpdateSkipIndexPlaceholder              修改Token 7的FGCSkipInfo  => {SkipIndex=10, InnerReturnCount=0}
    
4. End Of Token
    EmitObjectReference                  17 {GCRT_EndOfStream, 0}

关于GC_Cluster

这个尚未研究,个人估计是为了优化垃圾标记,比如在粒子系统中,这些例子对象标记为一组Cluster,那么可以一次性标记可见性。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,898评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,401评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,058评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,539评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,382评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,319评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,706评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,370评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,664评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,715评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,476评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,326评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,730评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,003评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,275评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,683评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,877评论 2 335

推荐阅读更多精彩内容