Python总和vs. NumPy numpy.sum

时间:2012-06-06 21:01:39

标签: python performance numpy python-3.x

使用Python的原生sum函数和NumPy的numpy.sum之间在性能和行为方面有何不同? sum适用于NumPy的数组,numpy.sum适用于Python列表,它们都返回相同的有效结果(没有测试边缘情况,如溢出)但不同的类型。

>>> import numpy as np
>>> np_a = np.array(range(5))
>>> np_a
array([0, 1, 2, 3, 4])
>>> type(np_a)
<class 'numpy.ndarray')

>>> py_a = list(range(5))
>>> py_a
[0, 1, 2, 3, 4]
>>> type(py_a)
<class 'list'>

# The numerical answer (10) is the same for the following sums:
>>> type(np.sum(np_a))
<class 'numpy.int32'>
>>> type(sum(np_a))
<class 'numpy.int32'>
>>> type(np.sum(py_a))
<class 'numpy.int32'>
>>> type(sum(py_a))
<class 'int'>

编辑:我认为我的实际问题是在Python整数列表中使用numpy.sum要比使用Python自己的sum更快吗?

此外,使用Python整数与标量numpy.int32有什么影响(包括性能)?例如,对于a += 1,如果a的类型是Python整数还是numpy.int32,是否存在行为或性能差异?我很好奇是否可以更快地使用NumPy标量数据类型(例如numpy.int32)来获取在Python代码中添加或减去很多的值。

为了澄清,我正在进行生物信息学模拟,其中部分包括将多维numpy.ndarray折叠成单个标量和,然后进行额外处理。我使用的是Python 3.2和NumPy 1.6。

提前致谢!

6 个答案:

答案 0 :(得分:55)

我好奇并定时了。对于numpy数组,numpy.sum似乎要快得多,但在列表上要慢得多。

import numpy as np
import timeit

x = range(1000)
# or 
#x = np.random.standard_normal(1000)

def pure_sum():
    return sum(x)

def numpy_sum():
    return np.sum(x)

n = 10000

t1 = timeit.timeit(pure_sum, number = n)
print 'Pure Python Sum:', t1
t2 = timeit.timeit(numpy_sum, number = n)
print 'Numpy Sum:', t2

x = range(1000)时的结果:

Pure Python Sum: 0.445913167735
Numpy Sum: 8.54926219673

x = np.random.standard_normal(1000)时的结果:

Pure Python Sum: 12.1442425643
Numpy Sum: 0.303303771848

我正在使用Python 2.7.2和Numpy 1.6.1

答案 1 :(得分:13)

  

[...]这里的问题是在Python整数列表中使用numpy.sum比使用Python自己的sum更快吗?

这个问题的答案是:否。

Pythons sum在列表上会更快,而NumPys sum在阵列上会更快。我实际上做了一个基准来显示时间(Python 3.6,NumPy 1.14):

import random
import numpy as np
import matplotlib.pyplot as plt

from simple_benchmark import benchmark

%matplotlib notebook

def numpy_sum(it):
    return np.sum(it)

def python_sum(it):
    return sum(it)

def numpy_sum_method(arr):
    return arr.sum()

b_array = benchmark(
    [numpy_sum, numpy_sum_method, python_sum],
    arguments={2**i: np.random.randint(0, 10, 2**i) for i in range(2, 21)},
    argument_name='array size',
    function_aliases={numpy_sum: 'numpy.sum(<array>)', numpy_sum_method: '<array>.sum()', python_sum: "sum(<array>)"}
)

b_list = benchmark(
    [numpy_sum, python_sum],
    arguments={2**i: [random.randint(0, 10) for _ in range(2**i)] for i in range(2, 21)},
    argument_name='list size',
    function_aliases={numpy_sum: 'numpy.sum(<list>)', python_sum: "sum(<list>)"}
)

有了这些结果:

f, (ax1, ax2) = plt.subplots(1, 2, sharey=True)
b_array.plot(ax=ax1)
b_list.plot(ax=ax2)

enter image description here

左:在NumPy数组上;右:在Python列表上。 请注意,这是一个对数 - 对数图,因为基准测试涵盖了非常广泛的值。但是对于定性结果:降低意味着更好。

这表明,对于列表,Pythons sum总是更快,而np.sum或数组上的sum方法会更快(除了非常短的数组,其中Pythons sum更快)。

如果你有兴趣将这些相互比较,我也制作了一个包含所有这些的情节:

f, ax = plt.subplots(1)
b_array.plot(ax=ax)
b_list.plot(ax=ax)
ax.grid(which='both')

enter image description here

有趣的是,numpy可以与Python和列表在数组上竞争的点大约是200个元素!请注意,这个数字可能取决于很多因素,例如Python / NumPy版本......不要太过于字面意思。

没有提到的是这种差异的原因(我的意思是大规模差异而不是短列表/数组的差异,其中函数只是具有不同的常量开销)。假设CPython是一个Python列表,它是一个包含Python对象(在本例中为Python整数)的C(语言C)数组的包装器。这些整数可以看作围绕C整数的包装(实际上并不正确,因为Python整数可以任意大,所以它不能简单地使用一个 C整数,但它足够接近)。

例如,像[1, 2, 3]这样的列表(示意图,我遗漏了一些细节)就像这样存储:

enter image description here

NumPy数组是包含C值的C数组的包装器(在本例中为intlong,具体取决于32或64位,具体取决于操作系统)。

所以像np.array([1, 2, 3])这样的NumPy数组看起来像这样:

enter image description here

接下来要理解的是这些功能是如何工作的:

  • Pythons sum遍历iterable(在本例中为列表或数组)并添加所有元素。
  • NumPys sum 方法遍历存储的C数组并添加这些C值,最后将该值包装在Python类型中(在本例中为numpy.int32(或{{ 1}})并返回它。
  • NumPys numpy.int64 功能将输入转换为sum(至少如果它不是数组),然后使用NumPy {{1} } 方法

显然从C数组中添加C值要比添加Python对象快得多,这就是NumPy函数可以快得多的原因(参见上面的第二个图,数组上的NumPy函数击败了到目前为止大型数组的Python总和)。

但是将Python列表转换为NumPy数组相对较慢,然后您仍然需要添加C值。这就是列表的原因,Python array会更快。

唯一剩下的未解决问题是为什么sum上的Pythons sum如此缓慢(它是所有比较函数中最慢的)。这实际上与Pythons sum简单迭代你传入的任何事实有关。如果是列表,它会获得存储的 Python对象但是在1D NumPy数组的情况下没有存储Python对象,只是C值,因此Python和NumPy必须为每个元素创建一个Python对象(sumarray),然后必须添加这些Python对象。为C值创建包装器的原因是它非常慢。

  

此外,使用Python整数与标量numpy.int32有什么影响(包括性能)?例如,对于a + = 1,如果a的类型是Python整数或numpy.int32,是否存在行为或性能差异?

我做了一些测试,对于标量的加法和减法,你一定要坚持使用Python整数。即使可能存在一些缓存,这意味着以下测试可能不具有完全代表性:

numpy.int32

使用Python整数进行标量操作比使用NumPy标量快3到6倍。我还没有检查过为什么会这样,但我的猜测是NumPy标量很少使用,可能没有针对性能进行优化。

如果您实际执行两个操作数都是numpy标量的算术运算,差异会变小一些:

numpy.int64

然后它只慢了2倍。

如果你想知道为什么我在这里使用from itertools import repeat python_integer = 1000 numpy_integer_32 = np.int32(1000) numpy_integer_64 = np.int64(1000) def repeatedly_add_one(val): for _ in repeat(None, 100000): _ = val + 1 %timeit repeatedly_add_one(python_integer) 3.7 ms ± 71.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) %timeit repeatedly_add_one(numpy_integer_32) 14.3 ms ± 162 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) %timeit repeatedly_add_one(numpy_integer_64) 18.5 ms ± 494 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) def repeatedly_sub_one(val): for _ in repeat(None, 100000): _ = val - 1 %timeit repeatedly_sub_one(python_integer) 3.75 ms ± 236 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) %timeit repeatedly_sub_one(numpy_integer_32) 15.7 ms ± 437 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) %timeit repeatedly_sub_one(numpy_integer_64) 19 ms ± 834 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) ,我可以简单地使用def repeatedly_add_one(val): one = type(val)(1) # create a 1 with the same type as the input for _ in repeat(None, 100000): _ = val + one %timeit repeatedly_add_one(python_integer) 3.88 ms ± 273 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) %timeit repeatedly_add_one(numpy_integer_32) 6.12 ms ± 324 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) %timeit repeatedly_add_one(numpy_integer_64) 6.49 ms ± 265 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) 。原因是itertools.repeat更快,因此每个循环的开销更少。因为我只对加法/减法时间感兴趣,所以最好不要让循环开销弄乱时间(至少没那么多)。

答案 2 :(得分:5)

Numpy应该快得多,特别是当你的数据已经是一个numpy数组时。

Numpy数组是标准C数组上的薄层。当numpy sum迭代时,它没有进行类型检查,而且速度非常快。速度应与使用标准C进行操作相当。

相比之下,使用python的总和,它必须首先将numpy数组转换为python数组,然后迭代该数组。它必须进行一些类型检查,并且通常会变慢。

python总和慢于numpy sum的确切数量没有明确定义,因为与在python中编写自己的sum函数相比,python sum将是一个稍微优化的函数。

答案 3 :(得分:3)

请注意,多维numpy数组上的Python和只会沿第一个轴执行求和:

Traceback (most recent call last):
  File "quickstart.py", line 76, in <module>
    main()
  File "quickstart.py", line 60, in main
    credentials = get_credentials()
  File "quickstart.py", line 48, in get_credentials
    credentials = tools.run_flow(flow, store, flags)
  File "/Library/Python/2.7/site-packages/oauth2client/util.py", line 135, in positional_wrapper
    return wrapped(*args, **kwargs)
  File "/Library/Python/2.7/site-packages/oauth2client/tools.py", line 199, in run_flow
    authorize_url = flow.step1_get_authorize_url()
  File "/Library/Python/2.7/site-packages/oauth2client/util.py", line 135, in positional_wrapper
    return wrapped(*args, **kwargs)
  File "/Library/Python/2.7/site-packages/oauth2client/client.py", line 2006, in step1_get_authorize_url
    return _update_query_params(self.auth_uri, query_params)
  File "/Library/Python/2.7/site-packages/oauth2client/client.py", line 490, in _update_query_params
    parts = urllib.parse.urlparse(uri)
AttributeError: 'Module_six_moves_urllib_parse' object has no attribute 'urlparse'

答案 4 :(得分:1)

这是answer post above by Akavall的扩展。从该答案可以看出np.sumnp.array个对象的执行速度更快,而sumlist个对象的执行速度更快。为了扩展它:

对于np.sum对象{strong>对象 np.array正在运行sum,似乎他们并驾齐驱。

list

在上方,# I'm running IPython In [1]: x = range(1000) # list object In [2]: y = np.array(x) # np.array object In [3]: %timeit sum(x) 100000 loops, best of 3: 14.1 µs per loop In [4]: %timeit np.sum(y) 100000 loops, best of 3: 14.3 µs per loop 比<{1}}快位,但有时我看到sum时间为{{1}也是。但大多数情况下,它是np.array

答案 5 :(得分:1)

如果您使用sum(),那么它会给出

a = np.arange(6).reshape(2, 3)
print(a)
print(sum(a))
print(sum(sum(a)))
print(np.sum(a))


>>>
[[0 1 2]
 [3 4 5]]
[3 5 7]
15
15