<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                本文章由cartzhang編寫,轉載請注明出處。 所有權利保留。 文章鏈接:[http://blog.csdn.net/cartzhang/article/details/50524317](http://blog.csdn.net/cartzhang/article/details/50524317) 作者:cartzhang 本篇譯文同發與蠻牛譯館, 地址:[http://www.manew.com/thread-46327-1-1.html?_dsign=ae91354a](http://www.manew.com/thread-46327-1-1.html?_dsign=ae91354a) 從我上次談論內存申請和跟蹤已經有一段時間了。我得抽出時間來在虛幻4上實現跟蹤,并且已經完成了。我假設你已經閱讀過來之前的博客:“虛幻引擎4中內存跟蹤功能的局限性”和“內存申請和跟蹤”。 ## 虛幻引擎4內存管理的API ### 基本的內存分配方法。 虛幻引擎4中,有三種基本的內存分配和釋放方法: 1.使用GMalloc指針。這種方法是獲得全局的分配器,分配器的使用依賴于GCreateMalloc()函數。 2.FMemory函數。有靜態函數比如:Malloc(),Realloc(),Free()。他們也是使用GMalloc來申請內存,但是在此之前,它會在每次allocation, reallocation或free之前檢查GMalloc 是否定義。若GMalloc 是空,就調用GCreateMalloc() 。 3.全局的的New和delete操作。缺省情況下,只在模塊的ModuleBoilerplate.h 的文件中定義,也就是說,很多調用new和delete的操作不在虛幻4的內存系統中管理。重載操作實際上調用的是FMemory函數。 這些情況就會出現使用這些機制就可能出現內存不會釋放和清空的問題。為了撲捉這些問題,我提交了一個申請,已經被整合并發布在版本4.9上,通過C運行時庫調用_CrtSetAllocHook()來獲取這些分配。其中一個例子,是引擎中Zlib集成并沒有使用引擎工具來分配,它使用了_CrtSetAllocHook() ,我提交了一個修復版本在4.9版本發布。 直接調用GMalloc 和FMemory 函數這兩個基礎的API,如下: ~~~ virtual void* Malloc( SIZE_T Count, uint32 Alignment = DEFAULT_ALIGNMENT ) = 0; virtual void* Realloc( void* Original, SIZE_T Count, uint32 Alignment = DEFAULT_ALIGNMENT ) = 0; virtual void Free( void* Original ) = 0; These are the places that would need to be modified if we want to add any new piece of data per allocation. ~~~ ### 引擎的整合 為了寫法類似,我重載了分配器,我從FMalloc繼承了一個新類,叫做FMallocTracker,這樣就可以勾到虛幻的內存分配系統上。因為一個有效的分配器必須在創建FMallocTracker 實例時所有實際的分配已經由其分配器完成。FMallocTracker 只是保存了跟蹤信息。但是這是不夠的,實際上需要知道分配器所有方法來跟蹤數據。因此,第一步就是當我們使用內存跟蹤功能時,修改分配器函數。 ~~~ #if USE_MALLOC_TRACKER virtual void* Malloc( SIZE_T Count, uint32 Alignment, const uint32 GroupId, const char * const Name ) = 0; virtual void* Realloc( void* Original, SIZE_T Count, uint32 Alignment, const uint32 GroupId, const char * const Name ) = 0; #else virtual void* Malloc( SIZE_T Count, uint32 Alignment = DEFAULT_ALIGNMENT ) = 0; virtual void* Realloc( void* Original, SIZE_T Count, uint32 Alignment = DEFAULT_ALIGNMENT ) = 0; #endif // USE_MALLOC_TRACKER ~~~ 新參數: ?名稱:分配名稱。這個名稱可以任何名稱,但是建議寫易懂便于搜索。在本文后面,將會展示更多相關內容。 ?分組:組ID是針對于當前所分配的。有些分組我已經定義過來,但是有些你需要根據你的需要來定義。 這個改變就意味著,所有的分配器在引擎中都是透明的,一旦完成,你可以標記分配器,而不用擔心底層的實現。標記分配的目的不僅僅是為了跟蹤,也涉及到代碼調試。一旦這些便簽在很大的代碼庫中實現后,當內存飆升,處理不同組的交互,修復相關的內存崩潰時,就會有很大的好處了。 下一步是集成new和delete操作。我之前提到過,在引擎的ModuleBoilerplate.h文件中已經定義好,為了更好的的覆蓋,我把它移動到MemoryBase.h中。下一步是定義新的重載操作,并傳入名稱和分組。 ~~~ OPERATOR_NEW_MSVC_PRAGMA FORCEINLINE void* operator new (size_t Size, const uint32 Alignment, const uint32 GroupId, const char * const Name) OPERATOR_NEW_NOTHROW_SPEC{ return FMemory::Malloc(Size, Alignment, GroupId, Name); } OPERATOR_NEW_MSVC_PRAGMA FORCEINLINE void* operator new[](size_t Size, const uint32 Alignment, const uint32 GroupId, const char * const Name) OPERATOR_NEW_NOTHROW_SPEC{ return FMemory::Malloc(Size, Alignment, GroupId, Name); } OPERATOR_NEW_MSVC_PRAGMA FORCEINLINE void* operator new (size_t Size, const uint32 Alignment, const uint32 GroupId, const char * const Name, const std::nothrow_t&) OPERATOR_NEW_NOTHROW_SPEC { return FMemory::Malloc(Size, Alignment, GroupId, Name); } OPERATOR_NEW_MSVC_PRAGMA FORCEINLINE void* operator new[](size_t Size, const uint32 Alignment, const uint32 GroupId, const char * const Name, const std::nothrow_t&) OPERATOR_NEW_NOTHROW_SPEC { return FMemory::Malloc(Size, Alignment, GroupId, Name); } ~~~ 為避免使用USE_MALLOC_TRACKER來進行檢測,提供這些定義來創建這些申請,在使用USE_MALLOC_TRACKER設置,但當不設置時并不增加不必要的開銷。其目的是不增加任何不必要的開銷。下面是基本定義: ~~~ #if USE_MALLOC_TRACKER #define PZ_NEW(GroupId, Name) new(DEFAULT_ALIGNMENT, (Name), (GroupId)) #define PZ_NEW_ALIGNED(Alignment, GroupId, Name) new((Alignment), (Name), (GroupId)) #define PZ_NEW_ARRAY(GroupId, Name, Type, Num) reinterpret_cast<##Type*>(FMemory::Malloc((Num) * sizeof(##Type), DEFAULT_ALIGNMENT, (Name), (GroupId))) #define PZ_NEW_ARRAY_ALIGNED(Alignment, GroupId, Name, Type, Num) reinterpret_cast<##Type*>(FMemory::Malloc((Num) * sizeof(##Type), (Alignment), (Name), (GroupId))) #else #define PZ_NEW(GroupId, Name) new(DEFAULT_ALIGNMENT) #define PZ_NEW_ALIGNED(Alignment, GroupId, Name) new((Alignment)) #define PZ_NEW_ARRAY(GroupId, Name, Type, Num) reinterpret_cast<##Type*>(FMemory::Malloc((Num) * sizeof(##Type), DEFAULT_ALIGNMENT)) #define PZ_NEW_ARRAY_ALIGNED(Alignment, GroupId, Name, Type, Num) reinterpret_cast<##Type*>(FMemory::Malloc((Num) * sizeof(##Type), (Alignment))) #endif // USE_MALLOC_TRACKER ~~~ 下面是兩個例子,說明在代碼中,帶標簽和不帶標簽內存分配的比較: 分配器的跟蹤由容器來完成 在命名分配空間時出現了一個問題,用一個簡單方法來識別,我們使用容器來處理。引擎中所有對象幾乎不可能只有一個單例,在做游戲時,可放置的例子,幾個玩家,所以引擎中使用很多容器。在使用帶容器的分配器時,使用一個通用的名字是沒有什么用處的。我們來看看這個FMeshParticleVertexFactory::DataType例子: ![這里寫圖片描述](https://box.kancloud.cn/2016-05-05_572b008680e26.jpg "") ![這里寫圖片描述](https://box.kancloud.cn/2016-05-05_572b0086a0936.jpg "") ~~~ /** The streams to read the texture coordinates from. */ TArray<FVertexStreamComponent,TFixedAllocator<MAX_TEXCOORDS> > TextureCoordinates; ~~~ 在容器中,一個普通的名字分配器類似于“TFixedAllocator::ResizeAllocation”。沒有太多意義。反之,對于容器來說,一個好的名字像這樣“FMeshParticleVertexFactory::DataType::TextureCoordinates”。因此,我們需要給容器標記名稱和分組,如此以來,當一個分配器在容器中后,通過容器的名稱和分組獲得所有的內存分配。因此,我們需要改變容器,讓分配器額可以使用這些容器。這將涉及到當使用USE_MALLOC_TRACKER時,需要為每個容器添加指針和一個32位的無符號整數,并修改必要的構造函數來添加選項信息。TArray的一個構造函數如下: ~~~ TArray(const uint32 GroupId = GROUP_UNKNOWN, const char * const Name = "UnnamedTArray") : ArrayNum(0) , ArrayMax(0) #if USE_MALLOC_TRACKER , ArrayName(Name) , ArrayGroupId(GroupId) #endif // USE_MALLOC_TRACKER {} ~~~ 這樣以來,我們可以把必要的信息傳遞給分配器來標記這些分配。接下來是要保證把這些改變信息傳遞到底層內存分配器中被使用。這些容器分配器通常使用FMemory來分配內存,FContainerAllocatorInterface定義ResizeAllocation 函數來做實際的內存申請。與之前的修改一樣,我們需要為內存分配添加名稱和分組。 ~~~ #if USE_MALLOC_TRACKER void ResizeAllocation(int32 PreviousNumElements, int32 NumElements, SIZE_T NumBytesPerElement, const uint32 GroupId, const char * const Name); #else void ResizeAllocation(int32 PreviousNumElements, int32 NumElements, SIZE_T NumBytesPerElement); #endif // USE_MALLOC_TRACKER ~~~ 同樣,因為我們不想使用那個ifdefs來填充引擎的代碼,我們再次使用定義來簡化: ~~~ #if USE_MALLOC_TRACKER #define PZ_CONTAINER_RESIZE_ALLOCATION(ContainerPtr, PreviousNumElements, NumElements, NumBytesPerElement, GroupId, Name) (ContainerPtr)->ResizeAllocation((PreviousNumElements), (NumElements), (NumBytesPerElement), (GroupId), (Name)) #else #define PZ_CONTAINER_RESIZE_ALLOCATION(ContainerPtr, PreviousNumElements, NumElements, NumBytesPerElement, GroupId, Name) (ContainerPtr)->ResizeAllocation((PreviousNumElements), (NumElements), (NumBytesPerElement)) #endif // USE_MALLOC_TRACKER ~~~ 這樣,我們可以把ArrayName和ArrayGroup傳遞給容器分配器。 在構造之后,還有一個需要修改容器的名稱或分組,因為給容器的容器命名分配器是非常有必要的。其中的一個例子就是,在任一TMap容器中FindOrAdd后,我們需要設置名稱或分組。 ~~~ /** Map of object to their outers, used to avoid an object iterator to find such things. **/ TMap<UObjectBase*, TSet<UObjectBase*> > ObjectOuterMap; TMap<UClass*, TSet<UObjectBase*> > ClassToObjectListMap; TMap<UClass*, TSet<UClass*> > ClassToChildListMap; ~~~ 這樣以來,所有的容器內存分配器有了標簽屬性。現在,我們需要的是給容器設置名稱。以FMeshParticleVertexFactory::DataType::TextureCoordinates為例,我們可以設置它的名稱和分組: ~~~ DataType() : TextureCoordinates(GROUP_RENDERING, "FMeshParticleVertexFactory::DataType::TextureCoordinates") , bInitialized(false) { } ~~~ ### 定義作用域 作為“內存申請和跟蹤”博客中一部分,為提供上下文鏈接,我提及到內存分配定義作用域的必要性。這個作用域與調用棧(它已經由MallocProfiler提供)不一樣。很多分配在同一棧中,但是涉及到完全不同的對象。在使用藍圖過程中更是普遍。正是由于這個,作用域在跟蹤或甚至帶藍圖的內存使用都是非常有用的。 為利用引擎中已有的代碼,我采用了重用FScopeCycleCounterUObject 結構體的方法,這個結構體用來在狀態系統中定義作用域的相關對象。引擎已經給他們配置了必要的作用域,并且你也可以使用FMallocTrackerScope 類來放置我們的內存跟蹤特性的作用域。也在每個FScopeCycleCounterUObject上自動創建的兩個域的可見性上做了改進,一個是對象類名的域,一個是對象名的域。這樣當我們最終創建一個可視數據工具時,對每個類名進行折疊時就會更簡單。讓我們從精靈Demo來看一看單獨作用域,它是一個感覺還不錯的復雜東西。 ![這里寫圖片描述](https://box.kancloud.cn/2016-05-05_572b0086b7092.jpg "") 我們分析作用域下的內存分配,結果如下: ~~~ Address Thread Name Group Bytes Name 0x0000000023156420 Main Thread UObject 96 InterpGroupInst 0x00000000231cf000 Main Thread Unknown 64 UnnamedTSet 0x0000000023168480 Main Thread UObject 80 InterpTrackInstMove 0x0000000028ee8480 Main Thread Unknown 64 UnnamedTSet 0x0000000022bc2420 Main Thread Unknown 32 UnnamedTArray 0x00000000231563c0 Main Thread UObject 96 InterpGroupInst 0x00000000231cefc0 Main Thread Unknown 64 UnnamedTSet 0x0000000023168430 Main Thread UObject 80 InterpTrackInstMove 0x00000000231cef80 Main Thread Unknown 64 UnnamedTSet 0x0000000022bc2400 Main Thread Unknown 32 UnnamedTArray 0x0000000023156360 Main Thread UObject 96 InterpGroupInst 0x00000000231cef40 Main Thread Unknown 64 UnnamedTSet 0x00000000231683e0 Main Thread UObject 80 InterpTrackInstMove 0x0000000028ee8380 Main Thread Unknown 64 UnnamedTSet 0x0000000022bc23e0 Main Thread Unknown 32 UnnamedTArray 0x00000000231cef00 Main Thread UObject 64 InterpTrackInstAnimControl 0x00000000231ceec0 Main Thread UObject 64 InterpTrackInstVisibility ~~~ 在藍圖的運行函數中只有17個內存分配。當我撲捉精靈Demo中實際的內存分配數為584454。唯一名稱的作用域數量高達4175。還有我們在捕捉時的內存分配為607M,而內存峰值為603M。這說明了對于這些需要內存跟蹤的必要性。 ### MallocTracker的實現 正如之前所說,MallocTracker 的使用方法與之前內存分配很相似。MallocTracker 是輕量級的,并且依據在“內存分配和跟蹤”文章中所說的性能需求。 使用方法在缺省狀態下是足夠快的,不會有太多的性能影響,并在內存方面有相當地的開銷。例如元素Demo顯示跟蹤開銷~30M,在Debug模式下CPU時間低于2ms,更不用說在優化發布版本下了。與平時一樣,在內存消耗和性能之前有個取舍,這些數字取決于我所選取的方法。還有其他的方法,可以優化性能或內存開銷,但是我想還是保持合理的平衡。 為分析應用,我們來看一個具體的例子。下面是當 FMemory::Malloc()調用時所發生的: ![這里寫圖片描述](https://box.kancloud.cn/2016-05-05_572b0086d78c2.jpg "") 1. FMemory::Malloc() 被調用,名字和分組需要一定的字節分配。 2. FMemory::Malloc()調用帶同樣參數的FMallocTracker::Malloc(),假設GMalloc指針指向的是FMallocTracker 的實例。 3. FMallocTracker::Malloc()在FMallocTracker創建期間,使用傳進來的分配器分配實際的內存,本例中是FMallocBinned類。 4. FMallocTracker::Malloc() 自動修改一些全局內存分配狀態,例如內存分配峰值內存大小,內存峰值數等等。 5. FMallocTracker::Malloc()關聯到當前線程PerThreadData 實例。 6. FMallocTracker::Malloc() 調用PerThreadData::AddAllocation來保存在此線程容器中的的內存分配數據。 7. FMallocTracker::Malloc() 返回指針給步驟三中的底層內存分配器。 ### 全局靜態 幾乎不包含全局狀態。只是給你一個快速的概覽而已。全局狀態包括: ?分配的字節。數據入棧時分配字節數。 ?分配次數。數據入棧時分配次數。數越大就會有更多的內存碎片。 ?分配字節的峰值。從MallocTracker 可用以后的最大字節分配數。 ?內存分配峰值次數。從MallocTracker 可用后,實時分配的最大數。 ?消耗字節。MallocTracker 的內部開銷字節數。 從所有線程分配內存開始,所有這些狀態自動更新。 ### 各線程的數據 為提高性能和避免多線程下內存分配和釋放的資源競爭,大部分工作在每個線程基礎中完成。所有內存分配和棧的作用域范圍被存儲在每個線程中。所有分配有一個相對的棧域定義,最大域范圍為GlobalScope。同樣的域名常出現在多域棧中。由此,為最小化內存開銷,某個線程的所有域名被存儲為獨一無二的,并關聯到域的棧上。因為域棧可以在多個內存分配中顯示,所有我們可以獨一無二的保存在域棧上。我們來看具體的例子,域名為藍色,分配器為桔黃色: ![這里寫圖片描述](https://box.kancloud.cn/2016-05-05_572b0086ea9e8.jpg "") 為保存數據,我們有三個不同的數組,它們在不同線程中不共享: ?唯一域名。保存本線程中唯一域名。至少GlobalScope 要在這里。它將會保存它們加入到棧中的新的域名。 ?唯一的域棧。它用一個固定長度的動態數組保存唯一棧,用索引指向相關的域名。 ?分配器。每個分配器的數據。它包含分配器地址,字節大小,分組和名稱,還有唯一域棧的索引。 若我們參考之前的圖,我們可以看到五個分配器。下面為5個分配器的數據存儲: ![這里寫圖片描述](https://box.kancloud.cn/2016-05-05_572b00870c8dd.jpg "") ### 重新分配和釋放內存 重新分配內存和釋放內存有點復雜,因為實際上在虛幻引擎中在一個線程中分配多個實例,然后被重新分配或在其他線程釋放或是很普遍的。也就是說,我們不能假設我們找到每個調用線程上的每個線程數據。因為那樣的話,也就意味著我們需要加一些鎖。為減少競爭,使用全局鎖而不是每個線程數據類有自己的鎖。在重新分配和釋放當前調用線程的每個線程數據時,對當前存在分配器進行檢測。若沒有找到,當前分配器在其他線程數據中在處理中被鎖定了。這用來確保減少競爭,并讓它們盡可能的忙碌。 ### 命名的處理 為使MallocTracker足夠快和內存消耗可被接受,我不得不制定嚴格的命名規范,無論是分配內存或域名命名。這種限制是,這些名字與內存的生命周期內,必須一致或比實際申請或域的存在時間要長。原因是,任何數據拷貝影響性能和內存開銷,所以只保存指針。雖然這貌似是一個復雜的規則,我覺得這個是完全正常的,因為你應該知道你分配器的生命周期。若你不知道你分配器的生命周期,為要知道這些名字要存在多久,那么你有了大麻煩處理了。 另外一個關于分配器和域名的特別應用是需要ANSI和寬字節。為使這些更透明,所有指針假定為ANSI,除非指針中的第63位字節被設置,它會假定指針指向一個寬字節。FMallocTracker提供了一個獲取為寬字節設置位的指針操作方法,并對寬字節或ANSI的FNames在必要情況下可設置。在輸出到文件時,名字是正確的并轉存到文件中。 ### 結論 只有當制作了一個可視的工具來展示數據時,我才會說這個系統真的很有用啊!你可以繼續我的工作,它真的很有用。查找碎片問題和處理內存使用使用這個數據會更加簡單。這個真的比引擎中已經提供的性能工具,內存消耗和數據質量要好。 下一步將真正全面轉換引擎使用標簽內存分配,但是仍有事情必須完成。若大部分內存沒有沖突,真的沒有必要花費太多時間來只是為了標記內存分配。它不只是更好標記大的內存分配,而是獲取更多的洞察細節的問題。盡管你會覺得標記分配器非常無聊,但是你仍會獲得有用的數據。下面是精靈Demo中截取的最大分配器的數據: ### 樣例數據和源代碼 為說明問題,我提供了樣例數據。樣例數據來自測試版本的精靈Demo的修改版本的運行。你可以在這里下載,在支持大文件的文本編輯器中打開。 若Epic Game 接受了我的代碼更新請求,你可以通過下載我上傳請求到Epic Games的代碼來查看。上傳請求的有效地址:[虛幻4上傳請求地址](https://github.com/EpicGames/UnrealEngine/pull/1500)。 Video overview. 視頻概述 [譯者注:28分鐘的視頻,有需要的可以下!] 原文地址:[https://pzurita.wordpress.com/2015/08/26/adding-memory-tracking-features-to-unreal-engine-4/](https://pzurita.wordpress.com/2015/08/26/adding-memory-tracking-features-to-unreal-engine-4/) ### -THE—END– 若有問題,請隨時聯系!!! 非常感謝!!!
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看