Je voudrais écrire un programme qui utilise largement les fonctionnalités d'algèbre linéaire BLAS et LAPACK. Étant donné que la performance est un problème, j'ai fait quelques analyses comparatives et j'aimerais savoir si l'approche que j'ai adoptée est légitime.
J'ai, pour ainsi dire, trois candidats et je veux tester leurs performances avec une simple multiplication matrice-matrice. Les candidats sont:
dot
.J'ai implémenté une multiplication matrice-matrice pour différentes dimensions i
. i
va de 5 à 500 avec un incrément de 5 et les matricies m1
et m2
sont configurés comme ceci:
m1 = numpy.random.Rand(i,i).astype(numpy.float32)
m2 = numpy.random.Rand(i,i).astype(numpy.float32)
Le code utilisé ressemble à ceci:
tNumpy = timeit.Timer("numpy.dot(m1, m2)", "import numpy; from __main__ import m1, m2")
rNumpy.append((i, tNumpy.repeat(20, 1)))
Avec la fonction
_blaslib = ctypes.cdll.LoadLibrary("libblas.so")
def Mul(m1, m2, i, r):
no_trans = c_char("n")
n = c_int(i)
one = c_float(1.0)
zero = c_float(0.0)
_blaslib.sgemm_(byref(no_trans), byref(no_trans), byref(n), byref(n), byref(n),
byref(one), m1.ctypes.data_as(ctypes.c_void_p), byref(n),
m2.ctypes.data_as(ctypes.c_void_p), byref(n), byref(zero),
r.ctypes.data_as(ctypes.c_void_p), byref(n))
le code de test ressemble à ceci:
r = numpy.zeros((i,i), numpy.float32)
tBlas = timeit.Timer("Mul(m1, m2, i, r)", "import numpy; from __main__ import i, m1, m2, r, Mul")
rBlas.append((i, tBlas.repeat(20, 1)))
Maintenant, le code c ++ est naturellement un peu plus long, donc je réduis les informations au minimum.
Je charge la fonction avec
void* handle = dlopen("libblas.so", RTLD_LAZY);
void* Func = dlsym(handle, "sgemm_");
Je mesure le temps avec gettimeofday
comme ceci:
gettimeofday(&start, NULL);
f(&no_trans, &no_trans, &dim, &dim, &dim, &one, A, &dim, B, &dim, &zero, Return, &dim);
gettimeofday(&end, NULL);
dTimes[j] = CalcTime(start, end);
où j
est une boucle exécutée 20 fois. Je calcule le temps passé avec
double CalcTime(timeval start, timeval end)
{
double factor = 1000000;
return (((double)end.tv_sec) * factor + ((double)end.tv_usec) - (((double)start.tv_sec) * factor + ((double)start.tv_usec))) / factor;
}
Le résultat est illustré dans le graphique ci-dessous:
Le benchmark complet peut être téléchargé ici . (J.F. Sebastian a rendu ce lien possible ^^)
J'ai exécuté votre référence . Il n'y a aucune différence entre C++ et numpy sur ma machine:
Pensez-vous que mon approche est juste ou y a-t-il des frais généraux inutiles que je peux éviter?
Cela semble juste car il n'y a pas de différence dans les résultats.
Vous attendriez-vous à ce que le résultat montre une énorme différence entre l'approche c ++ et python? Les deux utilisent des objets partagés pour leurs calculs.
Non.
Comme je préfère utiliser python pour mon programme, que puis-je faire pour augmenter les performances lors de l'appel des routines BLAS ou LAPACK?
Assurez-vous que numpy utilise une version optimisée des bibliothèques BLAS/LAPACK sur votre système.
Je relance la référence sur notre nouveau HPC. Le matériel ainsi que la pile de logiciels ont changé par rapport à la configuration de la réponse d'origine.
J'ai mis les résultats dans une feuille de calcul Google (contient également les résultats de la réponse d'origine).
Notre HPC possède deux nœuds différents, l'un avec les processeurs Intel Sandy Bridge et l'autre avec les nouveaux processeurs Ivy Bridge:
Sandy (MKL, OpenBLAS, ATLAS):
Lierre (MKL, OpenBLAS, ATLAS):
La pile logicielle est pour les deux nœuds le sam. Au lieu de GotoBLAS2 , OpenBLAS est utilisé et il y a aussi un ATLAS multithread BLAS défini sur 8 threads (codé en dur).
Le code de référence est le même que ci-dessous. Cependant, pour les nouvelles machines, j'ai également utilisé la référence pour les tailles de matrice 5000 et 8000 .
Le tableau ci-dessous inclut les résultats de référence de la réponse d'origine (renommé: MKL -> Nehalem MKL, Netlib Blas -> Nehalem Netlib BLAS, etc.)
Performances à un seul thread:
Performance multithread (8 threads):
Threads vs Matrix size (Ivy Bridge MKL) :
Performances à un seul thread:
Performances multi-thread (8 threads):
Les nouveaux résultats de référence sont similaires à ceux de la réponse d'origine. OpenBLAS et [~ # ~] mkl [~ # ~] effectuer au même niveau, à l'exception du test Eigenvalue . Le test Eigenvalue ne fonctionne que raisonnablement bien sur OpenBLAS dans mode thread unique . En mode multi-thread, les performances sont moins bonnes.
Le "Tableau de la taille de la matrice par rapport aux threads" montre également que bien que MKL et OpenBLAS soient généralement bien adaptés au nombre de cœurs/threads, cela dépend de la taille de la matrice. Pour les petites matrices, l'ajout de cœurs supplémentaires n'améliorera pas beaucoup les performances.
Il y a également une augmentation des performances d'environ 30% de Sandy Bridge à Ivy Bridge ce qui pourrait être dû à une fréquence d'horloge plus élevée (+ 0,8 Ghz) et/ou à une meilleure architecture.
Il y a quelque temps, j'ai dû optimiser certains calculs/algorithmes d'algèbre linéaire qui étaient écrits en python en utilisant numpy et BLAS donc j'ai comparé/testé différentes configurations numpy/BLAS.
Plus précisément, j'ai testé:
J'ai exécuté deux benchmarks différents:
Voici mes résultats:
Linux (MKL, ATLAS, No-MKL, GotoBlas2):
Mac Book Pro (Accelerate Framework):
Serveur Mac (Accelerate Framework):
Code :
import numpy as np
a = np.random.random_sample((size,size))
b = np.random.random_sample((size,size))
%timeit np.dot(a,b)
Résultats :
Système | taille = 1000 | taille = 2000 | taille = 3000 | netlib BLAS | 1350 ms | 10900 ms | 39200 ms | ATLAS (1 CPU) | 314 ms | 2560 ms | 8700 ms | MKL (1 CPU) | 268 ms | 2110 ms | 7120 ms | MKL (2 CPU) | - | 3660 ms | MKL (8 CPU) | 39 ms | 319 ms | 1000 ms | GotoBlas2 (1 CPU) | 266 ms | 2100 ms | 7280 ms | GotoBlas2 (2 CPU) | 139 ms | 1009 ms | 3690 ms | GotoBlas2 (8 CPU) | 54 ms | 389 ms | 1250 ms | Mac OS X (1 CPU) | 143 ms | 1060 ms | 3605 ms | Mac Server (1 CPU) | 92 ms | 714 ms | 2130 ms |
Code :
Pour plus d'informations sur la suite de référence, voir --- (ici .
Résultats :
Système | valeurs propres | svd | det | inv | dot | netlib BLAS | 1688 ms | 13102 ms | 438 ms | 2155 ms | 3522 ms | ATLAS (1 CPU) | 1210 ms | 5897 ms | 170 ms | 560 ms | 893 ms | MKL (1 CPU) | 691 ms | 4475 ms | 141 ms | 450 ms | 736 ms | MKL (2 CPU) | 552 ms | 2718 ms | 96 ms | 267 ms | 423 ms | MKL (8 CPU) | 525 ms | 1679 ms | 60 ms | 137 ms | 197 ms | GotoBlas2 (1 CPU) | 2124 ms | 4636 ms | 147 ms | 456 ms | 743 ms | GotoBlas2 (2 CPU) | 1560 ms | 3278 ms | 116 ms | 295 ms | 460 ms | GotoBlas2 (8 CPU) | 741 ms | 2914 ms | 82 ms | 262 ms | 192 ms | Mac OS X (1 CPU) | 948 ms | 4339 ms | 151 ms | 318 ms | 566 ms | Mac Server (1 CPU) | 1033 ms | 3645 ms | 99 ms | 232 ms | 342 ms |
L'installation de [~ # ~] mkl [~ # ~] comprenait l'installation de la suite complète de compilateurs Intel, qui est assez simple. Cependant, en raison de certains bugs/problèmes, la configuration et la compilation de numpy avec le support MKL était un peu compliquée.
GotoBlas2 est un petit paquet qui peut être facilement compilé comme une bibliothèque partagée. Cependant, à cause d'un bug vous devez recréer la bibliothèque partagée après l'avoir construite afin de l'utiliser avec numpy.
En plus de ce bâtiment, il ne fonctionnait pas pour une plate-forme à cibles multiples. J'ai donc dû créer un fichier . So pour chaque plateforme pour laquelle je veux avoir une libgoto2.so optimisée fichier.
Si vous installez numpy depuis le référentiel d'Ubuntu, il installera et configurera automatiquement numpy pour utiliser [~ # ~] atlas [~ # ~] . L'installation de l'atlas [~ # ~] [~ # ~] à partir de la source peut prendre un certain temps et nécessite des étapes supplémentaires (fortran, etc.).
Si vous installez numpy sur une machine Mac OS X avec Fink ou Ports Mac , il configurera numpy pour utiliser [~ # ~] atlas [~ # ~] ou Accelerate Framework d'Apple . Vous pouvez vérifier en exécutant ldd sur le fichier numpy.core._dotblas ou en appelant numpy.show_config () .
[~ # ~] mkl [~ # ~] donne les meilleurs résultats, suivi de près GotoBlas2 .
Dans le test de valeurs propres , GotoBlas2 fonctionne étonnamment moins bien que prévu. Je ne sais pas pourquoi c'est le cas.
Accelerate Framework d'Apple fonctionne vraiment bien, en particulier en mode simple thread (par rapport aux autres implémentations BLAS).
GotoBlas2 et [~ # ~] mkl [~ # ~] échelle très bien avec le nombre de fils. Donc, si vous devez gérer de grandes matrices, le faire fonctionner sur plusieurs threads vous aidera beaucoup.
Dans tous les cas, n'utilisez pas l'implémentation par défaut de netlib blas car elle est beaucoup trop lente pour un travail de calcul sérieux.
Sur notre cluster, j'ai également installé ACML d'AMD et les performances étaient similaires à [~ # ~] mkl [~ # ~ ] et GotoBlas2 . Je n'ai pas de chiffres difficiles.
Personnellement, je recommanderais d'utiliser GotoBlas2 car il est plus facile à installer et gratuit.
Si vous souhaitez coder en C++/C, consultez également Eigen qui est censé surpasser MKL/GotoBlas2 dans certains - cases et est également assez facile à utiliser.
Voici une autre référence (sous Linux, tapez simplement make
): http://dl.dropbox.com/u/5453551/blas_call_benchmark.Zip
http://dl.dropbox.com/u/5453551/blas_call_benchmark.png
Je ne vois essentiellement aucune différence entre les différentes méthodes pour les grandes matrices, entre Numpy, Ctypes et Fortran. (Fortran au lieu de C++ --- et si cela importe, votre référence est probablement cassée.)
Votre fonction Peut-être que votre référence a également d'autres bogues, par exemple, la comparaison entre différentes bibliothèques BLAS, ou différents paramètres BLAS tels que le nombre de threads, ou entre le temps réel et le temps CPU?CalcTime
en C++ semble avoir une erreur de signe. ... + ((double)start.tv_usec))
devrait être à la place ... - ((double)start.tv_usec))
.
[~ # ~] modifier [~ # ~] : échec du comptage des accolades dans la fonction CalcTime
- c'est OK.
À titre indicatif: si vous faites un benchmark, veuillez toujours poster tous le code quelque part. Il n'est généralement pas productif de commenter les repères, en particulier lorsqu'ils sont surprenants, sans avoir le code complet.
Pour savoir à quel BLAS Numpy est lié, procédez comme suit:
$ python Python 2.7.2+ (par défaut, 16 août 2011, 07:24:41) [GCC 4.6.1] sur linux2 Tapez " aide "," copyright "," crédits "ou" licence "pour plus d'informations. >>> import numpy.core._dotblas >>> numpy.core._dotblas .__ fichier __ '/ usr/lib/pymodules/python2.7/numpy/core/_dotblas.so' >>> $ ldd /usr/lib/pymodules/python2.7/numpy/ core/_dotblas.so linux-vdso.so.1 => (0x00007fff5ebff000) libblas.so.3gf => /usr/lib/libblas.so.3gf (0x00007fbe618b3000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fbe61514000)
[~ # ~] mise à jour [~ # ~] : Si vous ne peut pas importer numpy.core._dotblas , votre Numpy utilise sa copie de secours interne de BLAS, qui est plus lente et n'est pas destinée à être utilisée dans le calcul de performance! La réponse de @Woltan ci-dessous indique que c'est l'explication de la différence qu'il/elle voit dans Numpy vs Ctypes + BLAS.
Pour corriger la situation, vous avez besoin d'ATLAS ou de MKL --- vérifiez ces instructions: http://scipy.org/Installing_SciPy/Linux La plupart des distributions Linux sont livrées avec ATLAS, donc la meilleure option est d'installer leur libatlas-dev
package (le nom peut varier).
Compte tenu de la rigueur dont vous avez fait preuve avec votre analyse, je suis surpris des résultats obtenus jusqu'à présent. Je mets cela comme une "réponse", mais seulement parce qu'il est trop long pour un commentaire et offre une possibilité (même si je suppose que vous l'avez envisagée).
J'aurais pensé que l'approche numpy/python n'ajouterait pas beaucoup de frais généraux pour une matrice de complexité raisonnable, car à mesure que la complexité augmente, la proportion à laquelle python participe) devrait être petite. I ' Je suis plus intéressé par les résultats sur le côté droit du graphique, mais la différence des ordres de grandeur montrée là serait inquiétante.
Je me demande si vous utilisez les meilleurs algorithmes que numpy peut exploiter. Du guide de compilation pour Linux:
"Build FFTW (3.1.2): Versions de SciPy> = 0.7 et Numpy> = 1.2: En raison de problèmes de licence, de configuration et de maintenance, la prise en charge de FFTW a été supprimée dans les versions de SciPy> = 0.7 et NumPy> = 1.2. une version intégrée de fftpack. Il existe deux façons de profiter de la vitesse de FFTW si nécessaire pour votre analyse. Rétrogradez vers une version Numpy/Scipy qui inclut la prise en charge. Installez ou créez votre propre wrapper de FFTW. Voir - http://developer.berlios.de/projects/pyfftw/ comme exemple non approuvé. "
Avez-vous compilé numpy avec mkl? ( http://software.intel.com/en-us/articles/intel-mkl/ ). Si vous utilisez Linux, les instructions pour compiler numpy avec mkl sont ici: http://www.scipy.org/Installing_SciPy/Linux#head-7ce43956a69ec51c6f2cedd894a4715d5bfff974 (malgré l'url). La partie clé est:
[mkl]
library_dirs = /opt/intel/composer_xe_2011_sp1.6.233/mkl/lib/intel64
include_dirs = /opt/intel/composer_xe_2011_sp1.6.233/mkl/include
mkl_libs = mkl_intel_lp64,mkl_intel_thread,mkl_core
Si vous êtes sous Windows, vous pouvez obtenir un binaire compilé avec mkl (et également obtenir pyfftw et de nombreux autres algorithmes associés) à: http://www.lfd.uci.edu/~gohlke/pythonlibs / , avec une gratitude envers Christoph Gohlke au Laboratory for Fluorescence Dynamics, UC Irvine.
Attention, dans les deux cas, il existe de nombreux problèmes de licence et ainsi de suite, mais la page Intel les explique. Encore une fois, j'imagine que vous avez considéré cela, mais si vous remplissez les conditions de licence (ce qui est très facile à faire sur Linux), cela accélérerait considérablement la partie numpy par rapport à l'utilisation d'une construction automatique simple, sans même FFTW. Je serai intéressé de suivre ce fil et de voir ce que les autres pensent. Quoi qu'il en soit, excellente rigueur et excellente question. Merci de l'avoir posté.