Python 的 sum 和 NumPy 的 NumPy.sum

使用 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,是否存在行为或性能差异?我很好奇,对于在 Python 代码中增加或减去很多的值,使用 NumPy 标量数据类型(如 numpy.int32)是否会更快。

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

先谢谢你!

69298 次浏览

Numpy 应该快得多,特别是当您的数据已经是 Numpy 数组时。

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

相比之下,使用 python 的 sum,它必须首先将 numpy 数组转换为 python 数组,然后在该数组上迭代。它必须做一些类型检查,通常会比较慢。

与在 python 中编写自己的 sum 函数相比,python sum 比 numpy sum 慢的确切数量没有得到很好的定义,因为 python sum 将是一个稍微优化过的函数。

我很好奇并且计算了时间。对于数字数组来说,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

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

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

这是 作者: Akavall的一个扩展。从这个答案可以看出,np.sumnp.array对象的执行速度更快,而 sumlist对象的执行速度更快。进一步说明:

在对 np.array对象运行 np.sum时,对 list对象运行 V. 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

以上,sum是一个 很小位比 np.array快,虽然,有时我看到 np.sum的时机是 14.1 µs了。但主要是 14.3 µs

[ ... ]我的[ ... ]问题是在 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)

enter image description here

左: 在 NumPy 数组上; 右: 在 Python 列表上。 请注意,这是一个对数日志图,因为基准测试覆盖了非常广泛的值范围。然而对于定性结果来说: 越低意味着越好。

这表明对于列表,Python sum总是更快,而数组上的 np.sumsum方法总是更快(Python 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 版本,... ... 不要太照字面意思理解。

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

例如,像 [1, 2, 3]这样的列表(按照示意图,我省略了一些细节)是这样存储的:

enter image description here

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

所以像 np.array([1, 2, 3])这样的 NumPy 数组应该是这样的:

enter image description here

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

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

显然,从 C 数组中添加 C 值要比添加 Python 对象快得多,这就是 NumPy 函数 可以要快得多的原因(参见上面的第二张图,对于大型数组,数组上的 NumPy 函数远远超过 Python 的求和)。

但是将 Python 列表转换为 NumPy 数组相对较慢,因此仍然需要添加 C 值。这就是为什么对于 清单,Python sum会更快。

剩下的唯一悬而未决的问题是,为什么在 array上的 Python sum如此之慢(它是所有比较函数中最慢的)。这实际上与 Python sum 只是迭代传入的任何内容有关。对于一个列表,它获取存储的 Python 对象,但对于一个1D NumPy 数组,没有存储的 Python 对象,只有 C 值,所以 Python & NumPy 必须为每个元素创建一个 Python 对象(numpy.int32numpy.int64) ,然后必须添加这些 Python 对象。正是为 C 值创建包装器使它变得非常慢。

此外,使用 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 标量很少被使用,而且可能没有针对性能进行优化。

如果实际执行算术运算,其中两个操作数都是数字标量,那么差异就会小一些:

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,而我可以简单地使用 for _ in range(...)代替。原因是 repeat更快,因此每个循环的开销更小。因为我只对加减法时间感兴趣,所以实际上最好不要让循环开销干扰计时(至少不要太多)。

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