​TGDC | 一個遊戲程式設計師的堅持 —— 論向量化程式設計

遊資網發表於2020-12-22
2020年12月7日,由騰訊遊戲學院舉辦的第四屆騰訊遊戲開發者大會(Tencent Game Developers Conference,簡稱TGDC)於線上舉行。來自重慶帕斯亞科技的CTO謝怡欣先生,分享了他對於向量化程式設計的一些看法。以下是分享視訊和文字實錄:


大家好,我是來自重慶帕斯亞科技的謝怡欣。

首先,我想向大家簡單介紹一下我的經歷。之前我在加拿大溫哥華工作了幾年,是Offworld Industries的一個高階程式設計師,參與制作了一款射擊遊戲《Squad》。我還做過一個手遊,叫《Lionheart Tactics》。現在我在重慶帕斯亞科技擔任技術負責,帕斯亞科技是2011年成立的,我們專注於高創意度的沙盒獨立遊戲。我先後參與開發了《星球探險家》、《波西亞時光》和《超級巴基球》這些作品。

​TGDC | 一個遊戲程式設計師的堅持 —— 論向量化程式設計

在演講開始之前,我想先給大家看一個視訊。大概在2016年的時候,我在Youtube上看到的這個視訊,來自於一個叫Mike Acton的程式設計師。他在視訊裡面就講述了一個他稱之為面向資料程式設計的概念。當時我看到那個視訊是非常的不自在,因為2016年的話,應該是屬於我剛熟悉物件導向程式設計的階段。當時他說的那些東西,基本上就是說你學的那些東西,基本都是“垃圾”,都沒什麼意義。這確實很難讓別人接受。但是我在潛意識當中,卻又覺得這個人說的話,某些觀點是挺有意義的。

我接下來要跟大家分享的視訊,就是他做技術分享之後的問答環節。我們可以看到一個比較年長的業界人士,對他分享觀點的一些質問。結果Mike Acton就直接把話給他懟回去了。請先看一下這個視訊:


確實態度不算特別的友好。一個很偶然的機會,我和一個做HR的小夥伴,就聊到了這個事情。我和她一起看了這個視訊,她就提出一個觀點,就是這個穿橘黃色衣服的Mike Acton,他的姿態是比較有攻擊性的。就是他很喜歡把手舉到肩膀以上,這樣的話實際上是給觀眾一個資訊,就是我說的東西,或者是我的地位比你們要高。可能這也是我當時聽到他說的話,覺得難以接受的原因之一。當然,如果說有機會Mike Acton能看到我今天的演講的話,我想說我肯定不會有任何的不敬,我非常欣賞和佩服您的才華和知識。

這裡有另外一個連結,這個連結就是很早以前微軟一個寫Windows的程式設計師他的一個抱怨。他就是講Windows為什麼比其他的作業系統慢。他說因為Windows程式實際上是由於商業化開發,然後迭代了很多很多次,真正寫了很多程式碼的那些程式設計師,可能早就被類似亞馬遜和谷歌這些公司挖走了。剩下的一些程式設計師,都是相對來說沒那麼多經驗,改程式碼也不知道從何下手的。

​TGDC | 一個遊戲程式設計師的堅持 —— 論向量化程式設計

從專案管理的角度說,實際上做程式碼清理是不產生任何價值的。如果是增加新的功能,那就是增加它的價值。但如果只是清理程式碼,可能就不會被你的領導所器重。

然後說一下物件導向程式設計。在我看來,根據程式設計師自身的水平,他對於這種程式設計的理解是相差比較大的。就算是很高階很有經驗的程式設計師,他對物件導向程式設計的一些設計模式,也會有一些細微的差別。在經過多次修改之後,肯定也都會產生剛才那個程式設計師抱怨的那種大片大片的死程式碼。但是你又不是很敢刪,我相信很多在座的程式設計師都有過這樣的經歷。

此外,物件導向程式設計實際上對快取是很不友好的,但是這一部分資料網上有很多,我就不再贅述了。還有一個點是,我不知道大家有沒有發現,現在主流的遊戲軟體、遊戲程式或者說應用程式,都只是用了一到兩個執行緒,很少有多執行緒能得到充分利用的。現在大多數的中高階硬體,都是支援十個以上的執行緒,而向量化程式設計的話,實際上是可以充分利用這些計算資源的。

接下來,我想說一下為什麼需要了解向量化程式設計。在我看來,向量化程式設計實際上是提高程式設計師的內力。內力是什麼東西?就比如說張無忌他的內功深厚,他學了九陽神功之後,感覺他學其他的武功都很快,基本上就是信手拈來。如果你作為一個程式設計師,有很強的內功的話,那你要學那些比如說遊戲客戶端、伺服器、全棧工程師,包括多執行緒程式設計什麼的,都會變得很容易。我覺得至少從我一段時間的學習經歷來看的話,我覺得真的是有這種效果的。

​TGDC | 一個遊戲程式設計師的堅持 —— 論向量化程式設計

還有一點,簡單說一下。向量化程式設計實際上是簡化了多執行緒加鎖的邏輯,基本上是沒有什麼鎖的。或者說,鎖這個概念已經從框架層就給你模糊掉了,你是基本用不到這種東西的,所以說是一個很好的簡化。

向量化程式設計提高了程式碼的可讀性。大家可以想一下,比如說你有一個函式,函式裡面有很多很多行,實際上每一個行都是一個節點,然後每一個節點如果說是呼叫另外一個函式,它實際上在那個地方,就是一個分支。它就是可能分到另外一個深度的指數下面去了。那個東西又可能呼叫其他的函式,就分得更細一點。物件導向程式設計實際上是非常複雜的,基本上是比較鼓勵這種分支。

向量化程式設計實際上它也是一個樹形結構,但是相對來說要平坦很多,就是樹的複雜程度要簡化很多,也比較線性化。還有一點是,作為程式設計師,你學物件導向程式設計也就學幾年,我覺得應該差不多掌握以後,就可以考慮去學習一些新的技術和新的研究方向了。我覺得向量化程式設計就是一個不錯的選擇。

說到向量化程式設計,就不得不借助ECS框架。ECS在網上實際上是有非常多的很成熟的教程的。就是說為什麼它的速度快,這些快取、資料對齊這一系列東西,在這裡我就不再贅述了。我就做個很簡單的介紹,然後再加上我自己的一些理解。

​TGDC | 一個遊戲程式設計師的堅持 —— 論向量化程式設計

ECS就是Entity Component System的一個縮寫。Entity就是數字,是一個索引。Component就是元件,就是純資料的東西。當然這裡實際上在我們的實際開發當中,Component上面也可以帶一些簡單的方法,但是那個方法只是管自己的邏輯,就不會涉及到和其他資料型別的互動。肯定就是說Component也不會含有指標或者是任何複雜的那種資料型別。它可以是陣列,可以是Entity,也可以是引用到另外的一個Entity,這個是沒問題的,因為Entity也是數字對吧。System就是系統,它是對指定Component結合的Entity進行資料變換。

在這裡我想說一下,它跟傳統的Object Oriented Programming差別沒有想象中的那麼大,從概念上幾乎是一樣的。Entity對應那邊就是Object,Component對應那邊可能就是Object上面的一個屬性。你像比如說一個英雄,在ECS的話,英雄可能就是Entity,他的那些屬性,就是那些Component。在物件導向程式設計的話,英雄就是Object,他的那些屬性,比如說他的Class裡面,可能有其他的一些欄位。那麼從概念上面來講,這個基本上是一對一的。區別就是在於物件導向變成裡面的那些方法,實際上是和它的類是寫到一塊的。在那個方法裡面,基本上是想怎麼來就怎麼來。就是你想訪問什麼樣的資料,你就訪問什麼樣的資料,沒有什麼規定。ECS裡面的System的話,對資料的訪問是非常嚴格的。這也就是可能會勸退很多程式設計師的一個點。

​TGDC | 一個遊戲程式設計師的堅持 —— 論向量化程式設計

我想再引入一個維度,就是從頻率這個維度來看。遊戲邏輯的程式設計頻率維度可能分低頻率和高頻率。低頻率時間基本上可以把它歸納成在一幀裡面,就能夠開始並且結束的。就是完成它所有的資料轉換的一個事件,就比如說開始播一個動畫,結束播一個動畫; 怪物的產生,或者是說死亡;或者是說按了一個什麼鍵,這些都是低頻事件。高頻事件,就是一個持續的連續的行為。比如說一個角色,他在一直不停地動,說著是說不停地在播一個動畫,他需要每一幀都去維護。這個東西肯定也可以從函式的入口來判斷,比如說是Update,一般Update就是比較高頻,做按鍵的檢測或者滑鼠的檢測,都是在Update裡面。那麼檢測實際上也是個高頻操作。但是至於檢測到按鍵之後做的那些事情,那個就是低頻事件。

我想再說明一點,就是從頻率這個維度來講,向量化程式設計可能是比較初級的。這是我在摸索過程當中,尋找出來的一條路徑。我不排除有其他更好更高效的維度,我就想引入相對來說比較簡單的程式設計例項。這個例子就是在遊戲當中,比如說你有NPC,他可能每一幀都要去檢測他的視野範圍裡面有沒有其他陣營裡的人。如果有,可能這個NPC就需要做一些反應。這段程式碼是虛擬碼,簡化了很多的一個版本。我只是想讓大家能夠看一看就好了。在Update裡面,就是做一個物理上面的查詢,Get OverlapSphere,把自己坦克的位置放進去,然後把自己的視野半徑放進去,最後看Collider,就是有沒有碰撞體。要是有碰撞體的話,在它上面去再去拿一個看它有沒有Tank的這個Component。如果說有,再生成特效,生成飛行道具,播一些音效這些之類的東西,這一段程式碼大家可以看一下。

​TGDC | 一個遊戲程式設計師的堅持 —— 論向量化程式設計

實際上,Physics.GetOverlapSphere,它實際上是一個很高頻的操作。就是我不管你這個坦克,只要是活著的,只要在那裡沒有做其他事情,它就會執行這個程式碼。在Colliders Length大於0那一段程式碼裡面,它實際上是一個低頻程式碼,你真正遇到敵人了,你才會觸發的邏輯,就是這樣的一個高頻和低頻的分段。

然後就是向量化程式設計的一個例項,這個我引用了Unity dots最新出的Date-Oriented...TechStack的一些API。肯定也不是很完整,如果大家真的要去用的話,可能也要去參考一下他們官方網站上面的一些文件,這裡我大概有那個意思就行了。

​TGDC | 一個遊戲程式設計師的堅持 —— 論向量化程式設計

第一段就是EntityManager GetCompoentArray,就是把所有的坦克,比如說你有100個坦克、1000個坦克,把所有坦克的位置資訊,放到一個陣列裡面。第二句就是把它的視野半徑放到一個陣列裡面。第三個就是說把它周圍有沒有東西這個狀態,放到一個陣列裡面。然後那個Entities for Each就是dots,就是ECS很標準化的向量化的一個操作。就是把所有的坦克,它的位置還有它的視野做一個查詢。使用C++比較多的小夥伴就會發現,這個Physics.GetOverlapSphere實際上是一個const,是一個常稱之為常量函式的東西。它這個函式是不會改變任何狀態的,這種函式實際上是在向量化程式設計裡面是非常友好的。因為它不涉及到racing condition,就是不會產生那種比如說一個執行緒在讀一個記憶體,或者是說同時另一個執行緒又在往那個記憶體或者說往那個變數裡面寫東西的那種情況。這種對於多執行緒是非常友好的。

​TGDC | 一個遊戲程式設計師的堅持 —— 論向量化程式設計

下面那個HITS,相當於是把它返回的結果就放到那個裡面。如果說你有十個盒子十個執行緒,然後這裡面有1000個坦克,每一個執行緒可能分到的就是100個坦克,那麼他們就分配1000個陣列。第一個執行緒可能就是填充HITS陣列從0到99的位置。以此類推,Schedule就是做這個事情的。最後那個Complete Dependency就是它主執行緒上面的一個阻塞。它的意思就是說等所有執行緒的工作全部都做完了以後,我們的主執行緒然後再開始往下面走。接下來就是和剛才的那種順序化寫法是一模一樣的。就是我現在有這個結果,我怎麼去做響應,比如說create effect,就是產生特效,創造飛行道具,或者是說播音效也好,這些東西就跟之前的實際上是一樣的。大家可以發現這個東西實際上就是在向量化一個高頻的操作,然後低頻的操作還是按照之前傳統的那種順序化的寫法寫出來。

​TGDC | 一個遊戲程式設計師的堅持 —— 論向量化程式設計

這裡我想再做一個比喻。因為我平時有時候也玩一下樂高,有一次我在拼這個起重機的時候,我就發現一個比較有趣的事情。這是當時我拼的起重機的底盤,底盤有四個輪子,要支撐這四個輪子,就需要用到三個觸角的那種零件,左邊右邊各有一個,背後也有兩個,一共四個。拼裝這個零件的話,就是下面的這個步驟。大家看到從158-165一共八個步驟,172-179,又是八個步驟。你會發現,這個八步和那個八步實際上是一模一樣的,只是方向不一樣而已。

​TGDC | 一個遊戲程式設計師的堅持 —— 論向量化程式設計

當時我就在想,它只需要重複四遍做四個輪子而已,那如果說現在要重複一百遍的話,你會用什麼樣的一個流程去做呢?你是會按部就班地從零件包裡找那兩個零件,然後按照說明書的步驟一步一步拼起來嗎?那你光是找零件的動作,就需要重複8x100=800次,相當的耗費時間。但是也有另外一個辦法,比如你先做158,再做172,然後再回過頭做158,再做172,你一次性找100個158的那兩個零件,找100對就行了。同理,159也是找100對那兩個零件。用這個流程的話,找零件這個步驟你只會重複8次,其他時間你都在做很高效的拼接。

​TGDC | 一個遊戲程式設計師的堅持 —— 論向量化程式設計

這個例子實際上是可以體現向量化程式設計的一個很核心的思想,就是順序執行和向量化執行的差別。遇到這種數量級比較大的,100次或者說更多次的這種操作,你可以想辦法把它向量化,然後再對於那種低頻的操作,還是以順序化的方式去書寫。

​TGDC | 一個遊戲程式設計師的堅持 —— 論向量化程式設計

最後我再講一下向量化變成在實際應用當中的優劣勢。優勢還是挺明顯的,剛剛也大概提了一下,它的程式碼呼叫數的深度,低於物件導向程式設計,能夠產生爭議的點比較少,程式碼管理的成本也會低一些。System程式碼基本上不需要時間去讀,你只要把資料結構定義好,你這個資料是用來幹什麼的就行。System程式碼實際上就是把資料A變成資料B,就是一個陣列,陣列A變成陣列B,就是一個非常簡單非常透明的操作。

劣勢,確實這個技術的起點會比較高,在寫System程式碼的時候,需要把所有的資料的讀寫關係,是隻讀還是隻寫,還是又讀又寫,這些東西要把它摸索得很清楚,你才可以寫出比較好的System的程式碼。在這方面確實門檻是比物件導向程式設計是要高一些的。然後演算法從單執行緒改成多執行緒,難度確實是比較高。在這個點上我想給大家一個建議,一開始不要想把所有的演算法,所有的在順序化,或者說物件導向程式設計的那種思維,想出來的那種演算法,都把它改成多執行緒。我覺得這是一個難度比較大的問題。可能從專案管理上面來說,可以先就用單執行緒寫一下就好了。如果說這個東西真的在最後產品測試的時候發現花的時間太多,我們需要優化,然後在那個時候,再考慮怎麼把它的高頻的那些操作向量化。

還有最後一點,如果說用ECS這套框架,寫順序化執行的程式碼,它的boilerplate會比較多。如果說是物件導向程式設計,你有一個例項,你點一下,自動就把它的屬性這些成員變數就給你點出來了。在ECS就用Get Component data,如果說對資料有改動,還會再用Set Component data,把它賦值賦回去,大概就是這個樣子。

然後下面是我在自己學習向量化程式設計的時候,自己摸索做的一個展示。這是一個比較典型的塔防的一個DEMO。你們可能會發現有的時候這些小蟲子會消失,實際上它們是被炮塔攻擊了,只是沒有新增特效。大家可以看到比較多的蟲子,它們是沿著這個地形去走的。我特別花時間做了一個爬牆的邏輯,就是每個蟲子實際上都做了兩個射線查詢,來判斷自己是不是在牆上。當然有些岩石是沒有做碰撞體的,所以說它可能就是從岩石上面就穿過去了。當時也是時間比較趕,所以說也沒有做太多的那種細節的打磨。


在這個場景裡面差不多有七八千到一萬個蟲子,每一幀都做了射線查詢,以及它的周圍有哪些蟲子,避免蟲子與蟲子之間有穿插的現象,當時也是在開發環境裡面維持了有三十幀的樣子。

今天我的演講就到此結束,謝謝大家!

來源:騰訊遊戲學院
原文:https://mp.weixin.qq.com/s/PrU0o44PGYverwrQbJcchg

相關文章