47 votes

numpy float: 10 fois plus lent que les opérations arithmétiques?

EDIT:

Je exécutez à nouveau le code sous Windows 7 64 bits (Intel Core i7 930 @ 3.8 GHz).

Encore une fois, le code est:

from datetime import datetime
import numpy as np

START_TIME = datetime.now()

# one of the following lines is uncommented before execution
#s = np.float64(1)
#s = np.float32(1)
#s = 1.0

for i in range(10000000):
    s = (s + 8) * s % 2399232

print(s)
print('Runtime:', datetime.now() - START_TIME)

Les horaires sont les suivants:

  • float64: 16,1 s
  • float32: 16,1 s
  • float: 3,2 s

Maintenant, les deux np flotteurs (64 ou 32) sont 5 fois plus lente que dans le haut- float. Encore, une différence significative. Je suis à essayer de comprendre d'où il vient.

EDIT:

Merci pour les réponses, elles m'aident à comprendre la façon de traiter ce problème.

Mais j'aimerais connaître la raison précise (basé sur le code source peut-être) pourquoi le code ci-dessous fonctionne 10 fois plus lent avec float64 qu'avec float.

EDIT:

numpy.float64 est 10 fois plus lent que flotter dans les calculs arithmétiques. C'est tellement mauvais que même la conversion de flotter et revenir avant que les calculs rend le programme tourne 3 fois plus vite. Pourquoi? Est-ce que je peux faire pour le réparer?

Je tiens à souligner que mes timings ne sont pas pour les raisons suivantes:

  • les appels de fonction
  • la conversion entre les numpy et python flotteur
  • la création d'objets

J'ai mis à jour mon code pour le rendre plus clair où se trouve le problème. Avec le nouveau code, il semblerait que je vois un dix fois plus de performances de l'utilisation de numpy types de données:

from datetime import datetime
import numpy as np

START_TIME = datetime.now()

# one of the following lines is uncommented before execution
#s = np.float64(1)
#s = np.float32(1)
#s = 1.0

for i in range(10000000):
    s = (s + 8) * s % 2399232

print(s)
print('Runtime:', datetime.now() - START_TIME)

Les horaires sont les suivants:

  • float64: 34.56 s
  • float32: 35.11 s
  • float: 3.53 s

Juste pour l'enfer de celui-ci, j'ai aussi essayé:

de datetime import datetime import numpy comme np

START_TIME = datetime.now()

s = np.float64(1)
for i in range(10000000):
    s = float(s)
    s = (s + 8) * s % 2399232
    s = np.float64(s)

print(s)
print('Runtime:', datetime.now() - START_TIME)

Le temps d'exécution est 13.28 s; c'est en fait 3 fois plus rapide pour convertir l' float64 de float et à l'arrière que de l'utiliser comme est. Encore, la conversion prend son péage, de sorte que globalement, c'est plus de 3 fois plus lent par rapport à la pure python float.

Ma machine est:

  • Intel Core 2 Duo T9300 (2.5 GHz)
  • Windows xp Professionnel (32 bits)
  • ActiveState Python 3.1.3.5
  • Numpy 1.5.1

FIN DE L'EDIT

QUESTION DE DÉPART:

Je suis vraiment bizarre les horaires pour le code suivant:

import numpy as np
s = 0
for i in range(10000000):
    s += np.float64(1) # replace with np.float32 and built-in float
  • built-in float: 4.9 s
  • float64: 10,5 s
  • float32: 45.0 s

Pourquoi est - float64 deux fois plus lent que l' float? Et pourquoi est - float32 5 fois plus lent que float64?

Est-il un moyen d'éviter la sanction de l'utilisation de np.float64, et ont numpy retour des fonctions intégrées en float au lieu de float64?

J'ai trouvé que l'utilisation d' numpy.float64 est beaucoup plus lent que Python flottant, et numpy.float32 est encore plus lente (même si je suis sur une machine 32 bits).

numpy.float32 sur ma machine 32 bits. Par conséquent, chaque fois que je utiliser diverses fonctions de numpy comme numpy.random.uniform,- je convertir le résultat en float32 (ainsi que d'autres opérations pourraient être effectuées à 32 bits de précision).

Est-il possible de définir une variable unique, quelque part dans le programme ou dans la ligne de commande, et de faire toutes les fonctions de numpy retour float32 au lieu de float64?

44voto

samplebias Points 19805

Disponible flotteurs sont alloués en morceaux

Le principal problème avec la comparaison de numpy scalaire allocations à l' float est Disponible toujours alloue de la mémoire pour float et int objets en blocs de taille N.

En interne, Disponible maintient une liste chaînée de blocs assez grand pour contenir N float objets. Lorsque vous appelez float(1) Disponible vérifie si il y a de l'espace disponible dans le bloc courant; si non, elle alloue un nouveau bloc. Une fois qu'il a de l'espace dans le bloc en cours il initialise simplement que l'espace et renvoie un pointeur vers elle.

Sur ma machine, chaque bloc peut contenir 41 float objets, donc il y a une surcharge pour la première float(1) appel, mais les 40 prochaines courir beaucoup plus vite que la mémoire est allouée et prêt.

Lent numpy.float32 vs numpy.float64

Il semble que numpy a 2 chemins qu'il peut prendre lors de la création d'un type scalaire: rapide et lent. Cela dépend si le type scalaire a un Python de base de la classe à laquelle il peut reporter pour l'argument de conversion.

Pour certaines raisons, numpy.float32 est codée en dur de prendre le rythme plus lent (défini par l' _WORK0 macro), tandis que d' numpy.float64 obtient une chance de prendre le chemin plus rapide (défini par l' _WORK1 macro). Notez que scalartypes.c.src est un modèle qui génère scalartypes.c au moment de la construction.

Vous pouvez visualiser ce dans Cachegrind. J'ai inclus des captures d'écran montrant combien plus les appels sont effectués à la construction d'un float32 vs float64:

float64 prend la voie rapide

float64 takes the fast path

float32 prend la voie lente

float32 takes the slow path

Mise à jour - le type prend le slow/fast path peut dépendre de ce que l'OS est en 32 bits vs 64 bits. Sur mon système de test, Ubuntu Lucid 64 bits, l' float64 type est 10 fois plus rapide que l' float32.

22voto

Rosh Oxymoron Points 6965

D'exploitation avec des objets Python dans une grosse boucle comme ça, si ils sont float, np.float32, est toujours lente. NumPy est rapide pour les opérations sur les vecteurs et les matrices, parce que toutes les opérations sont effectuées sur de grandes quantités de données par des parties de la bibliothèque écrite en C, et non pas par l'interpréteur Python. Code à exécuter dans l'interprète et/ou en utilisant des objets Python est toujours lent, et à l'aide de non-autochtones qui le rend encore plus lent. C'est à prévoir.

Si votre application est lente et vous avez besoin de l'optimiser, vous devriez essayer de convertir votre code à un vecteur solution qui utilise NumPy directement, et il est rapide, ou vous pouvez utiliser des outils tels que Cython pour créer une mise en œuvre rapide de la boucle en C.

10voto

riza Points 2645

C'est peut-être pour cette raison que vous devriez utiliser Numpy directement plutôt que d'utiliser des boucles.

 s1 = np.ones(10000000, dtype=np.float)
s2 = np.ones(10000000, dtype=np.float32)
s3 = np.ones(10000000, dtype=np.float64)

np.sum(s1) <-- 17.3 ms
np.sum(s2) <-- 15.8 ms
np.sum(s3) <-- 17.3 ms
 

9voto

Travis Oliphant Points 749

La réponse est assez simple: l'allocation de mémoire pourrait être de la partie, mais le plus gros problème est que les opérations arithmétiques pour numpy scalaires est fait en utilisant le "ufuncs" qui sont destinés à être rapide pour plusieurs centaines de valeurs et pas seulement 1. Il y a une surcharge dans le choix de la bonne fonction à appeler et de configuration de la boucle. Les frais généraux, qui est non-nécessaire pour les scalaires.

Il était plus facile d'avoir juste les scalaires être converti en 0-d des tableaux et ensuite transmis à l'correspondant numpy ufunc puis écrire une des méthodes de calcul pour chacun des nombreux différents types scalaires que NumPy prend en charge.

L'intention était que des versions optimisées de la scalaires math serait ajouté à la nature des objets en C. Cela pourrait encore se produire, mais cela ne s'est passé parce que personne n'a été assez motivé pour le faire. Peut-être parce que le travail est de convertir numpy scalaires Python scalaires qui n'ont optimisé l'arithmétique.

8voto

max Points 6673

Résumé

Si une expression arithmétique contient à la fois numpy et intégré dans les chiffres, Python arithmétique fonctionne plus lentement. En évitant cette conversion enlève la quasi-totalité de la dégradation des performances je l'ai signalé.

Détails

Notez que dans mon code d'origine:

s = np.float64(1)
for i in range(10000000):
  s = (s + 8) * s % 2399232

les types d' float et numpy.float64 sont mélangés dans la même expression. Peut-être que Python a convertir tous à un seul type?

s = np.float64(1)
for i in range(10000000):
  s = (s + np.float64(8)) * s % np.float64(2399232)

Si le moteur d'exécution est le même (plutôt que l'augmentation de la), cela donnerait à penser que c'est ce que Python était en effet sous le capot, en expliquant les performances de glisser.

En fait, le moteur d'exécution a baissé de 1,5 fois! Comment est-il possible? N'est-ce pas la pire chose que Python pourrait éventuellement avoir à faire était de ces deux conversions?

Je ne sais pas vraiment. Peut-être Python avait pour vérifier de façon dynamique ce qui doit être converti dans l', ce qui prend du temps, et dit ce que précise conversions à effectuer le rend plus rapide. Peut-être, certains tout à fait différentes mécanisme est utilisé pour l'arithmétique (qui ne fait pas intervenir le nombre de conversions à tous), et il arrive à être super lent sur les types incompatibles. La lecture de numpy code source peut aider, mais c'est au-delà de mes compétences.

De toute façon, maintenant, nous pouvons évidemment d'accélérer les choses plus en déplaçant les conversions en dehors de la boucle:

q = np.float64(8)
r = np.float64(2399232)
for i in range(10000000):
  s = (s + q) * s % r

Comme prévu, l'exécution est considérablement réduite: par un autre 2,3 fois.

Pour être juste, nous devons maintenant modifier l' float version légèrement, en déplaçant les constantes littérales de la boucle. Il en résulte une minuscule (10%) ralentissement.

La comptabilité pour tous ces changements, l' np.float64 version du code est maintenant seulement 30% plus lent que l'équivalent float version; le ridicule de 5 fois de gain de performance est en grande partie disparu.

Pourquoi avons-nous encore voir les 30% de retard? numpy.float64 numéros de prendre la même quantité d'espace que float, de sorte que de ne pas en être la raison. Peut-être la résolution des opérateurs arithmétiques prend plus de temps pour les types définis par l'utilisateur. Certainement pas une préoccupation majeure.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X