世界越大,一天越難結束:日結效能的三道關卡
《轉生者們》的世界裡,每位居民、每間商會與公會都在各自過日子。每到一天結束,世界要做一次日結:工房結算產量、商會進貨、公會收購、居民吃飯看病、帳款入帳……全部算完,才能進入隔天。
世界還小的時候,這一切都很順。但隨著城市與人口變多,我們接連撞上了三道關卡。
第一關:一開觀測視窗就卡
開發時,我們會開「觀測站」視窗即時查看世界的狀態。問題是,畫面每次重繪都直接去翻世界的資料,而世界本身也在同一條執行緒上運作。結果就是:畫面在等世界,世界也在等畫面,整個編輯器跟著卡頓。
這不只是開發工具的問題,之後玩家看到的遊戲介面也會遇到。
解法:讓世界搬到自己的背景執行緒上運作,每秒發布一份「快照」。畫面只讀快照,不再碰正在變動的世界。一秒一次對人眼來說已經足夠,而世界終於可以專心跑自己的事。
第二關:一天比一天更難結束
世界搬家之後,新的問題浮現:模擬速度追不上設定,而且日結一天比一天慢。第一天只要 1.6 秒,幾天後最慢的一天竟然要 22 秒。
我們把日結的每一段分開計時,最後發現:大約九成的時間,都花在「每完成一張委託,就把整份委託資料寫回資料庫」。但在玩家真的存檔之前,根本沒有人需要這份資料。
解法:只在存檔時才寫入。修完之後,日結回到每天 1~2 秒,而且不再隨天數變長。
第三關:想同時做,卻怕算錯錢
接下來的目標,是讓世界變大時依然撐得住。最直覺的做法是併行:日結裡有許多彼此無關的工作,例如甲商會進貨、乙商會進貨、居民各自吃飯,沒道理一定要排成一列。
但併行有一個致命的風險。想像兩個人同時對同一個錢包做事:
錢包裡有 100 枚銅幣。A 收到 30 枚,B 同時收到 50 枚。兩人都先看到「100」,各自算出 130 和 150 再寫回去,最後錢包裡是 150。那 30 枚就這樣憑空消失了。
調查時我們還發現,這種風險其實早就存在:居民的行為和日結本來就可能同時動到同一個錢包,只是還沒有被觀察到。
現在的設計:分隊伍,共用窗口排隊
我們沒有大改每個系統的寫法,而是只改了兩個地方:
- 一位排程員:負責把當天的工作分隊伍。同一家商會、同一位居民的事永遠排在同一條隊伍,不同擁有者的事就可以同時進行。每個階段結束時,等所有隊伍都做完,才進入下一階段。
- 共用窗口加鎖:錢包、市場、人際關係、公會成員名冊這些大家都會碰的東西,各自加上「一次只服務一位」的鎖。鎖只包住「看、算、寫回」那一小段,而且辦事途中不准跑去別的窗口插隊,避免兩個人互相等對方而卡死。
另外還有幾條規則:
- 兩種模式隨時切換:「序列模式」照舊一件一件做,方便抓問題;「併行模式」用多條隊伍。量效能時切換比對,就知道瓶頸在哪。
- 兩個市場的日結不同時進行:市場缺貨時會向別的市場買,如果兩邊同時做,可能互相等對方。
- 怎麼確認沒算錯:併行之後,誰先買到貨會因時機而不同,所以我們不要求兩種模式的結果一模一樣,而是檢查守恆:全世界的錢與貨物,增減都要對得上帳,不能有負數,也不能有同一個人重複加入同一種組織。
成果
在四座城、128 位居民的測試世界裡:
- 整天的日結從平均約 3.5 秒降到 2.6 秒。
- 其中各家機構的結算段,速度約是原本的 2 倍。
- 兩種模式都跑出零錯誤。
不是每一段都能併行。有些階段本來就有先後順序,例如要先上架,別人才買得到,這些我們刻意保留成一件一件做。
量測過程中也看到了下一個瓶頸:世界每一刻都在替所有角色更新狀態效果的倒數,連遠方城市的居民也包括在內。這部分已經在規劃新的做法,之後有進展再跟大家分享。