注意: 雖然 JavaScript 對於本網站不是必需的,但您與內容的互動將會受限。請開啟 JavaScript 以獲得完整體驗。

構建 Python 程式碼庫的依賴關係圖

引言

秉承我們高頻交易的根基,Hudson River Trading (HRT) 行動迅速。正如工程中的任何指標一樣,速度也有其權衡。在過去五年中,由於一種偏愛“足夠好”而不是“完美”的草根工程文化、鼓勵團隊之間程式碼共享的協作工作環境,以及一段加速增長時期,HRT 看到其研究導向的 Python 程式碼庫的規模和互聯性呈指數級增長。隨著我們的 Python 程式碼庫增長到數百萬行,匯入時間增加了一個數量級,程式碼更改的測試成本變得更高,並且 lint 時間遠遠超出了實用範圍 - 我們正在經歷程式碼“糾纏”的影響。

糾纏

程式碼“糾纏”是 HRT 員工從 Dropbox 釋出的關於他們自己 Python 程式碼庫的相同問題的描述中借用的概念。當代碼的依賴關係圖具有許多重疊的迴圈,並且程式碼庫中不相關的部分透過間接和不直觀的匯入路徑耦合在一起時,我們稱之為程式碼“糾纏”。糾纏在任何大型程式碼庫中都可能是一個問題(包括其他語言)!

根據我們的經驗,糾纏會影響執行時匯入和靜態分析(例如 mypy)的效能,並導致緊密的 耦合,這會降低可靠性。在這些問題中,我們的使用者認為執行時匯入開銷是最大的問題,因為它減慢了開發迭代迴圈並浪費了資料中心的 CPU 時間。對於 HRT 來說,這可能比大多數其他 Python 商店更成問題,因為短期 Python 程序佔我們計算工作量的很大一部分。

糾纏的負面影響會迅速增加——一些錯誤的匯入,突然間數百個模組耦合在一起。匯入開銷的影響會因糾纏而放大,因為匯入迴圈中的任何模組最終都會傳遞性地匯入該迴圈中的所有模組(及其依賴項)。

雖然某些匯入速度非常快,但在許多情況下會產生很大的開銷。開銷偷偷引入的一種常見方式是透過檔案系統訪問——例如,現在已棄用的 pkg_resources 模組會爬取檔案系統以查詢資源。當在我們的網路檔案系統上執行時,此過程尤其成問題。計算開銷的另一個來源是 pandas 和 NumPy 等軟體包載入的大量單體 C 擴充套件——甚至是專有擴充套件。此外,我們的一些純 Python 模組會產生一系列昂貴的靜態初始化步驟,例如檢測環境特徵或處理類或回撥的動態註冊。

單獨來說,這些中的每一個都會引入可管理的匯入工作負載;但是,在我們程式碼庫中最糾纏的部分中,複合效應會導致大多數程式匯入時間超過 30 秒。這種開銷減慢了開發迭代迴圈,並浪費了我們分散式計算環境中的 CPU 時間。

依賴關係管理

在高層次上,我們解開糾纏的方法是建立並維護一個分層架構,其中較低層中的模組不從較高層中的模組匯入。建立適當的分層結構有助於呼叫者只匯入他們需要的內容。

理想情況下,我們的依賴關係圖應該類似於有向無環圖,其中模組按其分配的層進行拓撲排序。然而,在實踐中,只要一些迴圈相對較小並且包含在一個(子)包中,那麼這些迴圈是可以接受的。

過渡到更好的依賴關係管理範例需要識別當前糾纏的原因、重構程式碼庫以重組依賴關係,並實施依賴關係驗證以避免未來的迴歸。而所有這些工作都必須在不暫停程式碼庫開發的情況下完成!

糾纏工具:理解糾纏

一旦我們理解了糾纏是許多開發者體驗問題的根本原因,我們便開始構建一個工具包,用於分析我們程式碼庫的依賴關係圖——糾纏工具。糾纏工具分析 Python 原始碼以生成整個程式碼庫的依賴關係圖(節點對應於模組,邊對應於匯入)。然後,我們的使用者可以利用命令列和瀏覽器介面來發現、導航和重構依賴關係。

典型的解纏工作流程包括

  • 找到不需要的傳遞依賴關係

  • 從源追蹤到不需要的依賴關係的匯入路徑

  • 計算源和依賴關係之間的流網路

  • 識別刪除哪些邊會減少流量

  • 重構匯入以斷開源與依賴關係的連線

  • 利用程式碼轉換來自動化常見的重構(例如,將符號移動到新模組並更新現有引用)

  • 實施依賴關係驗證以避免迴歸

  • 使用者編寫依賴關係規則以約束其模組的依賴關係 

  • 這些規則在我們的持續整合管道中進行檢查

Rendered Summary Graph

如果沒有廣泛使用開源庫,這一切都不可能實現!我們使用 Python 內建的 ast 庫來解析我們的 Python 原始碼中的匯入。這項解析工作透過 stdlib 強大的 concurrent.futures 模組進行並行化,使我們能夠快速處理數千個模組。在底層,我們使用 networkx 的有向圖資料結構和廣泛的圖演算法庫——我們發現流演算法特別有用。最後,我們使用 libcst 庫執行自動原始碼重構,將其編寫為對具體語法樹的轉換。

結論

透過開發這些依賴關係管理和重構工作流程,我們能夠在解開糾纏方面取得重大進展。以前,查詢匯入依賴關係是一個緩慢的手動過程,而重構依賴關係就像一場打地鼠遊戲。現在,我們可以導航我們的完整依賴關係圖,並找到有效的重構方法來解決糾纏的根本原因。

認識作者

George Farcasiu - 核心開發者

  • George Farcasiu 參與了 HRT Python 生態系統中的各種專案,包括建立 Python 靜態分析和依賴關係管理工具、為分散式計算框架和環境做出貢獻,以及維護構建/測試/持續整合開發人員工具。

Noah Kim - 核心開發者

  • Noah Kim 主要關注 HRT 對 CPython 直譯器的使用。他還是糾纏工具的當前維護者,該工具是改進公司 Python 包生態系統的更廣泛工作的一部分。

Jacob Brugh - 核心開發者

  • Jacob Brugh 最近的關注領域包括改進 HRT 的 C++ 交易庫的 Python 繫結程式碼的效能,並開發內部工具,使我們能夠進行大規模的高效靜態分析。

Jiahao Li - 核心開發者

  • Jiahao Li 從事涉及分散式計算叢集和構建/測試平臺的各種專案。最近的專案包括全面改進 HRT 的測試環境,以提供依賴關係跟蹤和更智慧的測試選擇。

本文 最初發表在 HRT Beat 上。