切換
舊版
前往
大廳
主題

又是Job system

Lumi | 2015-06-16 00:36:07 | 巴幣 0 | 人氣 361

上一篇文章中,我照自己的需求實作了一個job system,其中有個功能不是很令人滿意。

假設目前有job A與job B,A做完才能做B。在上文架構中,能夠在執行job A時產生job C,且可允許C做完才會做B。但是在使用該架構時,無法以直觀的方式達成這功能。理由是job內部不能等待其他job完成,更精確地說是任一worker不能以任何原因等待其他worker。效率問題暫且不提,若每個worker都產生新的job然後等待其他worker完成該job,就沒有worker會做事了。為了減少worker等待的情況,該架構還實作了限制特定job同時被執行的數量。

現在有新的方法可以解決這些問題了,Naughty Dog在GDC 2015發表了他們的job system
裡頭提到利用Fiber使得job內的等待動作成為可能。原理是job要等待時就把目前的fiber存起來,然後從fiber pool拉出一個新fiber來執行別的job;要切換回等待中的job時,就把目前的fiber丟進pool裡,再還原先前暫存的fiber。

此法可使所有worker無時無刻都在做事,且由於等待的限制解除,job程式碼的撰寫也變的更直觀。這個方法看起來太美好了,因此我又實作了一份job system。這次的實作目標有以下幾點:
  • 使用boost coroutine來搞定fiber機制
  • 拔掉manager thread
  • 拔掉限制特定job同時執行數量的功能
  • 保留其他已實作的功能
由於少了manager thread來搶CPU,料想效率應該會比上個版本還快,但測得的時間卻不如預期,乾脆趁此機會來個大改版。從如何送job給worker到worker如何取出與執行job,實驗了許多種版本。最後程式碼大部份被改寫,核心概念改為偏向Doom3 BFG的方式,無論是傳遞job或執行job的效率都勝過上個版本。


以下再貼一次之前的測試數據來對比新架構的效能,設定Job數量為2000個。值得一提的是新架構為了測試Fiber切換效率,特地新增一個job專門用來送出2000個job,並等待該2000個job完成。2000個job的程式碼與上篇文章相同。

對照組,完全不使用平行處理,測試1000次得到的平均時間
迴圈1000,需時2.359ms
迴圈5000,需時11.705ms
迴圈10000,需時23.343ms

使用我的簡化版Doom3 BFG Job架構,測試1000次得到的平均時間
迴圈1000,需時0.704ms,效能提升3.35
迴圈5000,需時3.127ms,效能提升3.74
迴圈10000,需時6.200ms,效能提升3.77

使用上篇文章架構測試1000次得到的平均時間
迴圈1000,需時0.782ms,效能提升3.02
迴圈5000,需時3.181ms,效能提升3.68
迴圈10000,需時6.194ms,效能提升3.78

使用Fiber的新架構,2000+1個job測試1000次得到的平均時間
迴圈1000,需時0.653ms,效能提升3.61
迴圈5000,需時3.072ms,效能提升3.81
迴圈10000,需時6.084ms,效能提升3.84

這回總算是全面贏了我的簡化版Doom3 BFG,主要應是少了job list內sync機制的關係。Worker在衝刺job list的時候不需要每執行一個job就檢查一次sync,也沒有被sync中斷的可能,切換job的成本可減到最低。以往一直認為coroutine在多核心環境下無用武之處,要求效能的程式會選擇使用thread而不是coroutine,現在看來我錯了,而且錯得很開心。

創作回應

=星之卡比=
您好,之前發現了這個網誌以後就開始定期點閱這裡,感覺大大都很認真地在研究這些東西和分享一些經驗,對正在學習的小弟來說是非常有幫助(感覺台灣很少這種分享遊戲渲染技術的文章)。 這邊想問一下,個人在猜測Naughty Dog主要用Fiber是不是為了能容易Suspend的緣故,畢竟依照傳統的做法,他將Thread鎖緊HW Thread以後就可以去Job Queue裡面取得工作來運行,不需要再另外用Fiber來封裝Job的緣故,所以在猜他之所以用Fiber當容器是為了簡單地儲存當下暫停所需要的Context?
2015-09-10 18:30:23
Lumi
你抓到重點了, Naughty Dog要達成GDC2015文件中所列的目標
使Job可以暫時停下來是達成目的的其中一個關鍵
我目前遇到所有需要等待的狀況都可以用切換fiber達成
譬如等資源在背景讀取完、製造一堆新的job並等待它們執行完
等別人釋放lock、等gpu執行完...

我寫文章的初衷就是想要更多人加入3D繪圖的圈子,很高興你喜歡這些文章
考慮到現在正處於新舊圖形API轉換的尷尬期
加上我的r9 380沒辦法正確執行我的OpenGL程式=_=a
所以中途變成研究別的東西,像是Raspberry Pi...
等到我的程式轉換成Vulkan後, 寫作重心才會回到渲染技術的實作心得上
2015-09-10 19:59:17
=星之卡比=
(看來小屋好像沒辦法及時更新留言,我有改掉裡面一些贅字但還是沒被更新QQ)
我個人也是在等待新世代的API,畢竟兩者的設計就如同Unity那篇所說天差地遠,雖然一方面也是無奈考生XD(不過我並沒有像大大那樣實作IBL或是SSAO那類的核心技術,只是概念上有個基礎而已。換句話說就是看技術簡報看爽的(汗))

冒昧地在問下,之後有考慮公布部分的實作程式碼嗎?個人看久了文章發現雖然理論完善規完善,但似乎實作技巧跟有些細節似乎都會刻意被藏一手XD往往總在一些問題思考很久而不得其解,想說如果有別人的作法可以參考,或許可以讓其他跟我有類似想法的人參考交流?不過這是您的心血,當然也可以不公開,只是想問下而已。

最後,之前看到有其他文章一直在婊AMD的OpenGL Driver沒想到不是傳聞而是真有其事XD我覺得這也是很多遊戲商不願採用OpenGL的原因吧,驅動品質不太一致。但我想隨著新世代API的登場,一堆事情都轉到開發者上的情況下,似乎驅動要寫得太差勁也是一件難事XD
2015-09-10 20:11:34
Lumi
我沒公布實作與細節有幾個原因
其一, 我的目的一方面是讓大家對3D繪圖程式實作產生興趣
另一方面是掃盲, 因為太多人只憑感覺來評論遊戲畫面的技術好壞
所以我只會著重在原理解說, 提供參考資料
還有展示不同特效對畫面產生什麼影響

其二, 文章背後隱藏的知識可能有好幾篇論文或書本數個章節之多
我不太可能在一篇文章內把所有事情解釋清楚
在此情形下給了實作也沒有多大意義

其三, 我在寫文章的時候有部分刻意保留, 點到為止
一方面使文章簡短, 一方面希望讓大家去我列出的參考連結找答案
甚至勾起興趣開始動手實作, 而不是把我的文章看完就結束了
畢竟最新技術的文件都不是中文, 大家得養成看英日文技術文件的習慣
所以我不會在文章內手把手地教到會

最後, 看了文章會思考問題非常好, 歡迎提問!

目前介面是這樣設計的
typedef void(*JobFunc)(void *);

struct Job
{
JobFunc func;
void* data;
};

Job job = { (JobFunc)&JobSoundUpdate, &soundData };
JobInfo jobInfo;

jobManager->Submit(&job, 1, JT_LIFO, &jobInfo);
jobManager->Wait(&jobInfo);

JobInfo用來儲存job之間的dependency和紀錄該job是否執行完畢
2015-09-11 00:46:04
=星之卡比=
了解,不過我沒說清楚可能引起大大誤會,我說的是那些技術簡報的資料也是一樣。通常很難幾份資料就可以搞定,得去想辦法找些別人的Code來改。
希望之後能跟大大交流,到時候還請您指教了^^
2015-09-11 17:45:14
Lumi
技術文件不會從基礎開始教已經是常態了, 從頭教反而是少數
只能自己找參考資料來看, 還要找參考資料的參考資料的參考...
撐過去就是你的[e12]
2015-09-11 19:01:21
空之境
工作分配寫到最後都有種在寫os 排程器的感覺xd
2016-06-28 22:49:44
Lumi
我的實作是worker們去搶工作而不是分配工作給worker喔
2016-06-28 23:02:29
空之境
這樣搶工作是critical section吧
2016-06-28 23:05:45
Lumi
我的實測結果是用搶的比分配法還快,你可去看doom3 BFG原始碼是怎麼搶的
2016-06-28 23:13:08

更多創作