關於UE4++、GC與標準庫的一些雜談
從上一節 (http://dorgon.horizon-studio.net/archives/773) 的內容我們知道官方堅持不將Script導入引擎的原因,但對於稍微進階一點的程序員可能就會開始思考:既然都使用C++了,為什麼還要在引擎中實作Garbage Collection(GC)?直接使用RAII的機制不是能夠更進一步的避掉因為GC而產生的效能耗損? 先撇開效能問題,其實導入GC的優點很明顯:它幫助我們從複雜的記憶體管理議題中逃脫出來,進而能夠專心在遊戲邏輯的撰寫上。C++雖然是一個非常強大的語言,但反過來說,卻也是非常的複雜。 例如上面所提到的RAII,一般最常使用smart pointer的概念來實現。若沒有經過特別的訓練的話,則很有可能就會迷失在shared pointer跟weak pointer的使用情境之中。更糟的是,若使用者打從一開始就不知道weak pointer的概念,則有很大的可能會寫出物件間循環參照的邏輯,進而造成物件的記憶體永遠不會被釋放。 因此,並不是所有的使用者都能夠正確掌握語言的特性,為了理解並正確使用這些功能,是需要好幾年的經驗與養成才有辦法好好的揮舞這把強力武器。 而UE4為了將C++複雜的特性從一般使用者中隔離出來,所採用的並不是跟其他大部份的遊戲引擎一樣導入Script創造一個Sandbox環境的方法。相反的,它在原本的C++語法之上更進一步的設計了一套Reflection(UProperty)跟Garbage Collection(GC)的機制。 雖然導入GC會影響效能表現,但相對的C++整體在使用上的複雜度也降低到跟其他高階語言差不多的程度。而且,由於UE所實作的這套GC機制是為了遊戲這類即時互動性高的系統所設計的,因此裡面的GC機制不僅有許多回收的優化機制(見Figure 18),而且其採用的「漸進式」方法不會在GC發動時讓整個遊戲卡住,意即,每次進行回收的動作不會超過所設定的秒數(預設為0.002秒)。 Figure 18 GC預設設定,於Project Setting->Engine->Garbage Collection選項。其中在Optimization中的Create Garbage Collector UObject Clusters這個選項,在目前的版本(4.15)只支援Material跟Particle。 大部份的應用在這套系統下面其實不會有什麼大問題。 當然,C++裡面一個非常重要的設計哲學:不要為不需要的功能付出效能上的代價 (Stroustrup, March 1994)。對於更進階的效能需求,其實我們還是可以使用原本標準庫中所有的C++方法。當我們的類別不是繼承自UObject體系時,是可以使用自己的記憶體管理機制。基本上,UE4++包含了所有C++的語法特性,然後再加上一些給UHT自動產生reflection資訊的標記語言。在4.15版本釋出時,引擎更是在全平台支援C++14所有語法的特性。 只是當我們開始深入研究相關的方法的時候,會發現:為什麼引擎中實作了許多跟標準C++相同概念的方法?例如TArray對應到std::vector;TMap對應到了std::map。為什麼不使用標準庫中的方法而要自己實作一套呢?難不成UE4自己實作的方法有比標準庫中的還要快嗎? 其實,速度並不是主要的考量。 根據官方於論壇上的說法,歷史因素才是造成目前設計的主因。要知道,UE4是一個已經存在好幾十年的引擎,在第一代引擎釋出的那個年代其實也正是 C++發佈第一套標準 (C++98)。由於標準剛發佈時的各方實作並不是很穩定,各個不同平台下的行為模式還不是很一致。為了保證穩定性,當時的引擎有了自己的一套實現,並且一直延用並演化到現在。 由於現在C++標準庫已經非常的穩定,官方是有在思考將引擎中對應的方法全部替換成標準庫相關實作的可能性。只是,這部份的變更目前並不考慮在第四世代的引擎中實現。或許在「第五世代(UE5)」釋出的時候,我們就可以看到引擎跟標準庫有著緊密的結合。 本系列文章為個人原創,未經授權,謝絕轉載