Benchmarking (python vs. c ++ utilizzando BLAS) e (numpy)


107

Vorrei scrivere un programma che faccia un uso estensivo delle funzionalità di algebra lineare BLAS e LAPACK. Poiché le prestazioni sono un problema, ho eseguito alcuni benchmark e vorrei sapere se l'approccio che ho adottato è legittimo.

Ho, per così dire, tre concorrenti e voglio testare le loro prestazioni con una semplice moltiplicazione matrice-matrice. I concorrenti sono:

  1. Numpy, facendo uso solo della funzionalità di dot.
  2. Python, chiamando le funzionalità BLAS tramite un oggetto condiviso.
  3. C ++, chiamando le funzionalità BLAS tramite un oggetto condiviso.

Scenario

Ho implementato una moltiplicazione matrice-matrice per diverse dimensioni i. iva da 5 a 500 con un incremento di 5 e le matrici m1e m2si configurano così:

m1 = numpy.random.rand(i,i).astype(numpy.float32)
m2 = numpy.random.rand(i,i).astype(numpy.float32)

1. Numpy

Il codice utilizzato è simile a questo:

tNumpy = timeit.Timer("numpy.dot(m1, m2)", "import numpy; from __main__ import m1, m2")
rNumpy.append((i, tNumpy.repeat(20, 1)))

2. Python, chiamando BLAS tramite un oggetto condiviso

Con la funzione

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

il codice di prova ha questo aspetto:

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

3. c ++, chiamando BLAS tramite un oggetto condiviso

Ora il codice c ++ è naturalmente un po 'più lungo, quindi riduco le informazioni al minimo.
Carico la funzione con

void* handle = dlopen("libblas.so", RTLD_LAZY);
void* Func = dlsym(handle, "sgemm_");

Misuro il tempo in gettimeofdayquesto modo:

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

dove jè un ciclo in esecuzione 20 volte. Calcolo il tempo trascorso con

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;
}

risultati

Il risultato è mostrato nella trama sottostante:

inserisci qui la descrizione dell'immagine

Domande

  1. Pensi che il mio approccio sia giusto o ci sono delle spese generali non necessarie che posso evitare?
  2. Ti aspetteresti che il risultato mostrasse un'enorme discrepanza tra l'approccio c ++ e quello python? Entrambi utilizzano oggetti condivisi per i loro calcoli.
  3. Dato che preferirei usare python per il mio programma, cosa potrei fare per aumentare le prestazioni quando chiamo routine BLAS o LAPACK?

Scarica

Il benchmark completo può essere scaricato qui . (JF Sebastian ha reso possibile quel collegamento ^^)


nel tuo approccio ctypes, hai l'allocazione della memoria all'interno della funzione misurata. Il tuo codice c ++ segue questo approccio? Ma rispetto alla moltiplicazione di matrici questo non dovrebbe fare una grande differenza ...
rocksportrocker,

@rocksportrocker Hai ragione. L'allocazione della memoria per la rmatrice è ingiusta. Sto risolvendo il "problema" in questo momento e posto i nuovi risultati.
Woltan

1.Assicurati che gli array abbiano lo stesso layout di memoria np.ascontiguousarray()(considera l'ordine C rispetto a Fortran). 2. assicurati che np.dot()usi lo stesso libblas.so.
jfs

@JFSebastian Entrambi gli array m1e m2hanno il ascontiguousarrayflag come True. E numpy usa lo stesso oggetto condiviso di C. Per quanto riguarda l'ordine dell'array: Attualmente non sono interessato al risultato del calcolo, quindi l'ordine è irrilevante.
Woltan

1
@ Woltan: non utilizzare filefactory, il servizio è orribile. Ho aggiunto il tuo benchmark a github: woltan-benchmark . Se usi github potrei aggiungerti come collaboratore.
jfs

Risposte:


58

Ho eseguito il tuo benchmark . Non c'è differenza tra C ++ e numpy sulla mia macchina:

il punto di riferimento di Woltan

Pensi che il mio approccio sia giusto o ci sono delle spese generali non necessarie che posso evitare?

Sembra giusto perché non c'è differenza nei risultati.

Ti aspetteresti che il risultato mostrasse un'enorme discrepanza tra l'approccio c ++ e quello python? Entrambi utilizzano oggetti condivisi per i loro calcoli.

No.

Dato che preferirei usare python per il mio programma, cosa potrei fare per aumentare le prestazioni quando chiamo routine BLAS o LAPACK?

Assicurati che numpy utilizzi la versione ottimizzata delle librerie BLAS / LAPACK sul tuo sistema.


4
Quindi cosa ha sbagliato il poster originale? Vorrei che avesse commentato questo post. Conferma che Numpy è veloce quanto C ++?
wmac

Il tuo codice C ++ è più lento dei poster originali. Hai compilato in ottimizzazione?
cdcdcd

@cdcdcd non è il mio codice. Fare clic sul collegamento ed eseguire personalmente il benchmark con diverse opzioni di ottimizzazione (vedere il Makefile). Sebbene il codice non ricompili né blas né lapack.
jfs

73

AGGIORNAMENTO (30.07.2014):

Ho rieseguito il benchmark sul nostro nuovo HPC. Sia l'hardware che lo stack software sono cambiati rispetto alla configurazione nella risposta originale.

Metto i risultati in un foglio di calcolo di Google (contiene anche i risultati della risposta originale).

Hardware

Il nostro HPC ha due diversi nodi uno con CPU Intel Sandy Bridge e uno con le nuove CPU Ivy Bridge:

Sandy (MKL, OpenBLAS, ATLAS):

  • CPU : 2 x 16 Intel (R) Xeon (R) E2560 Sandy Bridge a 2,00 GHz (16 core)
  • RAM : 64 GB

Ivy (MKL, OpenBLAS, ATLAS):

  • CPU : 2 x 20 Intel (R) Xeon (R) E2680 V2 Ivy Bridge a 2,80 GHz (20 core, con HT = 40 core)
  • RAM : 256 GB

Software

Lo stack software è per entrambi i nodi il sam. Invece di GotoBLAS2 , viene utilizzato OpenBLAS e c'è anche un ATLAS BLAS multi-thread impostato su 8 thread (hardcoded).

  • OS : Suse
  • Compilatore Intel : ictce-5.3.0
  • Numpy: 1.8.0
  • OpenBLAS: 0.2.6
  • ATLAS :: 3.8.4

Punto di riferimento del prodotto

Il codice di benchmark è lo stesso di seguito. Tuttavia per le nuove macchine ho eseguito anche il benchmark per le dimensioni della matrice 5000 e 8000 .
La tabella seguente include i risultati del benchmark dalla risposta originale (rinominata: MKL -> Nehalem MKL, Netlib Blas -> Nehalem Netlib BLAS, ecc.)

Moltiplicazione di matrici (dimensioni = [1000,2000,3000,5000,8000])

Prestazioni a thread singolo: prestazioni a thread singolo

Prestazioni multi thread (8 thread): prestazioni multi-thread (8 thread)

Discussioni vs dimensione della matrice (Ivy Bridge MKL) : Dimensione matrice vs thread

Benchmark Suite

suite di benchmark

Prestazioni a thread singolo: inserisci qui la descrizione dell'immagine

Prestazioni multi thread (8 thread): inserisci qui la descrizione dell'immagine

Conclusione

I risultati del nuovo benchmark sono simili a quelli della risposta originale. OpenBLAS e MKL si comportano allo stesso livello, ad eccezione del test Eigenvalue . Gli autovalori esegue test only ragionevolmente pure sulle OpenBLAS in modo filettato singolo . In modalità multi-thread le prestazioni sono peggiori.

La "tabella delle dimensioni della matrice e dei fili" mostra anche che sebbene MKL e OpenBLAS generalmente si adattino bene con il numero di core / thread, dipende dalla dimensione della matrice. Per matrici piccole l'aggiunta di più core non migliorerà molto le prestazioni.

C'è anche un aumento delle prestazioni di circa il 30% da Sandy Bridge a Ivy Bridge, che potrebbe essere dovuto a una frequenza di clock più elevata (+ 0,8 Ghz) e / o a una migliore architettura.


Risposta originale (04.10.2011):

Qualche tempo fa ho dovuto ottimizzare alcuni calcoli / algoritmi di algebra lineare scritti in python usando numpy e BLAS, quindi ho confrontato / testato diverse configurazioni numpy / BLAS.

Nello specifico ho testato:

  • Numpy con ATLAS
  • Numpy con GotoBlas2 (1.13)
  • Numpy con MKL (11.1 / 073)
  • Numpy con Accelerate Framework (Mac OS X)

Ho eseguito due diversi benchmark:

  1. semplice prodotto scalare di matrici di diverse dimensioni
  2. Suite di benchmark che può essere trovata qui .

Ecco i miei risultati:

macchine

Linux (MKL, ATLAS, No-MKL, GotoBlas2):

  • Sistema operativo : Ubuntu Lucid 10.4 64 bit.
  • CPU : 2 x 4 Intel (R) Xeon (R) E5504 a 2,00 GHz (8 core)
  • RAM : 24 GB
  • Compilatore Intel : 11.1 / 073
  • Scipy : 0,8
  • Numpy : 1.5

Mac Book Pro (Accelerate Framework):

  • Sistema operativo : Mac OS X Snow Leopard (10.6)
  • PROCESSORE : 1 Intel Core 2 Duo 2.93 Ghz (2 core)
  • RAM : 4 GB
  • Scipy : 0.7
  • Numpy : 1.3

Mac Server (Accelerate Framework):

  • Sistema operativo : Mac OS X Snow Leopard Server (10.6)
  • CPU : 4 X Intel (R) Xeon (R) E5520 @ 2.26 Ghz (8 core)
  • RAM : 4 GB
  • Scipy : 0,8
  • Numpy : 1.5.1

Punto di riferimento del prodotto Dot

Codice :

import numpy as np
a = np.random.random_sample((size,size))
b = np.random.random_sample((size,size))
%timeit np.dot(a,b)

Risultati :

    Sistema | dimensione = 1000 | dimensione = 2000 | dimensione = 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 |

Punto di riferimento del prodotto Dot - grafico

Benchmark Suite

Codice :
per ulteriori informazioni sulla suite di benchmark, vedere qui .

Risultati :

    Sistema | autovalori | svd | det | inv | punto |
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 |

Benchmark suite - grafico

Installazione

L'installazione di MKL includeva l'installazione dell'intera Intel Compiler Suite, che è piuttosto semplice. Tuttavia, a causa di alcuni bug / problemi, la configurazione e la compilazione di numpy con il supporto MKL è stata un po 'una seccatura.

GotoBlas2 è un piccolo pacchetto che può essere facilmente compilato come libreria condivisa. Tuttavia, a causa di un bug, devi ricreare la libreria condivisa dopo averla compilata per poterla utilizzare con numpy.
Oltre a questa costruzione per piattaforme multiple di destinazione non ha funzionato per qualche motivo. Quindi ho dovuto creare un file .so per ogni piattaforma per la quale voglio avere un file libgoto2.so ottimizzato .

Se installi numpy dal repository di Ubuntu, installerà e configurerà automaticamente numpy per usare ATLAS . L'installazione di ATLAS dai sorgenti può richiedere del tempo e alcuni passaggi aggiuntivi (fortran, ecc.).

Se installi numpy su una macchina Mac OS X con Fink o porte Mac , configurerà numpy per utilizzare ATLAS o Accelerate Framework di Apple . Puoi controllare eseguendo ldd sul file numpy.core._dotblas o chiamando numpy.show_config () .

conclusioni

MKL si comporta al meglio seguito da vicino da GotoBlas2 .
Nel test agli autovalori GotoBlas2 si comporta sorprendentemente peggio del previsto. Non sono sicuro del motivo per cui questo è il caso.
Accelerate Framework di Apple funziona davvero bene soprattutto in modalità single threaded (rispetto alle altre implementazioni BLAS).

Sia GotoBlas2 che MKL si adattano molto bene con il numero di thread. Quindi, se hai a che fare con matrici grandi, eseguirlo su più thread ti aiuterà molto.

In ogni caso, non utilizzare l' implementazione predefinita di netlib blas perché è troppo lenta per qualsiasi lavoro di calcolo serio.

Sul nostro cluster ho installato anche ACML di AMD e le prestazioni erano simili a MKL e GotoBlas2 . Non ho numeri difficili.

Personalmente consiglierei di utilizzare GotoBlas2 perché è più facile da installare ed è gratuito.

Se vuoi programmare in C ++ / C controlla anche Eigen3 che dovrebbe superare MKL / GotoBlas2 in alcuni casi ed è anche abbastanza facile da usare.


Grazie mille per questa risposta elaborata!
Woltan

Molto completo, grazie! Mi chiedo, tre anni dopo, se OpenBLAS (per quanto ne so, è un discendente di GotoBLAS) avrebbe funzionato meglio. Ho letto da qualche parte che supera MKL, ma al momento non riesco a trovare la fonte.

Grazie! Questa è la mia impressione su0 (mi chiedevo se questa fosse solo la mia installazione): OpenBLAS non funziona così bene in modalità multi-thread quando si tratta di matrici diagonalizzanti (diagonalizzo in scipy, che è collegato a OpenBLAS).

@William: di solito non è necessario collegare specificamente scipy a openblas perché utilizzerà la configurazione numpy durante l'installazione e in realtà la maggior parte delle chiamate BLAS / Lapack verrà comunque inoltrata a numpy. Quindi, se numpy è correttamente collegato a openblas, tutto dovrebbe funzionare bene.
Ümit il

@ Ümit: Grazie! Sto provando a configurare numpy per collegarmi a MKL ora.

20

Ecco un altro benchmark (su Linux, basta digitare make): http://dl.dropbox.com/u/5453551/blas_call_benchmark.zip

http://dl.dropbox.com/u/5453551/blas_call_benchmark.png

Non vedo essenzialmente alcuna differenza tra i diversi metodi per matrici di grandi dimensioni, tra Numpy, Ctypes e Fortran. (Fortran invece di C ++ --- e se questo è importante, il tuo benchmark probabilmente non funziona.)

La tua CalcTimefunzione in C ++ sembra avere un errore di segno. ... + ((double)start.tv_usec))dovrebbe essere invece ... - ((double)start.tv_usec)). Forse il tuo benchmark ha anche altri bug, ad esempio il confronto tra diverse librerie BLAS o diverse impostazioni BLAS come il numero di thread o tra tempo reale e tempo CPU?

EDIT : non è riuscito a contare le parentesi graffe nella CalcTimefunzione - va bene.

Come linea guida: se fai un benchmark, per favore pubblica sempre tutto il codice da qualche parte. Commentare i benchmark, soprattutto quando sorprende, senza avere il codice completo di solito non è produttivo.


Per scoprire a quale BLAS Numpy è collegato, fai:

$ python
Python 2.7.2+ (predefinito, 16 agosto 2011, 07:24:41) 
[GCC 4.6.1] su linux2
Digita "aiuto", "copyright", "crediti" o "licenza" per ulteriori informazioni.
>>> importa numpy.core._dotblas
>>> numpy.core._dotblas .__ file__
'/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)

AGGIORNAMENTO : Se non puoi importare numpy.core._dotblas, il tuo Numpy sta usando la sua copia di fallback interna di BLAS, che è più lenta e non è pensata per essere usata nel performance computing! La risposta di @Woltan di seguito indica che questa è la spiegazione della differenza che vede in Numpy vs. Ctypes + BLAS.

Per risolvere la situazione, è necessario ATLAS o MKL --- controllare queste istruzioni: http://scipy.org/Installing_SciPy/Linux La maggior parte delle distribuzioni Linux viene fornita con ATLAS, quindi l'opzione migliore è installare il loro libatlas-devpacchetto (il nome può variare) .


Ho eseguito il tuo benchmark; i risultati sono gli stessi
jfs

Grazie mille per il tuo intervento. Ho eseguito il tuo benchmark con questo risultato. Quindi non posso riprodurre il tuo. Per controllare quale BLAS sta usando il mio numpy: non posso import numpy.core._dotblas. Quale potrebbe essere il problema qui? Cercherò di ripulire il mio benchmark e scrivere un makefile in modo che altri lo testino.
Woltan

2
@ Woltan: il fatto che non puoi importare numpy.core._dotblas significa che il tuo Numpy sta usando la sua copia interna di fallback di BLAS ( più lenta e non pensata per essere usata nel performance computing!), Piuttosto che la libreria BLAS che hai sul tuo sistema. Questo spiega i risultati ottenuti dal benchmark. Per risolvere la situazione, è necessario installare una versione BLAS con cui Numpy può funzionare --- che significa ATLAS o MKL. Ecco una serie di istruzioni: scipy.org/Installing_SciPy/Linux
pv.

@pv .: Potresti eseguire il benchmark di Woltan per confrontare i risultati.
jfs

1
Su Mac, puoi usare otool -Linvece di lddsu Linux
RichVel

9

Visto il rigore che hai dimostrato con la tua analisi, sono sorpreso dai risultati fino ad ora. Lo metto come una "risposta" ma solo perché è troppo lungo per un commento e fornisce una possibilità (anche se mi aspetto che tu l'abbia considerato).

Avrei pensato che l'approccio numpy / python non avrebbe aggiunto molto overhead per una matrice di ragionevole complessità, poiché all'aumentare della complessità, la proporzione a cui partecipa python dovrebbe essere piccola. Sono più interessato ai risultati sul lato destro del grafico, ma la discrepanza di ordini di grandezza mostrata sarebbe preoccupante.

Mi chiedo se stai usando i migliori algoritmi che numpy può sfruttare. Dalla guida alla compilazione per Linux:

"Build FFTW (3.1.2): Versioni SciPy> = 0.7 e Numpy> = 1.2: a causa di problemi di licenza, configurazione e manutenzione il supporto per FFTW è stato rimosso nelle versioni di SciPy> = 0.7 e NumPy> = 1.2. Invece ora utilizza una versione integrata di fftpack. Ci sono un paio di modi per sfruttare la velocità di FFTW se necessario per la tua analisi. Esegui il downgrade a una versione Numpy / Scipy che include il supporto. Installa o crea il tuo wrapper di FFTW. Vedi http: //developer.berlios.de/projects/pyfftw/ come esempio non approvato. "

Hai compilato numpy con mkl? ( http://software.intel.com/en-us/articles/intel-mkl/ ). Se stai usando Linux, le istruzioni per compilare numpy con mkl sono qui: http://www.scipy.org/Installing_SciPy/Linux#head-7ce43956a69ec51c6f2cedd894a4715d5bfff974 (nonostante url). La parte fondamentale è:

[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 

Se sei su Windows, puoi ottenere un binario compilato con mkl, (e anche pyfftw e molti altri algoritmi correlati) su: http://www.lfd.uci.edu/~gohlke/pythonlibs/ , con un debito di gratitudine a Christoph Gohlke del Laboratory for Fluorescence Dynamics, UC Irvine.

Avvertenza, in entrambi i casi, ci sono molti problemi di licenza e così via di cui essere a conoscenza, ma la pagina di informazioni li spiega. Di nuovo, immagino che tu abbia considerato questo, ma se soddisfi i requisiti di licenza (che su Linux è molto facile da fare), questo velocizzerebbe molto la parte numpy rispetto all'utilizzo di una semplice build automatica, senza nemmeno FFTW. Mi interesserà seguire questo thread e vedere cosa pensano gli altri. Indipendentemente da ciò, eccellente rigore e ottima domanda. Grazie per averlo postato.


Grazie per il tuo elaborato "commento" ^^. Per chiarire la mia configurazione python / numpy / BLAS: ho seguito questa guida all'installazione. Sono su un sistema operativo Linux e le versioni sono: Python 2.7, Scipy 0.9 Numpy 1.6. Sfortunatamente non ho costruito FFTW prima di mano, né ho usato mkl ...
Woltan

In un certo senso, è una fortuna. Significa che c'è un enorme margine di miglioramento nei risultati di Python e sembra che ti piacerebbe usare Python. Penso che se modifichi la tua build con quella mostrata sul link sarai molto più felice con la velocità di numpy, anche se sarei comunque affascinato nel vedere come si confronta con la tua implementazione C ++.
Profano

Potresti provare a costruire anche ATLAS, ma sembravano troppi grattacapi per le mie esigenze di prestazioni, quindi non ho alcuna esperienza. Immagino che se sei interessato a usare python ma sei in grado di usare C ++, ci sarebbe un punto in cui il costo di installazione per fare molte compilazioni speciali supererebbe i risparmi sul linguaggio e sarebbe più facile fare c ++. Ma mkl e fftw dovrebbero essere entrambi piuttosto semplici.
Profano

1
Attualmente MKL, Accelerate e OpenBLAS hanno prestazioni simili. OpenBLAS è tuttavia più scalabile di MKL.
Sturla Molden
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.