T
traeai
登录
返回首页
KDnuggets

3 NumPy Tricks for Numerical Performance

8.5Score

TL;DR · AI 摘要

使用 NumPy 的向量化、原地操作和内存视图可显著提升数值计算性能。

核心要点

  • 使用 NumPy 的向量化和广播机制替代显式循环,可提升性能。
  • 使用 out 参数进行原地操作,减少内存分配和复制。
  • 利用内存视图替代数组复制,优化大数组处理效率。

结构提纲

按章节快速跳转。

  1. NumPy 是 Python 科学计算和机器学习生态系统的核心,其性能来源于底层优化的 C 实现。

  2. 显式 Python 循环是数值计算中的性能杀手,应使用 NumPy 的向量化和广播机制替代。

  3. 使用 out 参数进行原地操作,可以减少内存分配和复制,提高性能。

  4. 使用内存视图替代数组复制,可以优化大数组处理效率,减少内存开销。

思维导图

用一张图看清主题之间的关系。

查看大纲文本(无障碍 / 无 JS 友好)
  • NumPy 性能优化技巧
    • 向量化和广播
      • 替代显式循环
      • 使用 ufuncs 和广播
    • 原地操作
      • 使用 out 参数
      • 减少内存分配
    • 内存视图
      • 避免数组复制
      • 优化大数组处理

金句 / Highlights

值得收藏与分享的关键句。

  • 显式 Python for 循环是数值计算中的性能杀手,因为它们迫使 Python 解释器在每一步执行类型检查和方法查找。

    第 2 段

    ⬇︎ 下载 PNG𝕏 分享到 X
  • np.vectorize 仅仅是提供了一个更干净 API 的便利包装器,它在背后运行的是一个缓慢的 Python 循环。

    第 2 段

    ⬇︎ 下载 PNG𝕏 分享到 X
  • 使用 NumPy 的广播机制,可以在不复制数据的情况下对不同形状的数组执行操作。

    第 2 段

    ⬇︎ 下载 PNG𝕏 分享到 X
#NumPy#Python#性能优化
打开原文

3 个提升数值性能的 NumPy 技巧 - KDnuggets

publ: 2026年6月12日

  • 博客热门文章
  • 主题 AI 职业建议 计算机视觉 数据工程 数据科学 语言模型 机器学习 MLOps NLP 编程 Python SQL
  • 数据集
  • 活动
  • 资源 快速参考指南 推荐 技术简报
  • 广告

加入新闻通讯

#header end

/ad_wrapper

3 个提升数值性能的 NumPy 技巧

在本文中,我们将介绍三个优化代码的重要 NumPy 技巧:向量化和广播、原地操作,以及利用内存视图而不是复制。

作者:

Matthew Mayo

KDnuggets 主编,2026年6月12日,在

Python

<div class="addthis_native_toolbox"></div>

# 引言

Python 的科学计算和机器学习生态系统高度依赖于 NumPy。它在 Pandas、Scikit-Learn、SciPy 和 PyTorch 等库中充当性能引擎。NumPy 的速度来源于其底层使用优化的 C 实现,其中连续的内存块被处理,而无需 Python 对象模型和动态解释器的开销。

不幸的是,许多数据科学家和开发者编写的 NumPy 代码未能充分利用这种能力。通过延续标准的 Python 循环或编写导致不必要的内存分配和数组复制的简单计算,性能瓶颈随之而来。在处理大型数据集时,这些低效率会导致内存使用量增加、缓存未命中和执行时间变慢。为了编写高性能的数值代码,你必须了解 NumPy 在幕后如何管理计算、内存分配和数据布局。

在本文中,我们将介绍三个优化代码的重要 NumPy 技巧:

  • 向量化和广播
  • 使用 out 参数的原地操作
  • 利用内存视图而不是复制

# 1. 用向量化和广播替代显式循环

显式的 Python for 循环是数值计算中最大的速度杀手。逐个元素迭代数据结构会迫使 Python 解释器在每一步执行类型检查和方法查找。

一个常见的错误是使用 np.vectorize。许多开发者认为将标准的 Python 函数用 np.vectorize 包裹会将其转换为优化的 C 代码。实际上,np.vectorize 仅仅是一个方便的包装器,它在更干净的 API 后面运行一个缓慢的标准 Python 循环,提供零性能提升。

为了优化,你必须使用原生的通用函数(ufuncs)和广播编写代码。广播允许 NumPy 在不复制数据的情况下对形状不同的数组执行操作,直接在编译的 C 中处理操作。

这种简单的方法逐行和逐列迭代一个二维数组,以执行列方向的标准化(减去列均值并除以列标准差):

code
import numpy as np
import time

# 创建一个样本矩阵(50000 行,1000 列)
matrix = np.random.rand(50000, 1000)

start_time = time.time()

# 基于循环的列标准化
res = matrix.copy()
for col in range(matrix.shape[1]):
    col_mean = np.mean(matrix[:, col])
    col_std = np.std(matrix[:, col])
    for row in range(matrix.shape[0]):
        res[row, col] = (matrix[row, col] - col_mean) / col_std

duration_loop = time.time() - start_time

print(f"嵌套循环处理矩阵耗时: {duration_loop:.4f} 秒")

输出:

code
向量化的广播处理矩阵耗时:0.1972 秒

这大约是 56 倍的加速!

在向量化实现中,matrix - means 和随后的除以 stds 的操作是使用 NumPy 的广播规则执行的。由于 matrix 的形状是 (50000, 1000),而 means 的形状是 (1000,),NumPy 会概念性地将 means 数组扩展以匹配 matrix 的形状。在底层,这种扩展在内存中瞬间完成,无需复制数据,计算被推送到 SIMD(单指令多数据)CPU 指令,从而实现高达 50 倍以上的加速。

# 2. 原地操作与 out 参数

当你编写类似 y = 2 * x + 3 的表达式时,你可能期望它运行得高效。然而,在底层,NumPy 会逐步评估这个表达式:

  • 它在内存中分配一个临时数组来存储 2 * x 的结果。
  • 它再分配另一个数组来存储将 3 加到临时数组上的结果。
  • 最后,它将这个第二个临时数组绑定到变量名 y 上。

在处理非常大的数组(例如数百万个元素)时,分配和垃圾回收这些临时中间数组会带来显著的开销。这会破坏 CPU 缓存并占用内存总线带宽。

我们可以通过使用 *=+= 等操作符进行原地计算,或者利用几乎所有 NumPy 通用函数内置的 out 参数来避免这种开销。

这种简单的方法对一个大型数组执行基本的线性缩放,导致多次临时分配:

code
import numpy as np
import time

# 创建一个包含 1000 万个元素的大型 1D 数组
x = np.random.rand(10000000)
scale = 2.5
offset = 1.2

start_time = time.time()

# 标准的链式数学运算会创建临时中间数组
y_naive = scale * x + offset

duration_naive = time.time() - start_time
print(f"链式表达式执行耗时:{duration_naive:.4f} 秒")
code
链式表达式执行耗时:0.0393 秒

在这里,我们预先分配了目标输出数组一次,并重用其缓冲区进行所有后续的数学运算,从而绕过临时分配:

code
import numpy as np
import time

# 创建一个包含 1000 万个元素的大型一维数组
x = np.random.rand(10000000)
scale = 2.5
offset = 1.2

start_time = time.time()

# 预分配最终数组
y_optimized = np.empty_like(x)

# 直接将数学运算结果写入目标缓冲区,不使用中间变量
np.multiply(x, scale, out=y_optimized)
np.add(y_optimized, offset, out=y_optimized)

duration_optimized = time.time() - start_time

print(f"优化后的原地表达式执行时间: {duration_optimized:.4f} 秒")
print(f"加速比: {duration_naive / duration_optimized:.2f}x 更快!")
code
优化后的原地表达式执行时间: 0.0133 秒

在优化的示例中,我们使用 np.multiply(x, scale, out=y_optimized) 将乘法运算的结果直接写入预分配的 y_optimized 数组中。然后,使用 np.add(y_optimized, offset, out=y_optimized) 添加偏移量,并将结果写回同一个缓冲区。这种方式完全避免了临时缓冲区的分配和垃圾回收,节省了系统内存,保持数据在 CPU 缓存中,并提升了执行速度。

# 3. 内存视图与内存复制(切片与高级索引)

理解 NumPy 在什么情况下返回数组的视图(view)而不是复制(copy)是数值编程中最重要的主题之一:

  • 视图是一个新的数组对象,它指向与原始数组相同的底层数据缓冲区。创建视图是一个零复制操作,运行时间为 $O(1)$ 常数时间与空间。
  • 复制会分配一个新的数据缓冲区并复制数据。这需要 $O(N)$ 线性时间与空间。

基本切片(使用起始、结束和步长索引,例如 arr[0:10:2])总是返回视图。相比之下,高级索引(使用索引列表或布尔掩码,例如 arr[[0, 2, 4]])总是返回复制。

如果你只需要读取或更新数组的子段,使用高级索引会触发大量不必要的内存分配。

在这里,我们尝试通过传递索引列表来对一个巨大的二维矩阵(每隔一行和一列)进行子采样。这迫使 NumPy 分配一个大型的新数组并复制所有元素:

code
import numpy as np
import time

# 创建一个包含 10000 x 10000 个元素的矩阵
matrix = np.random.rand(10000, 10000)

start_time = time.time()

# 使用整数数组的高级索引会强制复制数据
rows = np.arange(0, matrix.shape[0], 2)
cols = np.arange(0, matrix.shape[1], 2)
sub_matrix_copy = matrix[rows[:, None], cols]

duration_copy = time.time() - start_time
print(f"高级索引复制完成时间: {duration_copy:.4f} 秒")
code
高级索引复制完成时间: 0.1575 秒

现在,我们执行相同的操作,但使用基本切片。而不是复制数据,NumPy 会立即调整步长元数据以指向相同的缓冲区:

code
import numpy as np
import time

# 创建一个包含 10000 x 10000 个元素的矩阵
matrix = np.random.rand(10000, 10000)

start_time = time.time()

# 基本切片会立即返回零复制视图
sub_matrix_view = matrix[::2, ::2]

duration_view = time.time() - start_time
print(f"基本切片视图完成时间: {duration_view:.8f} 秒")
code
基本切片视图完成时间: 0.00001001 秒

当你使用 matrix[::2, ::2] 对数组进行切片时,NumPy 并不会访问底层的数据缓冲区。它只是创建一个新的数组头,其中包含修改后的元数据:不同的形状和新的步长(在每个维度中找到下一个元素所需的字节数)。无论矩阵有多大,这个操作的运行时间都少于一微秒。

然而,需要注意权衡:由于视图共享相同的内存缓冲区,修改 sub_matrix_view 也会影响原始矩阵。如果你必须避免修改原始数组,就必须显式调用 .copy()

# 总结

编写干净且高效的 NumPy 代码需要改变你对循环、内存分配和数据结构的思维方式。通过放弃标准的 Python 概念,转而使用原生的 NumPy 机制,你可以消除计算瓶颈。

简要回顾:

  • 放弃 Python 循环和 np.vectorize,让向量化广播将计算下推到优化的 C 代码中。使用原地操作和 out 参数来绕过分配器,防止缓存抖动并减少内存使用。
  • 掌握视图与复制之间的区别,利用即时、零拷贝的切片,而不是代价高昂的高级索引复制。

将这三种性能设计模式整合在一起,可以让你的数据处理管道保持精简、快速且适用于生产环境的工作负载。

Matthew Mayo(@mattmayo13)拥有计算机科学硕士学位和数据挖掘研究生文凭。作为 KDnuggets & Statology 的总编辑和 Machine Learning Mastery 的特约编辑,Matthew 致力于使复杂的数据科学概念变得易于理解。他的专业兴趣包括自然语言处理、语言模型、机器学习算法以及探索新兴的人工智能技术。他以使命驱动,致力于在数据科学社区中普及知识。Matthew 从6岁起就开始编程。

更多相关内容

  • 调试和分析 NumPy 代码以识别性能瓶颈
  • 7 个 XGBoost 技巧,用于构建更准确的预测模型
  • 高级 NotebookLM 技巧与技巧,面向高级用户
  • 5 个 Scipy.stats 技巧,用于模拟“如果”情景
  • 3 个 SpaCy 技巧,用于高效文本处理与实体识别
  • LLM 微调与推理的 5 个实用技巧

<hr class="grey-line"><br> <div><h3>我们推荐的 5 门免费课程</h3><br> </div>

Mailchimp for WordPress v4.13.0 - https://wordpress.org/plugins/mailchimp-for-wp/

/ Mailchimp for WordPress 插件

你可以从这里开始编辑。

如果评论已关闭。

<= 上一篇

下一篇 =>

#content end

<script type="text/javascript">kda_sid_write(kda_sid_n);</script>

最新文章

  • 将 Claude Code 与本地模型配对:3 个 NumPy 技巧提升数值性能 从零开始构建特征存储:一个最小可用实现 7 个获取创业点子资金的最佳方式 低成本本地智能代理编程:Claude Code + Ollama + Gemma4 5 个有用的 Python 脚本自动化无聊的 PDF 任务

热门文章

  • Anthropic 的 Claude 技能完整指南
  • 低成本本地智能代理编程:Claude Code + Ollama + Gemma4
  • 5 个有用的 Python 脚本自动化无聊的 PDF 任务
  • AI 工程师必须知道的 5 个 Python 概念
  • 获取创业点子资金的 7 个最佳方式
  • 用于 Python Web 开发的 10 个 GitHub 仓库
  • 将 Claude Code 与本地模型配对
  • 5 篇有趣论文清晰解释 LLMs
  • 智能代理时代对数据科学意味着什么
  • 10 个现代数据库系统和工具的 GitHub 仓库

#content_wrapper end

© 2026

Guiding Tech Media

|

关于

联系我们

广告合作

隐私政策

服务条款

发布于 2026 年 6 月 12 日 by

blank

不,谢谢!

/.main_wrapper

<script defer type="text/javascript" src="https://s7.addthis.com/js/300/addthis_widget.js#pubid=gpsaddthis"></script>

noptimize

/noptimize

AI 可能会生成不准确的信息,请核实重要内容

3 NumPy Tricks for Numerical Performance | KDnuggets | traeai