为什么NumPy数组如此快?

91

我刚刚修改了正在编写的程序,将我的数据存储为numpy数组,因为我遇到了性能问题,结果差异惊人。原本需要30分钟才能运行完毕,现在只需要2.5秒!

我很好奇它是如何做到的。我认为这是因为它消除了对for循环的需求,但除此之外,我感到困惑。


6
我猜这是因为NumPy数组是用C语言实现而不是Python。 - Noufal Ibrahim
16
Python的列表也是用C语言实现的。 - Fred Foo
15
这是一个相当含糊的问题,没有明确说明这两个不同的程序在做什么以及它们是如何实现的。 - David Heffernan
6个回答

129

Numpy数组是一种密集的同类型数组。相比之下,Python列表是对象指针的数组,即使它们都是相同类型的。因此,您可以获得引用局部性的好处。

此外,许多Numpy操作都是用C实现的,避免了Python循环的一般成本、指针间接和每个元素的动态类型检查。加速取决于您执行的操作,但在数字计算程序中,几个数量级的加速并不罕见。


5
如何为这些用C写的操作提供Python前端?这种技术叫什么名字? - user3046287
这不可能是真的。当元素为原始类型(如整数)时,Python列表不是指针数组。测试的快速方法是将一个数字保存到变量中,并在其中形成一个数组。如果更改变量,则数组不会更改。 - Rohan
17
请记住,即使是基本数据类型也是对象。因此,当您将该变量添加到列表中时,实际上只是将特定变量指向的对象添加到列表中。在这种情况下,该对象是一个数字。因此,当您更改变量,或者更准确地说,将名称重新绑定到新整数时,您并没有更改原始对象的属性,即原始数字。因此,预期数组中的“对应”数字不会更改其值。 - Kun
@Kun 如果我理解你的意思正确的话,如果第二个列表中被更改的值不是原始类型,那么你正在更改“相同”对象的内容,而如果你更改原始类型,你现在引用的是另一个对象? - IntegrateThis
@Rohan 那完全是错的。你提出的测试甚至都不能证明这一点。 - juanpa.arrivillaga

27

NumPy数组是专门的数据结构。这意味着您不仅可以获得高效的内存表示方式,还可以获得高效的专用实现。

例如,如果您正在对两个数组进行求和,则会使用专用CPU矢量操作执行加法,而不是在循环中调用Python整数加法的实现。


2
这些(专业操作和动态优化)是正确的答案。只有在解决主要性能因素(解释器开销)之后,预取和引用局部性等次要因素才会变得重要。 - Dave
4
引用局部性对于两个原因都很重要:一是因为引用的局部性本身(以及它对缓存的影响),二是因为缺乏间接引用意味着可以跳过处理间接引用的指令。 - Karl Knechtel
没错。其中一种机制是Atlas(http://math-atlas.sourceforge.net/faq.html#what),它使用的是专门针对机器特定指令的库。 - dfrankow

5

考虑以下代码:

import numpy as np
import time

a = np.random.rand(1000000)
b = np.random.rand(1000000)

tic = time.time()
c = np.dot(a, b)
toc = time.time()

print("Vectorised version: " + str(1000*(toc-tic)) + "ms")

c = 0
tic = time.time()
for i in range(1000000):
    c += a[i] * b[i]
toc = time.time()

print("For loop: " + str(1000*(toc-tic)) + "ms")

输出:

Vectorised version: 2.011537551879883ms
For loop: 539.8685932159424ms

Numpy在这里更快,因为它利用了并行性(即单指令多数据(SIMD)),而传统的for循环无法利用这一点。


1
请考虑将您的代码作为文本添加(使用代码标记),而不是代码图像。这样可以使您的答案更易于读者访问。 - Gavin
3
并行化似乎不太可能是250倍性能提升的主要原因。没有250个可用于并行化的CPU线程。 - Christian
1
这是最佳适合的解释。 - Cozy
3
不,NumPy不利用低级并行性(尽管某些BLAS库可能会在dot操作中使用它)。主要速度差异是由编译循环和解释循环引起的。 - hpaulj

1

虽然这个链接可能回答了问题,但最好在此处包含答案的基本部分并提供参考链接。如果链接页面更改,仅有链接的答案可能会失效。- 来自审查 - Peter Leimbigler

0
Numpy 数组与 C 语言中的“普通”数组非常相似。注意,每个元素必须是相同类型的。速度加快很大,因为可以利用预取和通过索引即时访问数组中的任何元素。

2
你能详细说明一下为什么每个元素具有相同的类型会使计算更快吗? - Rohan

0

你仍然可以使用for循环,但是它们是用c语言完成的。Numpy基于Atlas,这是一个线性代数操作库。

http://math-atlas.sourceforge.net/

面对大量的计算,我们会使用几个实现来运行测试,以找出当前计算机上哪一个是最快的。使用一些 numpy 构建,计算可能会在多个 CPU 上并行化处理。因此,您将得到高度优化的 C 代码,其运行在连续的内存块上。

11
Numpy不是基于Atlas开发的。如果可用,它可以使用BLAS实现对其功能的非常小的子集进行加速运算(基本上是点积、矩阵向量乘法和矩阵乘法)。BLAS可以是内置的参考BLAS,也可以是Atlas或者Intel MKL(Enthought发行版使用了Intel MKL)。 - talonmies
@talonmies您好,能否提供一些有关您所说的文档的有用链接? - SebMa
2
@SebMa 请查看 https://numpy.org/install/,章节“NumPy packages & accelerated linear algebra libraries”。 - Thomas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接