EVE Frontier - 走向未來升級 Python3

原文 EVE Frontier編輯 Chiny2025-02-14
page-hero-Moving into the Future
page-hero-Moving into the Future

我們剛剛經歷了 CCP 歷史上一個具有里程碑意義的時刻

第一次 (也可能是唯一一次) 主要 Python 版本升級,還同時一次性整合了驚人的 12 個次要版本 - 16 年的更新。

在我們深入研究這代表什麼之前,特別感謝在 Platform 和 Team Gadget 中才華橫溢的工程師:你們工作確實是偉大的,我們肯定會在 Groska 慶祝。

如果您不懂技術,請注意:這是一個很大的更新。

背景

EVE Frontier 和 EVE Online 一樣,都是基於 Carbon Engine 的。Carbon 是作為 EVE Online 的一部分開發的,將其納入一個支援多種產品的獨立專案本身就是一個挑戰。

使用 Carbon 及其框架讓《EVE Frontier》建立在多年優秀的太空遊戲技術之上,但同時也背負了多年的技術債務。

例如,Carbon 最初運行的是 Python 的一個分支,稱為「Stackless Python」。在早期,這對開發者來說非常有用,因為它允許創建大量的 tasklets(一種能夠並發運行函式的方法,而無需使用執行緒)。這個決定在當時雖然有些爭議,但對於《EVE Online》的初期開發來說是合理的。後來,原生的 Python 2 透過 gevent 和 greenlets 也獲得了類似的功能,而 Python 3 則進一步在標準函式庫中引入了 asyncio 來提供這種能力。

多年來,CCP 一直是 Stackless Python 分支的主要貢獻者之一,但該分支已經停留在 Python 3.8 的最高版本一段時間了。這使我們在繼續開發《EVE Frontier》,並確保其能夠在未來 20 年(甚至更久)作為一款遊戲持續發展時,面臨兩個選擇:

  1. 我們自己維護 Stackless Python 分叉
  2. 升級至原版 Python 3

升級過程

我們選擇了第二個選項,這帶來了兩項立即需要完成的任務:升級至 Python 3 並擺脫 Stackless。起初,看似合理的做法是先升級到 Stackless 3.8,然後再逐步移除它,因此我們也開始朝這個方向著手。然而,我們很快意識到,Stackless 3.8 在任何階段都無法達到可用於正式發行的穩定狀態,無法支撐我們的開發工作。

大約 400 萬行 Python 代碼和 100 萬行 C++ 代碼構成了 EVE Frontier 客戶端和伺服器。其中許多需要更新或完全更改。

Python 3 是一次重大的升級,但它包含許多不向下相容的變更。其中許多變更的遷移路徑相對簡單,例如 print 語句的改動,從 print "hello world!" 變為 print("hello world!")。然而,其他變更則較為複雜,例如除法運算從整數除法改為浮點數除法(在這種情況下,我們可以進行安全的轉換,但會以代碼可讀性和少許效能為代價)。

如果您不熟悉技術,請注意:這是一個很大的變化。甚至巨大。

此外,還出現了其他挑戰:有些功能已無法運作。例如,兩個不可比較的型別現在無法再進行比較。雖然這一變更有其歷史原因,但在開發過程中,我們遇到了一些舊有代碼,例如對元組列表進行排序時,第二個元素可能是不可比較的。此外,我們的 C++ 代碼與 Python 之間的接口在不同版本間變化相當大。例如,為 Python 2 編寫並編譯的代碼無法在 Python 3 中運行。

這些問題和其他類似問題已經被發現並修復,但是可能還有更多隱藏在很少執行或僅在生產環境中執行的代碼中。

網路模組同樣帶來了相當大的挑戰。過去,我們對 Python 的 socket 模組進行了重大修改,以提升效能。隨著 Python 語言的發展,這些修改已不再必要,但我們仍需要調整 ssl 模組,使其能夠與未經修改的 socket 模組相容。這讓我們面臨了一個選擇:

  1. 維護我們自己的這些模組版本
  2. 使 socket 模組與標準 socket 模組相容,這樣我們就不需要修改 ssl 模組。

我們選擇了第二個選項,以確保長期的可維護性,但這也意味著我們需要對網路堆疊進行大規模的修改。

您可以說,我們在汽車行駛時一直在更換發動機。

此時,我們仍然無法順利遷移到 Stackless Python 3.8 之後的版本。我們曾考慮轉向 Python 的 asyncio,但這需要對我們的代碼進行重大修改,因為其概念與我們原本的實作方式截然不同。asyncio 需要明確標記可以讓出控制權的函式,而我們的 Stackless 實作則不需要。我們無法找到一條明確的升級路徑,因此選擇了另一條方案。

最終,我們決定採用 greenlets,但不使用 gevent,原因是 gevent 由 Cython 編寫,而我們的 C++ 層需要與排程器(scheduler)進行交互,這使得 gevent 並不適合我們的架構。為了解決這一問題,我們開發了自己的排程器(scheduler)。

結果

  • 我們撰寫了 400 萬行 Python 代碼,與 Python 3 相容
  • 修改了 5840 個檔案
  • 80,564 行更改
  • 添加了 18,461 行
  • 刪除 16,305 行
  • 我們使 Python 的所有 C++ 介面與 Python 3 解釋器相容
  • 我們重寫了很大一部分網路堆疊
  • 我們實現了一個新的調度程序來處理 greenlets
  • 升級了我們所有的工具以使用 Python 3

由於程式庫中變更的範圍很大,所以發生衝突的可能性也相當高。如果有一個人修改了一行代碼以使其與 Python 相容,而另一個人因為遊戲玩法的變動修改了同一行代碼,那麼隨之而來的衝突就需要手動解決。雖然這不是最複雜的修復,但它高度依賴人工操作,而且錯誤解決的風險總是存在。

在像《Frontier》這樣的複雜產品中,估算效能提升是非常困難的。我們在對某些代碼路徑進行測量時,會發現有些路徑的效能有所改善,而有些則出現退步。然而,最終的影響取決於這些代碼路徑被執行的頻率。

如果在 Python 3 升級前後能夠保持一致的負載,我們可能會得到一些具體的數字,但《Frontier》並不符合這個條件。儘管如此,我們可以從數據中明確看到,當我們測量更複雜的代碼路徑時,例如多個用戶登錄或武器開火,我們觀察到的效能提升在 10% 到 30% 之間。

這是一個令人印象深刻的結果,尤其是在考慮到我們仍然在實時環境中開發《EVE Frontier》的情況下!可以說,我們是在車子行駛時更換引擎。這是一項龐大的工作,但回報也相當巨大:現在,《EVE Frontier》已經順利運行於 Python 3.12,為所有未來的開發奠定了基礎。

有興趣的讀者可以參考以下 Eve Frontier 的技術實作細節

https://docs.evefrontier.com/Blogs/moving-into-the-future