簡體   English   中英

Python 的總和與 NumPy 的 numpy.sum

[英]Python's sum vs. NumPy's 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 我很好奇使用 NumPy 標量數據類型(例如numpy.int32來處理在 Python 代碼中添加或減去很多的值是否更快。

為了澄清numpy.ndarray ,我正在研究生物信息學模擬,該模擬部分包括將多維numpy.ndarray折疊成單個標量和,然后對其進行額外處理。 我正在使用 Python 3.2 和 NumPy 1.6。

提前致謝!

我很好奇並計時。 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

[...] 我的 [...] 問題是在 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(C 語言)指向 Python 對象(在本例中為 Python 整數)指針數組的包裝器。 這些整數可以被看作是一個 C 整數的包裝器(實際上並不正確,因為 Python 整數可以是任意大的,所以它不能簡單地使用一個C 整數,但它已經足夠接近了)。

例如,像[1, 2, 3]這樣的列表(示意性地,我省略了一些細節)會像這樣存儲:

在此處輸入圖片說明

然而,NumPy 數組是包含 C 值的 C 數組的包裝器(在這種情況下, intlong取決於 32 位或 64 位並取決於操作系統)。

所以像np.array([1, 2, 3])這樣的 NumPy 數組看起來像這樣:

在此處輸入圖片說明

接下來要了解的是這些函數是如何工作的:

  • Python sum迭代可迭代對象(在本例中為列表或數組)並添加所有元素。
  • NumPys sum方法迭代存儲的 C 數組並添加這些 C 值,最后將該值包裝在 Python 類型(在本例中為numpy.int32 (或numpy.int64 ))並返回它。
  • NumPys sum函數將輸入轉換為array (至少如果它不是數組),然后使用 NumPy sum方法

顯然,從 C 數組中添加 C 值比添加 Python 對象要快得多,這就是為什么 NumPy 函數可以更快的原因(參見上面的第二個圖,數組上的 NumPy 函數遠遠超過了大型數組的 Python 總和)。

但是將 Python 列表轉換為 NumPy 數組相對較慢,然后您仍然需要添加 C 值。 這就是為什么對於列表,Python sum會更快。

唯一剩下的懸而未決的問題是為什么 Python 對array sum這么慢(它是所有比較函數中最慢的)。 這實際上與 Python 的 sum 簡單地迭代您傳入的任何內容有關。在列表的情況下,它獲取存儲的Python 對象,但在 1D NumPy 數組的情況下,沒有存儲的 Python 對象,只有 C 值,所以 Python&NumPy 必須為每個元素創建一個 Python 對象(一個numpy.int32numpy.int64 ),然后必須添加這些 Python 對象。 為 C 值創建包裝器是讓它變得非常慢的原因。

此外,使用 Python 整數與標量 numpy.int32 的含義(包括性能)是什么? 例如,對於 += 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速度更快,因此每個循環產生的開銷更少。 因為我只對加法/減法時間感興趣,所以實際上最好不要讓循環開銷與時間混淆(至少不是那么多)。

請注意,多維 numpy 數組上的 Python 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

Numpy 應該快得多,尤其是當您的數據已經是一個 numpy 數組時。

Numpy 數組是標准 C 數組上的一個薄層。 當 numpy sum 對此進行迭代時,它不會進行類型檢查,而且速度非常快。 速度應該與使用標准 C 進行操作相當。

相比之下,使用 python 的 sum 必須首先將 numpy 數組轉換為 python 數組,然后迭代該數組。 它必須進行一些類型檢查,並且通常會更慢。

python sum 比 numpy sum 慢的確切數量沒有很好地定義,因為與在 python 中編寫自己的 sum 函數相比,python sum 將是一個稍微優化的函數。

這是Akavall 上面回答帖子的擴展 從該答案中您可以看到np.sumnp.array對象執行得更快,而sumlist對象執行得更快。 對此進行擴展:

在為np.array對象運行np.sum 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

以上, sum是一點點的速度比np.array ,雖然有時我見過np.sum時序為14.1 µs ,太。 但大多數情況下,它是14.3 µs

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM