【问题标题】:Why are NumPy arrays so fast?为什么 NumPy 数组这么快?
【发布时间】:2012-01-13 05:02:58
【问题描述】:

我刚刚更改了我正在编写的程序,以将我的数据保存为 numpy 数组,因为我遇到了性能问题,并且差异令人难以置信。原来运行需要 30 分钟,现在需要 2.5 秒!

我想知道它是如何做到的。我认为是因为它消除了对 for 循环的需要,但除此之外我被难住了。

【问题讨论】:

  • 我猜这是因为 numpy 数组是用 C 而不是 Python 实现的。
  • @NoufalIbrahim:Python 列表也是implemented in C
  • 相当模糊的问题,没有任何迹象表明这两个不同的程序在做什么以及它们是如何实施的。

标签: python arrays numpy


【解决方案1】:

Numpy 数组是同构类型的密集排列的数组。相比之下,Python 列表是指向对象的指针数组,即使它们都属于同一类型。因此,您将获得locality of reference 的好处。

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

【讨论】:

  • 如何为这些 C 编写的操作提供 Python 前端?这种技术叫什么名字?
  • 这不可能。当元素是原始类型(如整数)时,Python 列表不是指针数组。一种快速的测试方法是将一个数字保存到一个变量中,并在其中形成一个包含该变量的数组。如果改变变量,数组不会改变。
  • @Rohan 请记住,即使原始类型也是对象。因此,当您将该变量添加到列表时,您实际上只是将特定变量指向的对象添加到列表中。在这种情况下,这个对象是一个数字。因此,当您更改变量,或者更准确地说,将名称重新绑定到新整数时,您不会更改原始对象的属性,即原始数字。因此,预计数组中的“对应”数字不会改变其值。
  • @Kun 因此,如果我理解正确,如果第二个列表中更改的值不是原始类型,那么您正在更改“相同”对象的内容,而如果您更改原始类型,您现在正在引用不同的对象?
  • @Rohan 完全错误。您提出的测试甚至无法证明这一点。
【解决方案2】:

Numpy 数组与 c 中的“普通”数组极为相似。请注意,每个元素都必须属于同一类型。加速非常好,因为您可以利用预取,并且可以通过索引立即访问数组中的任何元素。

【讨论】:

  • 您能否详细说明每个元素具有相同类型如何使计算更快?
【解决方案3】:

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

例如如果您要对两个数组求和,则将使用专门的 CPU vector operations 执行加法,而不是在循环中调用 int 加法的 python 实现。

【讨论】:

  • 这些(专门的操作和动态优化)是正确的答案。只有在解决了主要性能因素(解释器开销)之后,诸如预取和参考位置等次要因素才会变得重要。
  • 引用的局部性很重要,原因有二:局部性本身(及其对缓存的影响),以及缺少间接性意味着可以跳过处理间接性的指令。
  • 正确。一种机制是 Atlas (math-atlas.sourceforge.net/faq.html#what),它是可以使用机器特定指令的专用库。
【解决方案4】:

你仍然有 for 循环,但它们是在 c 中完成的。 Numpy 基于 Atlas,这是一个用于线性代数运算的库。

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

当面临大型计算时,它将使用多种实现运行测试,以找出目前我们计算机上最快的一种。对于一些 numpy 构建,计算可能会在多个 cpu 上并行化。因此,您将在连续内存块上运行高度优化的 c。

【讨论】:

  • Numpy 不是基于 Atlas。如果可用,它可以将 BLAS 实现用于其功能的非常非常小的子集(基本上是 dot、gemv 和 gemm)。该 BLAS 可以是它附带的内置参考 BLAS,也可以是 Atlas,也可以是 Intel MKL(enthought 发行版就是以此构建的)。
  • @talonmies 嗨,您能否提供一些有用的链接,其中包含有关您所说内容的文档?
  • @SebMa 请参阅numpy.org/install,“NumPy 包和加速线性代数库”一章。
  • @Thomas 谢谢 :)
【解决方案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 循环无法利用它。

【讨论】:

  • 请考虑将您的代码添加为文本(使用代码标记),而不是您的代码图像。它使读者更容易获得您的答案。
  • 并行性似乎不太可能是 250 倍改进的主要原因。没有 250 个 CPU 线程可供并行化。
  • 这是最合适的解释
  • 不,numpy 不使用低级并行性(尽管特定的 BLAS 库可能会将其用于dot。)主要的速度差异是由于编译循环与解释循环。
【解决方案6】:

Numpy 数组作为连续的内存块存储在内存中,而 python 列表作为分散在内存中的小块存储,因此在 numpy 数组中内存访问既容易又快速,而在 python 列表中内存访问既困难又慢。

来源:https://algorithmdotcpp.blogspot.com/2022/01/prove-numpy-is-faster-than-normal-list.html

【讨论】:

  • 虽然此链接可能会回答问题,但最好在此处包含答案的基本部分并提供链接以供参考。如果链接页面发生更改,仅链接答案可能会失效。 - From Review
猜你喜欢
  • 2020-06-08
  • 2014-05-28
  • 1970-01-01
  • 2014-11-15
  • 1970-01-01
  • 2022-01-14
  • 2018-03-22
  • 2021-12-19
  • 2013-09-09
相关资源
最近更新 更多