71

sumPythonのネイティブ関数とNumPyのネイティブ関数を使用した場合のパフォーマンスと動作の違いは何numpy.sumですか?sumNumPyの配列で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'>

編集:ここでの私の実際的な質問はnumpy.sum、Python整数のリストで使用する方が、Python独自の整数を使用するよりも速いと思いsumますか?

さらに、Python整数とスカラーを使用することの意味(パフォーマンスを含む)はnumpy.int32何ですか?たとえば、の場合、のタイプがPython整数または?a += 1の場合、動作またはパフォーマンスの違いはありますか?Pythonコードで多くの値が加算または減算される値など、NumPyスカラーデータ型を使用する方が速いかどうか知りたいです。anumpy.int32numpy.int32

明確にするために、私はバイオインフォマティクスシミュレーションに取り組んでいます。これは、多次元numpy.ndarrayを単一のスカラー和に折りたたむことで部分的に構成され、その後さらに処理されます。Python3.2とNumPy1.6を使用しています。

前もって感謝します!

4

6 に答える 6

82

私は興味を持ち、時間を計りました。numpy.sumnumpy配列の場合ははるかに高速に見えますが、リストの場合ははるかに低速です。

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

Python2.7.2とNumpy1.6.1を使用しています

于 2012-06-06T21:43:54.530 に答える
61

[...]ここでの私の[...]質問はnumpy.sum、Python整数のリストで使用する方が、Python独自の整数を使用するよりも高速sumでしょうか?

この質問に対する答えは次のとおりです。いいえ。

Pythonの合計はリストでより速くなり、NumPysの合計は配列でより速くなります。私は実際にタイミングを示すためにベンチマークを行いました(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リスト上。ベンチマークは非常に広範囲の値をカバーしているため、これは両対数プロットであることに注意してください。ただし、定性的な結果の場合:低いほど良いことを意味します。

これは、リストの場合、Pythonsumが常に高速であるのに対し、配列np.sumsumメソッドが高速になることを示しています(Pythonが高速である非常に短い配列を除くsum)。

これらを互いに比較することに興味がある場合に備えて、私はそれらすべてを含むプロットも作成しました。

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

ここに画像の説明を入力してください

興味深いことに、numpyPythonやリストと配列で競合できるポイントは、およそ200要素です。この数は、Python / NumPyのバージョンなど、多くの要因に依存する可能性があることに注意してください...文字通りに解釈しすぎないでください。

言及されていないのは、この違いの理由です(つまり、関数が単に異なる一定のオーバーヘッドを持つ短いリスト/配列の違いではなく、大規模な違いを意味します)。CPythonを想定すると、PythonリストはPythonオブジェクト(この場合はPython整数)へのポインターのC(言語C)配列のラッパーです。これらの整数は、C整数のラッパーと見なすことができます(Python整数は任意に大きくなる可能性があるため、1つのC整数を単純に使用することはできませんが、十分に近いため、実際には正しくありません)。

たとえば、次のようなリスト[1, 2, 3]が(概略的には、いくつかの詳細を省略して)次のように保存されます。

ここに画像の説明を入力してください

ただし、NumPy配列は、C値を含むC配列のラッパーです(この場合int、またはlong32ビットまたは64ビットに依存し、オペレーティングシステムに依存します)。

したがって、NumPy配列は次のようnp.array([1, 2, 3])になります。

ここに画像の説明を入力してください

次に理解すべきことは、これらの関数がどのように機能するかです。

  • Pythonsumは、反復可能ファイル(この場合はリストまたは配列)を反復処理し、すべての要素を追加します。
  • NumPyssum メソッドは、格納されているC配列を反復処理し、これらのC値を追加し、最後にその値をPython型(この場合はnumpy.int32(またはnumpy.int64))でラップして返します。
  • NumPyssum 関数は、入力をarray(少なくともまだ配列でない場合は)に変換してから、NumPysum メソッドを使用します。

明らかに、C配列からC値を追加する方が、Pythonオブジェクトを追加するよりもはるかに高速です。そのため、NumPy関数はるかに高速になります(上記の2番目のプロットを参照してください。配列のNumPy関数は、大きな配列のPythonの合計をはるかに上回ります)。

ただし、PythonリストをNumPy配列に変換するのは比較的遅いため、C値を追加する必要があります。これが、リストの場合、Pythonsumが高速になる理由です。

残っている唯一の未解決の質問は、なぜPythonsumarray非常に遅いのかということです(比較されたすべての関数の中で最も遅い)。そして、それは実際には、Pythonの合計が渡されたものを単純に繰り返すという事実と関係があります。リストの場合は格納されたPythonオブジェクトを取得しますが、1D NumPy配列の場合は格納されたPythonオブジェクトはなく、C値のみです。そのため、Python&NumPyは要素ごとにPythonオブジェクト(numpy.int32または)を作成する必要があり、次にこれらのPythonオブジェクトを追加する必要があります。numpy.int64C値のラッパーを作成すると、処理が非常に遅くなります。

さらに、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より高速であるため、ループあたりのオーバーヘッドが少なくなるためです。私は加算/減算時間にのみ関心があるので、実際には、ループするオーバーヘッドがタイミングを乱さないことが望ましいです(少なくともそれほど多くはありません)。

于 2018-04-18T20:29:50.853 に答える
8

多次元のnumpy配列でのPythonの合計は、最初の軸に沿った合計のみを実行することに注意してください。

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
于 2016-06-06T20:42:28.253 に答える
6

Numpyは、特にデータがすでにNumpy配列である場合は、はるかに高速になるはずです。

Numpyアレイは、標準のCアレイ上の薄いレイヤーです。numpy sumがこれを繰り返す場合、型チェックは行われず、非常に高速です。速度は、標準Cを使用して操作を行うのと同等である必要があります。

それに比べて、Pythonの合計を使用すると、最初にnumpy配列をpython配列に変換してから、その配列を反復処理する必要があります。何らかの型チェックを行う必要があり、通常は遅くなります。

Pythonの合計がnumpyの合計よりも遅い正確な量は、Pythonで独自の合計関数を作成する場合と比較して、Pythonの合計がある程度最適化された関数になるため、明確に定義されていません。

于 2012-06-06T21:19:19.367 に答える
2

これは、Akavallによる上記の回答投稿の拡張です。その答えから、オブジェクトのパフォーマンスが速いのに対し、オブジェクトのnp.sumパフォーマンスは速いことがわかります。それを拡張するには:np.arraysumlist

オブジェクトnp.sumの実行時。オブジェクトの場合、彼らは首から首まで実行しているようです。np.array sumlist

# 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.sum14.1 µs14.3 µs

于 2017-04-07T15:35:09.880 に答える
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
于 2020-08-29T15:20:23.137 に答える