web-dev-qa-db-fra.com

Parallélisation d'une opération vectorielle Numpy

Utilisons, par exemple, numpy.sin()

Le code suivant renverra la valeur du sinus pour chaque valeur du tableau a:

import numpy
a = numpy.arange( 1000000 )
result = numpy.sin( a )

Mais ma machine a 32 cœurs, je voudrais donc les utiliser. (La surcharge peut ne pas valoir la peine pour quelque chose comme numpy.sin() mais la fonction que je veux réellement utiliser est un peu plus compliquée, et je vais travailler avec une énorme quantité de données.)

Est-ce la meilleure méthode (lire: la plus intelligente ou la plus rapide):

from multiprocessing import Pool
if __== '__main__':
    pool = Pool()
    result = pool.map( numpy.sin, a )

ou existe-t-il une meilleure façon de procéder?

43
user1475412

Il est une meilleure façon: numexpr

Légèrement reformulé à partir de leur page principale:

Il s'agit d'un VM écrit en C multi-threads qui analyse les expressions, les réécrit plus efficacement et les compile à la volée dans du code qui obtient des performances parallèles presque optimales pour les opérations liées à la mémoire et au processeur).

Par exemple, dans ma machine à 4 cœurs, l'évaluation d'un sinus est juste un peu moins de 4 fois plus rapide que numpy.

In [1]: import numpy as np
In [2]: import numexpr as ne
In [3]: a = np.arange(1000000)
In [4]: timeit ne.evaluate('sin(a)')
100 loops, best of 3: 15.6 ms per loop    
In [5]: timeit np.sin(a)
10 loops, best of 3: 54 ms per loop

Documentation, y compris les fonctions prises en charge ici . Vous devrez vérifier ou nous donner plus d'informations pour voir si votre fonction plus compliquée peut être évaluée par numexpr.

58
jorgeca

Eh bien, c'est une sorte de note intéressante si vous exécutez les commandes suivantes:

import numpy
from multiprocessing import Pool
a = numpy.arange(1000000)    
pool = Pool(processes = 5)
result = pool.map(numpy.sin, a)

UnpicklingError: NEWOBJ class argument has NULL tp_new

ne s'attendait pas à cela, alors qu'est-ce qui se passe, eh bien:

>>> help(numpy.sin)
   Help on ufunc object:

sin = class ufunc(__builtin__.object)
 |  Functions that operate element by element on whole arrays.
 |  
 |  To see the documentation for a specific ufunc, use np.info().  For
 |  example, np.info(np.sin).  Because ufuncs are written in C
 |  (for speed) and linked into Python with NumPy's ufunc facility,
 |  Python's help() function finds this page whenever help() is called
 |  on a ufunc.

yep numpy.sin est implémenté en c en tant que tel, vous ne pouvez pas vraiment l'utiliser directement avec le multitraitement.

nous devons donc l'envelopper avec une autre fonction

perf:

import time
import numpy
from multiprocessing import Pool

def numpy_sin(value):
    return numpy.sin(value)

a = numpy.arange(1000000)
pool = Pool(processes = 5)

start = time.time()
result = numpy.sin(a)
end = time.time()
print 'Singled threaded %f' % (end - start)
start = time.time()
result = pool.map(numpy_sin, a)
pool.close()
pool.join()
end = time.time()
print 'Multithreaded %f' % (end - start)


$ python perf.py 
Singled threaded 0.032201
Multithreaded 10.550432

wow, je ne m'attendais pas à ce que, bien qu'il y ait quelques problèmes pour les débutants, nous utilisons une fonction python même si ce n'est qu'un wrapper vs une fonction c pure, et il y a aussi la surcharge de en copiant les valeurs, le multitraitement par défaut ne partage pas les données, car chaque valeur doit être copiée d'avant en arrière.

notez que si vous segmentez correctement nos données:

import time
import numpy
from multiprocessing import Pool

def numpy_sin(value):
    return numpy.sin(value)

a = [numpy.arange(100000) for _ in xrange(10)]
pool = Pool(processes = 5)

start = time.time()
result = numpy.sin(a)
end = time.time()
print 'Singled threaded %f' % (end - start)
start = time.time()
result = pool.map(numpy_sin, a)
pool.close()
pool.join()
end = time.time()
print 'Multithreaded %f' % (end - start)

$ python perf.py 
Singled threaded 0.150192
Multithreaded 0.055083

Alors, que pouvons-nous en tirer, le multitraitement est génial, mais nous devons toujours le tester et le comparer parfois, c'est plus rapide et parfois plus lent, selon la façon dont il est utilisé ...

Certes, vous n'utilisez pas numpy.sin mais une autre fonction, je vous recommande tout d'abord de vérifier qu'en effet le multitraitement accélérera le calcul, peut-être que le surcoût de la copie des valeurs en avant/en arrière peut vous affecter.

De toute façon, je fais aussi croyez qu'en utilisant pool.map est la méthode de multithreading la plus sûre et la plus sûre ...

J'espère que ça aide.

20
Samy Vilar

SciPy a en fait un assez bon résumé sur ce sujet ici: http://wiki.scipy.org/ParallelProgramming

Edit: lien mort, peut maintenant être trouvé à: http://scipy-cookbook.readthedocs.io/items/ParallelProgramming.html

6
entropy