web-dev-qa-db-fra.com

Comparaison de vitesse. numpy vs python standard

J'ai fait quelques expériences et trouvé un certain nombre de cas où la bibliothèque standard de python random et math est plus rapide que son homologue numpy.

Je pense que la bibliothèque standard de python a tendance à être 10 fois plus rapide pour les opérations à petite échelle, tandis que numpy est beaucoup plus rapide pour les opérations à grande échelle (vectorielles). Je suppose que numpy a des frais généraux qui deviennent dominants pour les petits cas.

Ma question est: mon intuition est-elle correcte? Et sera-t-il généralement conseillé d'utiliser la bibliothèque standard plutôt que numpy pour de petites opérations (typiquement scalaires)?

Voici des exemples.

import math
import random
import numpy as np

Journal et exponentielle

%timeit math.log(10)
# 158 ns ± 6.16 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

%timeit np.log(10)
# 1.64 µs ± 93.2 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

%timeit math.exp(3)
# 146 ns ± 8.57 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

%timeit np.exp(3)
# 1.72 µs ± 78.5 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

Générer une distribution normale

%timeit random.gauss(0, 1)
# 809 ns ± 12.8 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

%timeit np.random.normal()
# 2.57 µs ± 14.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

Choisir un élément aléatoire

%timeit random.choices([1,2,3], k=1)
# 1.56 µs ± 55.9 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

%timeit np.random.choice([1,2,3], size=1)
# 23.1 µs ± 1.04 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

idem avec tableau numpy

arr = np.array([1,2,3])

%timeit random.choices(arr, k=1)
# 1.72 µs ± 33.2 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

%timeit np.random.choice(arr, size=1)
# 18.4 µs ± 502 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

Avec un grand tablea

arr = np.arange(10000)

%timeit random.choices(arr, k=1000)
# 401 µs ± 6.16 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

%timeit np.random.choice(arr, size=1000)
# 41.7 µs ± 1.39 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
14
Kota Mori

numpy n'est vraiment qu'une amélioration des performances pour de gros blocs de données. Le surcoût de s'assurer que les blocs de mémoire s'alignent correctement avant de verser un ndarray dans une fonction numpy compilée en C dépassera généralement tout avantage de temps si le tableau n'est pas relativement grand. C'est pourquoi tant de questions numpy sont essentiellement "Comment puis-je prendre ce code en boucle et le rendre rapide", et pourquoi il est considéré comme une question valide dans cette balise où presque toutes les autres balises vous lanceront - Revue de code avant de dépasser le titre.

Donc, oui, votre observation est généralisable. La vectorisation est tout l'intérêt de numpy. Le code numpy qui n'est pas vectorisé est toujours plus lent que le code python nu, et est sans doute aussi "faux" que de casser un seul noyer avec un marteau-piqueur. Soit trouver le bon outil ou obtenir plus de noix.

6
Daniel F

NumPy est principalement utilisé pour les performances avec les tableaux . Cela repose sur l'utilisation de blocs de mémoire contigus et une itération de niveau inférieur plus efficace. L'application d'une fonction mathématique NumPy sur un scalaire ou le calcul d'un nombre aléatoire ne sont pas des opérations vectorisables. Cela explique le comportement que vous voyez.

Voir aussi Quels sont les avantages de NumPy par rapport aux listes régulières Python?

Et sera-t-il généralement conseillé d'utiliser la bibliothèque standard plutôt que NumPy pour les petites opérations (généralement scalaires)?

Il est rare que le goulot d'étranglement d'un programme soit provoqué par des opérations sur des scalaires. En pratique, les différences sont négligeables. Donc, dans les deux cas, ça va. Si vous utilisez déjà NumPy, il n'y a aucun mal à continuer d'utiliser les opérations NumPy sur les scalaires.

Cela vaut la peine de faire un cas spécial de calcul de nombres aléatoires. Comme vous pouvez vous y attendre, le nombre aléatoire sélectionné via random vs NumPy peut ne pas être le même:

assert random.gauss(0, 1) == np.random.normal()  # AssertionError
assert random.choices(arr, k=1)[0] == np.random.choice(arr, size=1)[0]  # AssertionError

Vous avez des fonctionnalités supplémentaires dans NumPy pour rendre les nombres aléatoires "prévisibles". Par exemple, l'exécution répétée du script ci-dessous ne générera que le même résultat:

np.random.seed(0)
np.random.normal()

De même pour np.random.choice. Il existe donc des différences dans comment le nombre aléatoire est dérivé et la fonctionnalité disponible. À des fins de test ou autres, vous souhaiterez peut-être pouvoir produire des nombres "aléatoires" cohérents.

4
jpp