web-dev-qa-db-fra.com

Somme Python vs NumPy numpy.sum

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.ndarrays en sommes scalaires uniques qui sont ensuite traitées en plus. J'utilise Python 3.2 et NumPy 1.6.

Merci d'avance!

44
dpyro

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

55
Akavall

[...] ma [...] question ici est d'utiliser numpy.sum sur une liste d'entiers Python soit plus rapide que d'utiliser le propre sum 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)

enter image description here

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')

enter image description here

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:

enter image description here

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:

enter image description here

La prochaine chose à comprendre est de savoir comment ces fonctions fonctionnent:

  • Pythons sum itère sur l'itérable (dans ce cas la liste ou le tableau) et ajoute tous les éléments.
  • La méthode NumPys 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.
  • NumPys 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).

14
MSeifert

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.

5
Claris

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
3
MichaelSB

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.

1
xyres