Ich möchte ein Programm schreiben, das die linearen Algebra-Funktionen von BLAS und LAPACK in großem Umfang nutzt. Da Leistung ein Problem ist, habe ich ein Benchmarking durchgeführt und würde gerne wissen, ob der von mir verfolgte Ansatz legitim ist.
Ich habe sozusagen drei Teilnehmer und möchte ihre Leistung mit einer einfachen Matrix-Matrix-Multiplikation testen. Die Teilnehmer sind:
- Numpy, nutzt nur die Funktionalität von
dot
. - Python, der die BLAS-Funktionen über ein gemeinsam genutztes Objekt aufruft.
- C ++, Aufrufen der BLAS-Funktionen über ein gemeinsam genutztes Objekt.
Szenario
Ich habe eine Matrix-Matrix-Multiplikation für verschiedene Dimensionen implementiert i
. i
Läuft von 5 bis 500 mit einem Inkrement von 5 und den Matrizen m1
und m2
ist wie folgt aufgebaut:
m1 = numpy.random.rand(i,i).astype(numpy.float32)
m2 = numpy.random.rand(i,i).astype(numpy.float32)
1. Numpy
Der verwendete Code sieht folgendermaßen aus:
tNumpy = timeit.Timer("numpy.dot(m1, m2)", "import numpy; from __main__ import m1, m2")
rNumpy.append((i, tNumpy.repeat(20, 1)))
2. Python ruft BLAS über ein gemeinsam genutztes Objekt auf
Mit der Funktion
_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))
Der Testcode sieht folgendermaßen aus:
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 ++, BLAS über ein gemeinsam genutztes Objekt aufrufen
Jetzt ist der C ++ - Code natürlich etwas länger, so dass ich die Informationen auf ein Minimum reduziere.
Ich lade die Funktion mit
void* handle = dlopen("libblas.so", RTLD_LAZY);
void* Func = dlsym(handle, "sgemm_");
Ich messe die Zeit folgendermaßen gettimeofday
:
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);
Wo j
läuft eine Schleife 20 Mal. Ich berechne die Zeit, die mit vergangen ist
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;
}
Ergebnisse
Das Ergebnis ist in der folgenden Darstellung dargestellt:
Fragen
- Halten Sie meinen Ansatz für fair oder gibt es unnötige Gemeinkosten, die ich vermeiden kann?
- Würden Sie erwarten, dass das Ergebnis eine so große Diskrepanz zwischen dem C ++ - und dem Python-Ansatz aufweist? Beide verwenden gemeinsam genutzte Objekte für ihre Berechnungen.
- Was kann ich tun, um die Leistung beim Aufrufen von BLAS- oder LAPACK-Routinen zu steigern, da ich lieber Python für mein Programm verwenden möchte?
Herunterladen
Der vollständige Benchmark kann hier heruntergeladen werden . (JF Sebastian hat diesen Link möglich gemacht ^^)
r
Matrix ist ungerecht. Ich löse gerade das "Problem" und veröffentliche die neuen Ergebnisse.
np.ascontiguousarray()
(beachten Sie die Reihenfolge C vs. Fortran). 2. Stellen Sie sicher, dass np.dot()
das gleiche verwendet libblas.so
.
m1
und m2
haben das ascontiguousarray
Flag als True
. Und numpy verwendet dasselbe gemeinsame Objekt wie C. Was die Reihenfolge des Arrays betrifft: Derzeit interessiert mich das Ergebnis der Berechnung nicht, daher ist die Reihenfolge irrelevant.