【问题标题】:Why is numpy.array so slow?为什么 numpy.array 这么慢?
【发布时间】:2011-09-27 10:01:09
【问题描述】:

我对此感到困惑

def main():
    for i in xrange(2560000):
        a = [0.0, 0.0, 0.0]

main()

$ time python test.py

real     0m0.793s

现在让我们用 numpy 来看看:

import numpy

def main():
    for i in xrange(2560000):
        a = numpy.array([0.0, 0.0, 0.0])

main()

$ time python test.py

real    0m39.338s

神圣的 CPU 循环蝙蝠侠!

使用numpy.zeros(3) 会有所改善,但恕我直言仍然不够

$ time python test.py

real    0m5.610s
user    0m5.449s
sys 0m0.070s

numpy.version.version = '1.5.1'

如果您想知道在第一个示例中是否跳过列表创建进行优化,它不是:

  5          19 LOAD_CONST               2 (0.0)
             22 LOAD_CONST               2 (0.0)
             25 LOAD_CONST               2 (0.0)
             28 BUILD_LIST               3
             31 STORE_FAST               1 (a)

【问题讨论】:

  • 快速思考:numpy.array 实际上是一个比列表更复杂的数据结构。在第二个 sn-p 中,您创建一个列表一个 numpy 数组(在第一个中只有一个列表)。这是否是造成如此大差异的唯一原因,我不能说。
  • @Felix:好的,但是列表的创建速度很快,所以即使我在第二种情况下创建了一个列表和一个numpy数组,这里的热点仍然是numpy创建,而且无论结构多么复杂,它仍然非常昂贵......
  • 但请考虑:在使用 numpy 的复杂应用程序中,创建数据很少是瓶颈。我也不知道幕后发生了什么,但它显然使数学繁重的程序在一天结束时变得更快,所以没有理由抱怨;)
  • @Stefano:你不是在计时中包括了 numpy 的导入吗? (python 也有一个内置的计时模块。)
  • 快速提示,您可以使用python -mtimeit test.py 进行基准测试。

标签: python performance numpy


【解决方案1】:

Numpy 针对大量数据进行了优化。给它一个很小的 ​​3 长度数组,不出所料,它的性能很差。

考虑单独测试

import timeit

reps = 100

pythonTest = timeit.Timer('a = [0.] * 1000000')
numpyTest = timeit.Timer('a = numpy.zeros(1000000)', setup='import numpy')
uninitialised = timeit.Timer('a = numpy.empty(1000000)', setup='import numpy')
# empty simply allocates the memory. Thus the initial contents of the array 
# is random noise

print 'python list:', pythonTest.timeit(reps), 'seconds'
print 'numpy array:', numpyTest.timeit(reps), 'seconds'
print 'uninitialised array:', uninitialised.timeit(reps), 'seconds'

输出是

python list: 1.22042918205 seconds
numpy array: 1.05412316322 seconds
uninitialised array: 0.0016028881073 seconds

似乎是数组的归零一直在花费 numpy.因此,除非您需要初始化数组,否则请尝试使用 empty。

【讨论】:

  • 为了公平起见,你应该这样做pythonTest = timeit.Timer('a = [0.] * 1000000'),它的执行速度仍然比 numpy 慢,但它比 LC 快得多。而且它“更接近”列表文字(如问题中给出的那样),因为它不运行 Python 循环。
  • @Rosh 好点。我想我一直回避 * 运算符的列表,因为它将相同的对象放在每个索引中。虽然由于数字是不可变的,但在这种情况下并不重要。尽管尝试对列表/数组执行批量操作,但 numpy 再次领先(例如 arr += 1)。
  • 非常好,谢谢。考虑到结果,您对小型阵列有何建议?我的意思是,列表和元组在涉及基本数组操作(例如向量-向量乘积、数组乘以数字等、小矩阵的行列式)时并不是很好当然我可以自己重新实现算法,它不是这里的大问题,但如果已经有一些东西,我认为它是首选的解决方案。
  • 单独的问题?但无论如何,itertools 文档建议您可以结合使用 itertool 和 operator 函数来制作非常有效的向量函数。
  • @Stefano Borini:简单;不要尝试使用小的 numpy 数组进行优化。相反,尝试为更大的块合并操作。无论如何,您的咆哮似乎仅基于创建小数组。请描述您的实际问题,以确定是否更适合在“纯python”或“numpy”领域解决。谢谢
【解决方案2】:

Holy CPU cycles batman!,确实如此。

但请考虑一些与numpy 相关的非常基本的东西;复杂的基于线性代数的功能(如random numberssingular value decomposition)。现在,考虑这些极其简单的计算:

In []: A= rand(2560000, 3)
In []: %timeit rand(2560000, 3)
1 loops, best of 3: 296 ms per loop
In []: %timeit u, s, v= svd(A, full_matrices= False)
1 loops, best of 3: 571 ms per loop

请相信我,目前可用的任何软件包都不会显着击败这种性能。

所以,请描述你真正的问题,我会尝试找出合适的基于numpy 的解决方案。

更新:
以下是光线球相交的一些简单代码:

import numpy as np

def mag(X):
    # magnitude
    return (X** 2).sum(0)** .5

def closest(R, c):
    # closest point on ray to center and its distance
    P= np.dot(c.T, R)* R
    return P, mag(P- c)

def intersect(R, P, h, r):
    # intersection of rays and sphere
    return P- (h* (2* r- h))** .5* R

# set up
c, r= np.array([10, 10, 10])[:, None], 2. # center, radius
n= 5e5
R= np.random.rand(3, n) # some random rays in first octant
R= R/ mag(R) # normalized to unit length

# find rays which will intersect sphere
P, b= closest(R, c)
wi= b<= r

# and for those which will, find the intersection
X= intersect(R[:, wi], P[:, wi], r- b[wi], r)

显然我们计算正确:

In []: allclose(mag(X- c), r)
Out[]: True

还有一些时间安排:

In []: % timeit P, b= closest(R, c)
10 loops, best of 3: 93.4 ms per loop
In []: n/ 0.0934
Out[]: 5353319 #=> more than 5 million detection's of possible intersections/ s
In []: %timeit X= intersect(R[:, wi], P[:, wi], r- b[wi])
10 loops, best of 3: 32.7 ms per loop
In []: X.shape[1]/ 0.0327
Out[]: 874037 #=> almost 1 million actual intersections/ s

这些计时是用非常普通的机器完成的。使用现代机器,仍然可以预期显着的加速。

无论如何,这只是一个简短的演示如何使用numpy 进行编码。

【讨论】:

  • @Stefano Borini:更新了我的答案。谢谢
  • 好。但是,它实际上并不允许您以这种方式直接处理 Sphere 对象。您必须有一个后端,将高级设计转换为一组聚合的坐标,然后将这些坐标提供给 numpy。
  • +1 表示“请考虑一些与 numpy 相关的非常基本的东西”
  • @Stefano Borini:FWIW 至少你似乎有很多光线。我仍然建议将所有“永久”点保存在数组中并编写这样的代码,让numpy 处理临时变量,即尽量减少创建小型numpy 数组的需要。祝你好运!谢谢
【解决方案3】:

迟到的答案,但可能对其他观众很重要。

kwant 项目中也考虑了这个问题。 确实,小数组在 numpy 中没有优化,而且经常小数组正是您所需要的。

在这方面,他们创建了一个小数组的替代品,它与 numpy 数组表现并共存(新数据类型中的任何未实现的操作都由 numpy 处理)。

你应该看看这个项目:
https://pypi.python.org/pypi/tinyarray/1.0.5
其主要目的是为小型阵列表现良好。当然,你可以用 numpy 做的一些更花哨的事情不受此支持。但数字似乎是您的要求。

我做了一些小测试:

蟒蛇

我添加了 numpy 导入以正确加载时间

import numpy

def main():
    for i in xrange(2560000):
        a = [0.0, 0.0, 0.0]

main()

numpy

import numpy

def main():
    for i in xrange(2560000):
        a = numpy.array([0.0, 0.0, 0.0])

main()

numpy-零

import numpy

def main():
    for i in xrange(2560000):
        a = numpy.zeros((3,1))

main()

小阵列

import numpy,tinyarray

def main():
    for i in xrange(2560000):
        a = tinyarray.array([0.0, 0.0, 0.0])

main()

tinyarray-零

import numpy,tinyarray

def main():
    for i in xrange(2560000):
        a = tinyarray.zeros((3,1))

main()

我运行了这个:

for f in python numpy numpy_zero tiny tiny_zero ; do 
   echo $f 
   for i in `seq 5` ; do 
      time python ${f}_test.py
   done 
 done

得到:

python
python ${f}_test.py  0.31s user 0.02s system 99% cpu 0.339 total
python ${f}_test.py  0.29s user 0.03s system 98% cpu 0.328 total
python ${f}_test.py  0.33s user 0.01s system 98% cpu 0.345 total
python ${f}_test.py  0.31s user 0.01s system 98% cpu 0.325 total
python ${f}_test.py  0.32s user 0.00s system 98% cpu 0.326 total
numpy
python ${f}_test.py  2.79s user 0.01s system 99% cpu 2.812 total
python ${f}_test.py  2.80s user 0.02s system 99% cpu 2.832 total
python ${f}_test.py  3.01s user 0.02s system 99% cpu 3.033 total
python ${f}_test.py  2.99s user 0.01s system 99% cpu 3.012 total
python ${f}_test.py  3.20s user 0.01s system 99% cpu 3.221 total
numpy_zero
python ${f}_test.py  1.04s user 0.02s system 99% cpu 1.075 total
python ${f}_test.py  1.08s user 0.02s system 99% cpu 1.106 total
python ${f}_test.py  1.04s user 0.02s system 99% cpu 1.065 total
python ${f}_test.py  1.03s user 0.02s system 99% cpu 1.059 total
python ${f}_test.py  1.05s user 0.01s system 99% cpu 1.064 total
tiny
python ${f}_test.py  0.93s user 0.02s system 99% cpu 0.955 total
python ${f}_test.py  0.98s user 0.01s system 99% cpu 0.993 total
python ${f}_test.py  0.93s user 0.02s system 99% cpu 0.953 total
python ${f}_test.py  0.92s user 0.02s system 99% cpu 0.944 total
python ${f}_test.py  0.96s user 0.01s system 99% cpu 0.978 total
tiny_zero
python ${f}_test.py  0.71s user 0.03s system 99% cpu 0.739 total
python ${f}_test.py  0.68s user 0.02s system 99% cpu 0.711 total
python ${f}_test.py  0.70s user 0.01s system 99% cpu 0.721 total
python ${f}_test.py  0.70s user 0.02s system 99% cpu 0.721 total
python ${f}_test.py  0.67s user 0.01s system 99% cpu 0.687 total

现在这些测试(如前所述)不是最好的测试。但是,它们仍然表明 tinyarray 更适合小型阵列。
另一个事实是,使用 tinyarray 最常见的操作应该更快。因此,它可能比仅创建数据具有更好的使用优势。

我从未在一个成熟的项目中尝试过它,但kwant 项目正在使用它

【讨论】:

  • 在旁注中,如果某些numpy 函数产生了太多开销,有时通过单个函数推迟它而不是在模块中查找它会受益,即@ 987654332@.
【解决方案4】:

当然 numpy 在这种情况下会消耗更多时间,因为:a = np.array([0.0, 0.0, 0.0]) a = [0.0, 0.0, 0.0]; a = np.array(a),它需要两个步骤。但是 numpy-array 有很多优点,它的高速度可以从对它们的操作中看出,而不是在它们的创建上。我个人想法的一部分:)。

【讨论】:

    猜你喜欢
    • 2021-09-03
    • 2016-09-28
    • 2020-02-08
    • 2012-07-17
    • 2011-11-07
    • 2015-08-24
    • 2013-08-06
    • 2014-07-16
    相关资源
    最近更新 更多