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

Published: 2026-10-17

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

Canonical: https://thedailydiff.dev/zh-TW/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
