+− 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 列表轉換回來再轉換回去,使優勢縮小到幾乎持平。本集解釋了執行邊界以及如何測量應用程式所需的結果。

閱讀書面版(英文) ↗

本影片重點

  • 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

相關影片