[程式] 了解UE4同步傳輸的開銷(Bunch Overhead)

看板GameDesign (遊戲設計)作者 (yekdniw)時間5年前 (2019/06/27 22:23), 編輯推噓1(103)
留言4則, 3人參與, 5年前最新討論串1/1
網頁版 https://yekdniwunrealengine.blogspot.com/2019/06/BunchOverhead.html 這篇文章其實算是Network Profiler (一) (二)的後續, 主要是利用Profiler分析的過程中發現replicate property已經減少很多, 可是total send Bytes沒有如預期的下降到目標, 經過追查研究後,發現問題在Bunch Overhead後, 才有了這一篇文章的內容 Bunch Overhead 如果降replicate傳輸到一定程度之後,會發現其實Bunch Overhead蠻大的。 從Profiler裡面可以看到Bunch Overhead分為 ★Bunch Headers ★ContentBlock Headers ★Content Footers ★Handles ★Export GUIDS ★MustBeMapped GUIDS 這幾個項目 如圖1.所示 [圖1.] 圖1. BunchOverhead的細項可在Network Profiler的Summary內找到 其中我只稍微追查Bunch Headers, Handles, Export GUIDS這幾項而已, 其他項目因為傳輸不多所以我沒有深入追。 Bunch Headers Bunch Header到底傳了那些,可以在 Engine/Source/Runtime/Engine/Private/NetConnection.cpp 的UNetConnection::SendRawBunch() 裡面追查到 大致上就是這個Bunch 是open/close,是不是reliable, channel index是多少等等的資訊 Bunch headers的資料量從我們開發端是很難省掉的, 不過引擎端因為Fornite持續在開發的關係,應該會不斷地改進。 例如以前有Bunch.bIsDormant現在直接被合併進 enum EChannelCloseReason Bunch.CloseReason。 Export GUIDS 這個項目主要發生在Server 要同步一個新的Actor給client的時候, 需要利用GUID跟client建立對應關係。 如果是動態生成的物件,會直接使用物件的路徑+名稱,以字串的方式作為GUID傳送 舉例來說server生成一個新的需要同步的Actor 路徑在Content/Gameplay/Character/Skill/BP_bbb 那麼生成這個actor就會產生Export GUIDS的項目 在network profiller可以看到,大小至少是 Gameplay/Character/Skill/BP_bbb 31個Bytes。 會說至少是因為前後還會再加上Prefix以及Suffix,所以實際會更多。 除此之外,如果BP_bbb這個Actor的component也勾了component replicate的話 這個component也會產生Export GUIDS的項目。 也就是說如果你的遊戲很頻繁的動態生成物件,那麼Export GUIDS這個項目會很高。 但是Export GUIDS是可以透過物件池改善的。 同一個Actor可以重複使用的話就不用傳GUID。 當然網路版本的物件池如何同步狀態又是另一個難題了~ 有關Export GUIDS輸出的程式碼大約是在 Engine/Source/Runtime/Engine/Private/PackageMapClient.cpp UPackageMapClient::SerializeNewActor-> UPackageMapClient::SerializeObject-> UPackageMapClient::InternalWriteObject 有需要可以從這幾個地方開始追。 Handles FNetworkProfiler::TrackWritePropertyHandle這個函式就是 用來處理Handles的項目,而呼叫的地方都是從 Engine/Source/Runtime/Engine/Private/RepLayout.cpp的 WritePropertyHandle觸發的 如果搜尋程式碼的話就可以知道Handle就是Server在Replicate Actor replicate變數的時候,用來告訴client後續的資料是哪個變數的 舉例來說BP_bbb有三個可同步的integer變數var1, var2, var3 如果只有var2有改變,var2的值從0變成1 那server 就需要送類似 BP_bbb (var2, 1) 這樣的資料給Client。 而var2要怎麼表示讓client知道,就是由Handle負責。 基本上就是每個變數依照順序給編號,所以var1=1, var2=2, var3=3 經由轉換後就是 BP_bbb (2, 1) 不過因為server需要讓client知道property資料傳完了, 所以最後還要傳編號0作為property的結束 也就是BP_bbb (2, 1, 0) Array property的情況就更複雜了 除了array property的index,最後也要加上編號0作Array結束的識別證明。 詳細傳Array的過程的額外細節我就沒有特別追查, 總之記得成本比一般的property多就對了。 Handle的傳輸細節 Handle的變數宣告為uint16,所以如果我們傳var2的資料(2, 1), 大小會是多少呢? 在實際輸出Handle的時候,因為呼叫了 Writer.SerializeIntPacked(LocalHandle) 所以不會每次輸出都是2+4 Bytes。但是還是有一定的大小 輸出的實際程式碼在 Engine/Source/Runtime/Core/Private/Serialization/BitWriter.cpp FBitWriter::SerializeIntPacked(uint32& InValue) 裡面,有需要可以追一下。 Worst Case 上述的範例 假設handle是1 Byte 那麼傳遞一個integer實際上是 4/(4+1) = 80% 等於有20%是overhead 如果我們要同步的變數只有1bit的boolean呢? 最後可能要傳9Bits 1/ (8+1) = 11% 等於接近90%的傳輸都是overhead... 貴爆! 改善Handle的Overhead 有一個作法可以避免replicate每個property前面都要帶一個handle 那就是把多個property包成一個structure 並且實作這個structure的NetSerialize() 如此一來 這多個property的傳輸只會共用一個handle來處理 範例 http://www.aclockworkberry.com/custom-struct-serialization-for-networking-in-unreal-engine/ 可以參考這篇文章來實作。或是直接搜尋引擎程式碼,有很多範例 缺點 整個structure實作NetSerialize後有一個重大的缺點 就是在計算變動的時候也是以整個structure作記憶體比較 所以整個structure如果只要有一個變數變動 也會呼叫NetSerialize並輸出。 原來的版本因為各個property拆開,所以每個property會獨自作記憶體比較再輸出 所以如果整個structure常常只有少部分變動的話, 有可能實作NetSerialize反而傳輸會變大。 實際上還是要在沒實作NetSerialize+Handles跟實作可能會浪費, 取得平衡點。 追蹤SerializeNewActor傳輸開銷 除了Bunch Overhead屬於比較隱密容易被漏掉的項目 Server spawn一個新的actor所需要的傳輸量, 其實在Network Profiler沒有頁面顯示出來 除了從程式碼可以稍微知道基本要傳哪些資訊 , 最後實際上同步新的actor的資料量其實是非常難知道的 這邊列出幾個我知道的項目 ★GUIDS ★Transform與速度 ★Replicate Properties GUIDS 前面有提過,這個是NetworkProfiler內就能看到的項目 Transform, Rotation, Scale, Velocity等項目是要傳輸, 但無法在Profiler內找到的。 最後是這個actor需要replicate的各個變數,如果與預設值不同就會傳輸。 這部份在NetworkProfiler可以看的到。 程式碼可以看到在傳各個Transform, Rotation, Scale. Velocity之前 會先給一個bit告知是不是預設值,如果是預設值的話就直接不傳了。 這個傳輸技巧還蠻值得學習,可以應用在NetSerialize實作的時候, 搜尋引擎程式碼也會看到這個技巧被大量應用在各個需要傳輸的地方。 然後因為Transform, Rotation等等都有自己的NetSerialize版本。 所以傳輸的資料量是變動的,造成SerializeNewActor的成本很難預估。 另一個值得注意的是Vector是使用FVector_NetQuantize10, Rotation是使用FRotator。 平常有需要同步座標或旋轉的時候,記得使用這兩種類型來降低傳輸。 以上就是有關網路傳輸開銷的內容 Gameplay Network 傳輸相關的內容會暫時告一段落,目前沒有新的項目要分享。 不過如果有想知道UE4的課題,也歡迎讓我知道,謝謝~ -- ※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 1.169.24.134 (臺灣) ※ 文章網址: https://www.ptt.cc/bbs/GameDesign/M.1561645395.A.11B.html

06/28 21:00, 5年前 , 1F
同樣未看先推
06/28 21:00, 1F

06/29 10:53, 5年前 , 2F
你有FB嗎?想訂閱
06/29 10:53, 2F

07/02 01:23, 5年前 , 3F
抱歉 可能沒時間經營FB~
07/02 01:23, 3F

07/02 01:23, 5年前 , 4F
但是我文章會出沒在discord跟PTT
07/02 01:23, 4F
文章代碼(AID): #1T5D5J4R (GameDesign)
文章代碼(AID): #1T5D5J4R (GameDesign)