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

Published: 2026-10-17

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

Canonical: https://thedailydiff.dev/zh-HK/video/2026-10-17-python-native-loops/

## 本影片涵蓋的內容

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

## 章節

- 0:00 為什麼同一個 CPU 可以更快完成？
- 0:52 Python 迴圈重複了什麼工作？
- 1:38 NumPy 將迴圈移到哪裡？
- 2:13 快速是指 GPU 還是多個 CPU 核心？
- 2:49 什麼時候驚人的加速會消失？
- 3:28 哪些邊界屬於基準測試？
- 4:00 編譯過的 Python 迴圈也能快嗎？
- 4:56 np.vectorize 實際執行了什麼？
- 5:41 你應該更改代碼中的什麼？

## 翻譯的文字記錄

從英文原文旁白翻譯。可用的音頻和字幕由 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。 負責任地合併。

## 來源

- [Universal functions (ufunc) basics — NumPy 2.2](https://numpy.org/doc/2.2/user/basics.ufuncs.html) — NumPy
- [The N-dimensional array — NumPy 2.2](https://numpy.org/doc/2.2/reference/arrays.ndarray.html) — NumPy
- [numpy.vectorize — convenience and performance notes](https://numpy.org/doc/2.2/reference/generated/numpy.vectorize.html) — NumPy
- [CPU/SIMD optimizations — NumPy 2.2](https://numpy.org/doc/2.2/reference/simd/index.html) — NumPy
- [dis — Disassembler for Python bytecode](https://docs.python.org/3.10/library/dis.html) — Python Software Foundation
- [CPython 3.10.12 list representation](https://github.com/python/cpython/blob/v3.10.12/Include/cpython/listobject.h) — CPython contributors
- [The limits of Python vectorization as a performance technique](https://pythonspeed.com/articles/vectorization-python-alternatives/) — Itamar Turner-Trauring / Python⇒Speed
- [Performance Tips](https://numba.readthedocs.io/en/stable/user/performance-tips.html) — Numba
- [Basics of CuPy](https://docs.cupy.dev/en/stable/user_guide/basic.html) — CuPy
