2017年10月27日 星期五

大量運行 Unity Headless Player 遭遇的坑

Unity 支援將專案輸出為 Linux 平台上以 Headless 模式運行的 Player。Headless 指的是沒有圖形環境 (Ex: 沒有執行 X-Server)、甚至沒有接螢幕的電腦。通常放在機房中代管的伺服器、或是雲端平台上租用的虛擬主機都是以 Headless 的方式來運行。Headless Player 在應用上除了可利用來製作簡單 Server 程式之外,也可以用來製作「特殊的」客戶端版本,例如開發線上陪玩的 BOT 機器人、或是功能測試機器人等等,這麼做的考量點通常是想最大程度地共用既有的客戶端程式碼。

當我試著要在一台 Linux 機器上大量跑 Headless Player 時,總會發現運行到 170 ~ 180 隻後,系統就會開始出現無法 fork 的錯誤訊息,甚至整個系統都被癱瘓無法做事。就算是空的 Unity 專案也會有這個現象,因此懷疑一部分的原因出在 Unity 本身。


事發的當下我有對環境做了一些檢查:
  1. 檢查 ulimit -a 的運行結果。open files 與 number of process 的上限都還未到。
  2. 檢查 lsof -u {user} | wc -l 的數量,也還未達到 sysctl fs.file-max 返回的值。
  3. 主記憶體還未用盡 (根本用不到一半)。
同台主機先前曾運行了另一個以自製引擎開發的遊戲的壓測機器人程式,壓測時都是近萬隻程序同時在運行的,也沒發生什麼大問題。因此一開始對這現象有點摸不清頭緒。一陣好找之後,才發現問題是出在 pid 的空間不夠用了。

Linux 系統上,每一個程序運行時,都會配到一個獨一無二的數字,那個數字即為 pid (process id)。但其實 pid 的粒度並不只到 "process" 而已,而是細到 "thread"。也就是說,運行中的「每個 thread」其實都配到一個獨一無二的 pid。而 pid 可能的值域,初始只有 0 ~ 32767。可由 cat /proc/sys/kernel/pid_max 來得知目前的 pid 上限值。所以每當 pid 用到 32767 時,就會再回頭去由 0 開始,找空閒的 pid 再分配出去。

請注意,如果你的系統是受 systemd 管控的,那麼你能夠佔用的 pid 數量可能會更少。這是因為 systemd 也有透過 Linux Kernel 的 cgroup 功能對每個用戶做資源管控。可以查看 /sys/fs/cgroup/pids/user.slice/user-{uid}.slice/pids.max 這個檔案的內容來得知目前帳號能運用的 pid 數量上限 ( {uid} 請自行代換成你的 user id )。在我的 Ubuntu 16.04.3 機器上,這個值是 12288。這是由 /etc/systemd/logind.conf 這個設定檔在控制,檢查內容會發現有個 UserTasksMax 的預設值正好就是這數字。

那麼運行一個 Unity Player 會佔用掉多少 pid 呢? 試著帶起一個 Unity Player,去檢查 /proc/{pid}/status 的數據 ( {pid} 代換為由 ps 或 top 等觀察到的 Unity Player pid ),會發現它在背景竟然創建了 63 條 thread (我使用的 Unity 版本 為 5.6.3f1,空專案的情況)。因此若沒去調整 pid 總量的話,我只能跑  12288 / 63,大約 195 個左右的 Unity Player 程序。實際情況會更少一些,因為還有別的程序也在佔用 pid,因此確信這就是主要的原因。

排除方式:

以 root 身份,透過以下指令:

echo max > /sys/fs/cgroup/pids/user.slice/user-{uid}.slice/pids.max

暫時把該用戶的 pid 管制用量移除,使之能利用全部的 pid 空間。若要永久性的修改,請編輯 systemd 的設定檔 /etc/systemd/logind.conf,修改 UserTasksMax 的值。設成 infinity 即代表無上限。修改後重啟 systemd-logind 服務: service systemd-logind restart 。重啟後現有已登入的使用者要登出再登入才會套用新設定。

至於系統的 pid 總量上限,在 32-bits 的機器上最大就只能到 32768,因此無解。但在 64-bits 機器上則最多可以放大到 2^22 (4,194,304,詳見 man page)。需以 root 身份執行以下指令:

echo 4194304 > /proc/sys/kernel/pid_max

來暫時調整 pid 總量上限以供測試。如要永久性的設定新的 pid 總量上限,可編輯 /etc/sysctl.conf ,加入以下設定:

kernel.pid_max=4194304

完成上述調整後就可以盡情放量,直到把 CPU 或記憶體吃好吃滿為止。

注意: 如果目的是為了要進行類似機器人的動作,則應調降 Unity 專案中的 target frame rate。因為在 PC 平台上預設的 target frame rate 值是 0,也就是無上限。這會導致 Unity Player 的 CPU 佔用率極高,這樣是無法放量來跑的。建議調低到 30 左右,若內部邏輯允許也可嘗試更低的值 (影響到 Update() 被呼叫的頻率)。

Unity 中是透過以下方式來設定 target frame rate:

Application.targetFrameRate = 30;


1 則留言: