Quelles sont les différences de performances et de comportement entre l'utilisation de la fonction native sum
de Python et _ numpy.sum
? sum
fonctionne sur les tableaux de NumPy et numpy.sum
fonctionne sur Python et elles renvoient toutes les deux le même résultat effectif (n'ont pas testé les cas Edge tels que le débordement) mais des types différents.
>>> 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'>
Edit: Je pense que ma question pratique ici est d'utiliser numpy.sum
sur une liste de Python sont plus rapides que d'utiliser le propre sum
de Python?
De plus, quelles sont les implications (y compris les performances) de l'utilisation d'un Python entier contre un scalaire numpy.int32
? Par exemple, pour a += 1
, existe-t-il une différence de comportement ou de performances si le type de a
est un Python entier ou un numpy.int32
? Je suis curieux de savoir s'il est plus rapide d'utiliser un type de données scalaire NumPy tel que numpy.int32
pour une valeur ajoutée ou soustraite beaucoup dans le code Python.
Pour plus de clarté, je travaille sur une simulation bioinformatique qui consiste en partie à regrouper des numpy.ndarray
s en sommes scalaires uniques qui sont ensuite traitées en plus. J'utilise Python 3.2 et NumPy 1.6.
Merci d'avance!
Je suis devenu curieux et l'ai chronométré. numpy.sum
Semble beaucoup plus rapide pour les tableaux numpy, mais beaucoup plus lent sur les listes.
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
Résultat lorsque x = range(1000)
:
Pure Python Sum: 0.445913167735
Numpy Sum: 8.54926219673
Résultat lorsque x = np.random.standard_normal(1000)
:
Pure Python Sum: 12.1442425643
Numpy Sum: 0.303303771848
J'utilise Python 2.7.2 et Numpy 1.6.1
[...] ma [...] question ici est d'utiliser
numpy.sum
sur une liste d'entiers Python soit plus rapide que d'utiliser le propresum
de Python ?
La réponse à cette question est: Non.
La somme de Pythons sera plus rapide sur les listes, tandis que la somme de NumPys sera plus rapide sur les tableaux. J'ai en fait fait un benchmark pour montrer les timings (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>)"}
)
Avec ces résultats:
f, (ax1, ax2) = plt.subplots(1, 2, sharey=True)
b_array.plot(ax=ax1)
b_list.plot(ax=ax2)
Gauche: sur un tableau NumPy; Droite: sur une liste Python. Notez qu'il s'agit d'un tracé log-log car le benchmark couvre une très large gamme de valeurs. Cependant pour des résultats qualitatifs: plus bas signifie mieux.
Ce qui montre que pour les listes Pythons sum
est toujours plus rapide tandis que np.sum
Ou la méthode sum
sur le tableau sera plus rapide (sauf pour les tableaux très courts où Pythons sum
est plus rapide).
Juste au cas où vous souhaiteriez les comparer les uns aux autres, j'ai également fait un complot comprenant tous:
f, ax = plt.subplots(1)
b_array.plot(ax=ax)
b_list.plot(ax=ax)
ax.grid(which='both')
Fait intéressant, le point auquel numpy
peut rivaliser sur les tableaux avec Python et les listes est d'environ 200 éléments! Notez que ce nombre peut dépendre de nombreux facteurs, tels que Python/Version NumPy, ... Ne le prenez pas trop à la lettre.
Ce qui n'a pas été mentionné est la raison de cette différence (je veux dire la différence à grande échelle et non la différence pour les listes courtes/tableaux où les fonctions ont simplement des frais généraux constants différents). En supposant que CPython a Python list est un wrapper autour d'un tableau C (le langage C) de pointeurs vers Python objets (dans ce cas Python entiers). Ces entiers peuvent être vus comme des enveloppes autour d'un entier C (pas vraiment correct parce que Python peuvent être arbitrairement grands donc il ne peut pas simplement utiliser un entier C mais il est assez proche).
Par exemple, une liste comme [1, 2, 3]
Serait (schématiquement, j'ai omis quelques détails) stockée comme ceci:
Un tableau NumPy est cependant un wrapper autour d'un tableau C contenant des valeurs C (dans ce cas int
ou long
selon 32 ou 64 bits et selon le système d'exploitation).
Ainsi, un tableau NumPy comme np.array([1, 2, 3])
ressemblerait à ceci:
La prochaine chose à comprendre est de savoir comment ces fonctions fonctionnent:
sum
itère sur l'itérable (dans ce cas la liste ou le tableau) et ajoute tous les éléments.sum
parcourt le tableau C stocké et ajoute ces valeurs C et enveloppe finalement cette valeur dans un Python tapez (dans ce cas numpy.int32
(ou numpy.int64
) et le retourne.sum
convertit l'entrée en array
(au moins si ce n'est pas déjà un tableau) et utilise ensuite la méthode NumPy sum
.Il est clair que l'ajout de valeurs C à partir d'un tableau C est beaucoup plus rapide que l'ajout d'objets Python, c'est pourquoi les fonctions NumPy peuvent être beaucoup plus rapide (voir le deuxième tracé ci-dessus, les fonctions NumPy sur les tableaux battent de loin la somme Python pour les tableaux de grande taille).
Mais la conversion d'une liste Python en tableau NumPy est relativement lente et vous devez ensuite ajouter les valeurs C. C'est pourquoi pour les listes le Python sum
sera plus rapide.
La seule question en suspens est de savoir pourquoi Pythons sum
sur un array
est si lent (c'est la plus lente de toutes les fonctions comparées). Et cela a en fait à voir avec le fait que la somme de Pythons itère simplement sur tout ce que vous passez. Dans le cas d'une liste, il obtient l'objet Python stocké mais dans le cas d'un tableau 1D NumPy, il n'y a pas d'objets Python stockés, juste des valeurs C, donc Python & NumPy doivent créer un objet Python (un numpy.int32
ou numpy.int64
) pour chaque élément, puis ces objets Python doivent être ajoutés. La création du wrapper pour la valeur C est ce qui le rend vraiment lent.
De plus, quelles sont les implications (y compris les performances) de l'utilisation d'un Python entier par rapport à un scalaire numpy.int32? Par exemple, pour un + = 1, y a-t-il une différence de comportement ou de performances si le type d'un est un Python entier ou un numpy.int32?
J'ai fait quelques tests et pour l'addition et la soustraction de scalaires, vous devez absolument vous en tenir aux entiers Python. Même s'il peut y avoir une mise en cache, ce qui signifie que les tests suivants peuvent ne pas être totalement représentatifs:
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)
Il est 3-6 fois plus rapide de faire des opérations scalaires avec Python entiers qu'avec les scalaires NumPy. Je n'ai pas vérifié pourquoi c'est le cas mais je suppose que les scalaires NumPy sont rarement utilisés et probablement pas optimisés pour la performance.
La différence devient un peu moindre si vous effectuez réellement des opérations arithmétiques où les deux opérandes sont des scalaires 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)
Ensuite, c'est seulement 2 fois plus lent.
Au cas où vous vous demanderiez pourquoi j'ai utilisé itertools.repeat
Ici alors que j'aurais simplement pu utiliser for _ in range(...)
à la place. La raison en est que repeat
est plus rapide et entraîne donc moins de surcharge par boucle. Parce que je ne suis intéressé que par le temps d'addition/soustraction, il est en fait préférable de ne pas avoir le temps de boucle en boucle avec les timings (du moins pas tant que ça).
Numpy devrait être beaucoup plus rapide, surtout lorsque vos données sont déjà un tableau numpy.
Les tableaux Numpy sont une couche mince sur un tableau C standard. Lorsque numpy sum itère sur cela, il ne fait pas de vérification de type et il est très rapide. La vitesse doit être comparable à celle de l'opération en utilisant la norme C.
En comparaison, en utilisant la somme de python, il doit d'abord convertir le tableau numpy en un tableau python, puis itérer sur ce tableau. Il doit faire une vérification de type et va généralement être plus lent.
Le montant exact que python sum est plus lent que numpy sum n'est pas bien défini car la python sum va être une fonction quelque peu optimisée par rapport à l'écriture de votre propre fonction de somme en python.
Notez que Python sum sur les tableaux numpy multidimensionnels effectuera uniquement une somme le long du premier axe:
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
Ceci est une extension du réponse post ci-dessus par Akavall . De cette réponse, vous pouvez voir que np.sum
fonctionne plus rapidement pour np.array
objets, tandis que sum
est plus rapide pour les objets list
. Pour développer cela:
En cours d'exécution np.sum
pour un np.array
objet Vs. sum
pour un objet list
, il semble qu'ils fonctionnent au coude à coude.
# 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
Ci-dessus, sum
est minuscule un peu plus rapide que np.array
, même si j'ai parfois vu np.sum
les horaires doivent être 14.1 µs
, aussi. Mais surtout, c'est 14.3 µs
.