Python的和与NumPy的numpy.和

vyu0f0g1  于 2022-12-29  发布在  Python
关注(0)|答案(6)|浏览(146)

使用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更快吗?

另外,这意味着什么(包括性能)。例如,对于a += 1a的类型是Python整数还是numpy.int32,在行为或性能上会有差异吗?我很好奇,对于在Python代码中经常加减的值,使用NumPy标量数据类型(如numpy.int32)是否更快。
为了澄清一下,我正在进行一个生物信息学模拟,其中一部分是将多维numpy.ndarray折叠成单个标量和,然后进行额外的处理,我使用的是Python 3.2和NumPy 1.6。

0ejtzxu1

0ejtzxu11#

我很好奇,并计算了它的时间。numpy.sum对于numpy数组似乎要快得多,但对于列表则要慢得多。

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

3qpi33ja

3qpi33ja2#

[...]我的问题是,在Python整数列表上使用numpy.sum会比使用Python自己的sum更快吗?
这个问题的答案是:没有。
Python的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)

左:在NumPy数组上;右:在Python列表中。注意,这是一个双对数图,因为基准测试涵盖了非常广泛的值。然而,对于定性结果:越低越好。
这表明对于列表,Python sum总是更快,而np.sum或数组上的sum方法将更快(除了非常短的数组,其中Python sum更快)。
如果你有兴趣比较一下,我还做了一个图,包括所有这些:

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

有趣的是,numpy可以在数组上与Python和列表竞争的点大约是200个元素!注意,这个数字可能取决于很多因素,比如Python/NumPy版本,......不要把它看得太字面了。
没有提到的是造成这种差异的原因(我指的是大规模的差异,而不是短列表/数组的差异,因为它们的函数只是具有不同的常量开销)假设CPython,Python列表是C++的 Package 器(C语言)指向Python对象的指针数组(在本例中为Python整数)。这些整数可以看作是C整数的 Package 器(实际上并不正确,因为Python的整数可以是任意大的,所以它不能简单地使用oneC整数,但它已经足够接近了)。
例如,像[1, 2, 3]这样的列表(示意性地,我省略了一些细节)将如下存储:

然而NumPy数组是包含C值的C数组的 Package 器(在本例中,intlong取决于32或64位以及操作系统)。
因此,np.array([1, 2, 3])这样的NumPy数组看起来如下所示:

接下来要了解的是这些函数是如何工作的:

  • Python sum迭代可迭代对象(在本例中为列表或数组),并将所有元素相加。
  • NumPys sum方法迭代存储的C数组,并将这些C值相加,最后将该值 Package 为Python类型(在本例中为numpy.int32(或numpy.int64))并返回。
  • NumPys sum函数将输入转换为array(至少在它还不是数组时),然后使用NumPy sum方法

显然,从C数组中添加C值比添加Python对象要快得多,这就是NumPy函数可以快得多的原因(参见上面的第二张图,NumPy函数在数组上的速度远远超过Python对大型数组求和的速度)。
但是将Python列表转换为NumPy数组相对较慢,而且还需要加上C值,这就是为什么对于列表,Python sum会更快。
剩下的唯一问题是,为什么array上的Pythons sum速度如此之慢(这是所有比较函数中最慢的),这实际上与Pythons sum简单地迭代你传入的任何东西有关,在列表的情况下,它得到存储的Python对象,但在一维NumPy数组的情况下,没有存储的Python对象,只有C值,所以Python和NumPy必须为每个元素创建一个Python对象(numpy.int32numpy.int64),然后必须添加这些Python对象。为C值创建 Package 器是它真正慢的原因。
另外,使用Python整数和标量numpy.int32的含义(包括性能)是什么?例如,对于a += 1,如果a的类型是Python整数或numpy.int32,是否存在行为或性能差异?
我做了一些测试,对于标量的加法和减法,你绝对应该坚持使用Python整数,尽管可能会有一些缓存,这意味着下面的测试可能不完全具有代表性:

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)

用Python整数做标量运算比用NumPy标量快3 - 6倍,我还没有检查为什么会这样,但我猜NumPy标量很少使用,可能没有优化性能。
如果实际执行两个操作数都是numpy标量的算术运算,则差异会小一些:

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)

那就只会慢2倍。
如果你想知道为什么我在这里使用itertools.repeat,而我可以简单地使用for _ in range(...)。原因是repeat更快,因此每个循环的开销更少。因为我只对加法/减法时间感兴趣,实际上最好不要让循环开销扰乱计时(至少不那么多)。

hgncfbus

hgncfbus3#

注意,Python对多维numpy数组求和只会沿着第一个轴求和:

sum(np.array([[[2,3,4],[4,5,6]],[[7,8,9],[10,11,12]]]))
Out[47]: 
array([[ 9, 11, 13],
       [14, 16, 18]])

np.sum(np.array([[[2,3,4],[4,5,6]],[[7,8,9],[10,11,12]]]), axis=0)
Out[48]: 
array([[ 9, 11, 13],
       [14, 16, 18]])

np.sum(np.array([[[2,3,4],[4,5,6]],[[7,8,9],[10,11,12]]]))
Out[49]: 81
jhkqcmku

jhkqcmku4#

Numpy应该快得多,特别是当您的数据已经是一个numpy数组时。
Numpy数组是标准C数组的一个薄层。当numpy sum在这个数组上迭代时,它不做类型检查,而且速度非常快。速度应该可以和用标准C做操作相比。
相比之下,使用python的sum必须先将numpy数组转换为python数组,然后迭代该数组,必须做一些类型检查,通常会比较慢。
python sum比numpy sum慢的确切数量并没有很好的定义,因为与用python编写自己的sum函数相比,python sum将是一个稍微优化的函数。

ffx8fchx

ffx8fchx5#

这是对answer post above by Akavall的扩展。从这个答案可以看出,np.sum对于np.array对象执行得更快,而sum对于list对象执行得更快。
在为np.array对象运行np.sum与为list对象运行sum相比,它们的表现似乎并驾齐驱。

# 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

在上面,sumnp.array快了一点,尽管有时候我看到np.sum的时间也是14.1 µs,但大多数时候,它是14.3 µs

ozxc1zmp

ozxc1zmp6#

如果使用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

相关问题