# Python 性能：原生 NumPy 循环、回调和转换成本

Published: 2026-10-17

一项受控的纯 CPU 数字测试跟踪了 CPython 循环、NumPy 算术、Python 回调包装器和编译后的 Numba 循环中的相同值。准备好输入的 NumPy 具有很大的领先优势，而从 Python 列表转换到 Python 列表再转换回来则将该优势缩小到几乎持平。本集解释了执行边界以及如何测量应用程序所需的结果。

Canonical: https://thedailydiff.dev/zh-CN/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 也指出了这个问题。 分配器对你的审美观 remarkably indifferent。

### 编译后的 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
