Ik zou graag een programma willen schrijven dat uitgebreid gebruik maakt van BLAS en LAPACK lineaire algebra functionaliteiten. Omdat prestaties een probleem zijn, heb ik wat benchmarking gedaan en zou graag willen weten of de benadering die ik heb gevolgd legitiem is.
Ik heb, om zo te zeggen, drie deelnemers en wil hun prestaties testen met een eenvoudige matrix-matrix vermenigvuldiging. De deelnemers zijn:
- Numpy, die alleen gebruik maakt van de functionaliteit van
dot
. - Python, die de BLAS-functionaliteit aanroept via een gedeeld object.
- C++, roept de BLAS-functionaliteiten aan via een gedeeld object.
Scenario
Ik implementeerde een matrix-matrix-vermenigvuldiging voor verschillende dimensies ik
. i
loopt van 5 tot 500 met een toename van 5 en de matrices m1
en m2
zijn als volgt ingesteld:
m1 = numpy.random.rand(i,i).astype(numpy.float32) m2 = numpy.random.rand(i,i).astype(numpy.float32)
1. Numpy
De gebruikte code ziet er als volgt uit:
tNumpy = timeit.Timer("numpy.dot(m1, m2)", "import numpy; from __main__ import m1 , m2") rNumpy.append((i, tNumpy.repeat(20, 1)))
2. Python, BLAS aanroepend via een gedeeld object
Met de functie
_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(nul) , r.ctypes.data_as(ctypes.c_void_p), byref(n))
de testcode ziet er als volgt uit:
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++, aanroepen van BLAS via een gedeeld object
Nu is de c++-code natuurlijk iets langer, dus ik beperk de informatie tot een minimum.
Ik laad de functie met
void* handle = dlopen("libblas.so", RTLD_LAZY); void* Func = dlsym(handle, "sgemm_");
Ik meet de tijd met gettimeofday
als volgt:
gettimeofday(&start, NULL); f(&no_trans, &no_trans, &dim, &dim, &dim, &one, A, &dim, B, &dim, &nul, Return, &dim); gettimeofday(&end, NULL); dTimes[j] = CalcTime(begin, einde);
waarbij j
een lus is die 20 keer wordt uitgevoerd. Ik bereken de verstreken tijd met
double CalcTime(timeval start, timeval end) { dubbele factor = 1000000; return (((double)end.tv_sec) * factor + ((double)end.tv_usec) - (((double)start.tv_sec) * factor + ((double)start.tv_usec))) / factor; }
Resultaten
Het resultaat wordt weergegeven in de onderstaande grafiek:
Vragen
- Vind je mijn aanpak eerlijk, of zijn er onnodige overheadkosten die ik kan vermijden?
- Zou je verwachten dat het resultaat zo'n enorme discrepantie zou vertonen tussen de c++- en python-aanpak? Beide gebruiken gedeelde objecten voor hun berekeningen.
- Aangezien ik liever Python voor mijn programma gebruik, wat kan ik dan doen om de prestaties te verbeteren bij het aanroepen van BLAS- of LAPACK-routines?
Download
De volledige benchmark kan hier worden gedownload. (JF Sebastian heeft die link mogelijk gemaakt^^)