+− THE DAILY DIFFdev & AI news
SHIP IT

Python 效能:原生 NumPy 迴圈、回呼與轉換成本

一個受控的純 CPU 數值測試追蹤相同的數值,這些數值會經過 CPython 迴圈、NumPy 算術、Python 回呼包裝器和編譯過的 Numba 迴圈。

Want to see our stories first on Google? Add us to your preferred sources — it helps us grow.

一個受控的純 CPU 數值測試追蹤相同的數值,這些數值會經過 CPython 迴圈、NumPy 算術、Python 回呼包裝器和編譯過的 Numba 迴圈。準備好的輸入 NumPy 具有很大的領先優勢,而從 Python 列表轉換並返回 Python 列表則將該優勢縮小到幾乎持平。本集解釋了執行邊界以及如何測量應用程式所需的結果。

閱讀書面版本(英文) ↗

本影片涵蓋的內容

  • CPython 將原始碼編譯成位元碼。重複的解釋器/物件工作和類型化的原生數字迴圈是不同的執行安排。
  • 數字 NumPy 算術可以將類型化的批次傳遞給編譯操作;算術迴圈仍然存在,並且視圖可以具有非連續佈局。
  • 測量轉換和所需的輸出表示。在此執行中,將 Python 列表轉換為陣列再轉換回來,抹去了大的就緒陣列優勢。
  • 小型工作負載可能偏愛普通 Python,因為固定的陣列呼叫成本很重要。八值推導式贏得了這次測量比較。
  • np.vectorize 是一個便利包裝器,它為連續元素呼叫 Python;單獨的批次介面並不能建立原生數值執行。
  • 編譯的 Python 語法迴圈也可以很快。當對呼叫者很重要時,請包括冷編譯/設定成本。
  • 原生執行、CPU SIMD、多執行緒和 GPU 後端是獨立的概念。可用的 CPU 功能不會追蹤特定執行中使用的指令。

翻譯的文字記錄

從英文原文旁白翻譯。可用的音頻和字幕由 YouTube 控制。

為什麼同一個 CPU 可以更快完成?

0:00 你認為 Python 需要一種新語言才能變得快速。 迴圈移動後,同一個 CPU 執行我們的批次更快了。 工作去哪兒了? 加速什麼時候會消失? 哪個捷徑讓 Python 恢復了? 這是 The Daily Diff,幕後揭秘。 留意那個捷徑。 CPython 是標準的 Python 實作。

0:17 它將你的原始碼編譯成位元碼,然後在 解釋器中執行這些指令。NumPy 是一個數值庫,其陣列儲存具有 聲明資料類型的值。 其內建的算術功能可以將整個批次傳遞給編譯操作。 取一百萬個浮點值,將每個值平方, 然後加上原始值。 每個版本都會獲得相同的值並產生匹配的答案。 這些值足夠小,足以避免四捨五入的差異影響結果。

0:40 在這台機器上,推導式大約花了三十三毫秒。 NumPy 則不到一毫秒。 兩者都在 CPU 上執行。 這對此工作負載來說是個有用的證據,但對於程式語言來說,卻是個糟糕的訃聞。 從普通的迴圈開始。

Python 迴圈重複了什麼工作?

0:53 Python 獲取下一個值,執行算術,並附加答案。 然後它再做一次。 乘法很便宜。 安排下一個乘法,並處理周圍的 Python 物件, 每次都會增加工作量。 這是測試函數的實際反彙編。 迭代、乘法和加法都以位元碼指令的形式出現。 Python 版本和解釋器會做出不同的選擇。

1:14 這些時間屬於螢幕上顯示的版本,即 Python 3.10。 Python 列表儲存對物件的參考,因此不同種類的事物可以 共享一個列表。 我們的算術工作全程使用數字。 它為一般安排付費,同時請求重複服務。 想像一下,點一百萬次相同的咖啡,每次都填寫一份新表格。 為每一杯咖啡填寫一張新表格。 咖啡機沒問題。

1:36 文件工作已發展出自己的事業。

NumPy 將迴圈移到哪裡?

1:38 NumPy 改變了合約。 這個陣列儲存具有一個聲明類型的固定大小浮點值。 我們的陣列是連續的,所以它的值在記憶體中是緊密排列的。 視圖可以有其他佈局。 算術運算知道它正在接收什麼類型的資料。 該呼叫將批次傳遞給原生操作。 迴圈仍然在編譯程式碼內部遍歷值。 它可以在該工作中重用類型知識。

2:00 Python 仍然是負責交辦工作的人。 Itamar Turner Trauring 在 Python Speed 中解釋了這一區別。 批次介面、原生實作和處理器向量指令是 獨立的事物。函數名稱提供的證據很少。

快速是指 GPU 還是多個 CPU 核心?

2:13 這個結果在 CPU 上使用了 NumPy。 CuPy 等 GPU 後端為陣列和操作選擇了不同的位置。 購買 GPU 會讓這個普通的 Python 迴圈繼續在其已經執行的地方運行。 CPU 向量指令可以同時處理多個值。 NumPy 具有選擇受支援核心的機制。 我們的運行時報告了可用功能,但沒有追蹤此表達式的 指令。將所有測量到的增益歸因於向量指令將會 過度誇大證據。

2:40 GPU 購物車可以保持關閉狀態。 一個既能解決性能問題又能取消購買的修復方法,真是令人心滿意足。 不幸的是,基準測試仍在繼續。

什麼時候驚人的加速會消失?

2:49 對於八個值,推導式勝出。 呼叫陣列機制所花費的成本比這個小任務還要多。 較大的準備好的陣列後來居上。 我們使用了重複試驗和暖機過程。 較新的 Python 版本可能會改變這個交叉點。 然後改變應用程式的需求。 從 Python 列表開始,並以新的 Python 列表結束。 NumPy 必須將輸入轉換為陣列,並將答案轉換回 Python

3:11 物件。如果你的應用程式需要這些步驟,它們就應該計入成本。 在一百萬個值時,這個完整路徑大約花了二十八毫秒。 推導式也大約花了二十八毫秒。 我們的巨大領先優勢變成了幾乎持平。 快速的部分是真的。 優勢取決於讓數據保持其數值形式。

哪些邊界屬於基準測試?

3:28 跟隨數據。 如果下一個操作接受陣列,就保留陣列。 如果你在列表和陣列之間重複轉換, 測量這些轉換。 在昂貴的交接前停止,會為錯誤的 問題提供準確的數字。乘法此陣列會產生一個臨時結果。 加上原始值會產生輸出。 原生執行仍然需要記憶體和移動數據。

3:47 某些操作接受輸出緩衝區,在你的程式碼可以 安全重複使用時減少分配。 一行簡潔的程式碼可以訂購多個大型緩衝區。 Python Speed 也點出了這個問題。 分配器對你的美學毫不在意。

編譯過的 Python 迴圈也能快嗎?

4:00 你也可以保留迴圈語法並改變其執行方式。 Numba 可以將支援的數值 Python 函數編譯成原生代碼。 這是我們測試中的迴圈,帶有其編譯器裝飾器。 它仍然寫著 for。 昂貴的解釋器路徑已改變。 編譯後,此迴圈在使用準備好的 陣列時僅耗時不到半毫秒。這次運行中,編譯本身大約花了一秒鐘。 該設定成本未包含在重複計時中,

4:22 因此一個微不足道的一次性工作值得不同的比較。 Numba 自己的指南樂於展示明確的迴圈。 在找到 熱路徑之前開始語言遷移是一種複雜的 避免開啟分析器的方法。 首先找出什麼消耗了時間。 網絡等待、物件解析和大量算術呼叫需要不同的修復方法。 當計算和數據適合時,原生陣列操作會有幫助。

4:43 測量呼叫者實際需要的結果。 現在來說說那個捷徑。 你擁有一個 Python 函數,並希望它能接受一個陣列。 一個名為 vectorize 的包裝器聽起來像是缺失的效能按鈕。 它的文件有一個重要的句子在等待。

np.vectorize 實際執行了什麼?

4:56 NumPy 表示其 vectorize 包裝器主要用於便利性。 它的實作本質上是一個 for 迴圈。 這個包裝器接受批次,但會持續為連續的元素呼叫你的 Python 函數。 界面變短了。 執行邊界依然繁忙。 觀察相同的數值通過 Python 回呼傳回。 使用準備好的陣列,這個版本大約花了八十七毫秒。 它比我們的推導式還要慢。

5:18 一個有用的廣播包裝器可以保留你試圖移除的每個元素的精確工作。 你試圖移除。 檢查批次介面背後的實作,將兼容的資料保持在 有用的形式,並將轉換納入比較。 原生、回呼和編譯後的迴圈從呼叫者的角度來看可能相似。 vectorize 這個名字做出了你的秒錶從未簽署的承諾。 當你只需要便利性時,請保留它。 為了性能,請檢查運行的迴圈。

你應該更改代碼中的什麼?

5:41 我的判決是 SHIP IT,用於批次處理已測量的數值熱路徑。 當它們運作良好時,保留普通迴圈。 檢查工作發生在哪裡以及下一個呼叫者需要什麼。 訂閱、點擊鈴鐺,並在評論中告訴我你是否會蓋上 不同的章。這就是今天的差異。 我是 Axrisi 的 Niko。 負責任地合併。

來源

  1. Universal functions (ufunc) basics — NumPy 2.2NumPy
  2. The N-dimensional array — NumPy 2.2NumPy
  3. numpy.vectorize — convenience and performance notesNumPy
  4. CPU/SIMD optimizations — NumPy 2.2NumPy
  5. dis — Disassembler for Python bytecodePython Software Foundation
  6. CPython 3.10.12 list representationCPython contributors
  7. The limits of Python vectorization as a performance techniqueItamar Turner-Trauring / Python⇒Speed
  8. Performance TipsNumba
  9. Basics of CuPyCuPy

相關影片