Chciałbym napisać program, który szeroko wykorzystuje funkcjonalności algebry liniowej BLAS i LAPACK. Ponieważ wydajność jest problemem, przeprowadziłem analizę porównawczą i chciałbym wiedzieć, czy przyjęte przeze mnie podejście jest uzasadnione.
Mam, że tak powiem, trzech zawodników i chcę przetestować ich wydajność za pomocą prostej matrycy macierzy mnożenie. Zawodnikami są:
- Numpy, korzystający tylko z funkcjonalności
kropki
. - Python, wywołujący funkcje BLAS przez wspólny obiekt.
- C++, wywoływanie funkcjonalności BLAS przez obiekt współdzielony.
Scenariusz
Zaimplementowałem mnożenie macierzy dla różnych wymiarów ja.
i
działa od 5 do 500 z przyrostem 5, a macierze m1
i m2
są skonfigurowane w następujący sposób:
m1 = numpy.random.rand(i,i).astype(numpy.float32) m2 = numpy.random.rand(i,i).astype(numpy.float32)
1. Numpy
Użyty kod wygląda tak:
tNumpy = timeit.Timer("numpy.dot(m1, m2)", "import numpy; from __main__ import m1 , m2") rNumpy.append((i, tNumpy.repeat(20, 1)))
2. Python, wywoływanie BLAS przez obiekt współdzielony
Za pomocą funkcji
_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))
kod testu wygląda tak:
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++, wywoływanie BLAS przez obiekt współdzielony
Teraz kod c++ jest oczywiście trochę dłuższy, więc ograniczam informacje do minimum.
Ładuję funkcję za pomocą
unieważniony* uchwyt = dlopen("libblas.so", RTLD_LAZY); void* Func = dlsym(uchwyt, "sgemm_");
Mierzę czas za pomocą gettimeofday
w ten sposób:
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(początek, koniec);
gdzie j
jest pętlą wykonaną 20 razy. Obliczam czas, który minął za pomocą
double CalcTime (początek przedziału czasu, koniec przedziału czasu) { double factor = 1000000; return (((double)end.tv_sec) * factor + ((double)end.tv_usec) - (((double)start.tv_sec) * factor + ((double)start.tv_usec))) / factor; }
Wyniki
Wynik jest pokazany na poniższym wykresie:
Pytania
- Czy uważasz, że moje podejście jest sprawiedliwe, czy może mam jakieś niepotrzebne koszty ogólne uniknąć?
- Czy spodziewałbyś się, że wynik pokaże tak dużą rozbieżność między podejściem C++ i Pythona? Oba używają obiektów współdzielonych do swoich obliczeń.
- Ponieważ wolałbym używać Pythona w moim programie, co mogę zrobić, aby zwiększyć wydajność podczas wywoływania procedur BLAS lub LAPACK?
Pobierz
Pełny test porównawczy można pobrać tutaj. (JF Sebastian umożliwił ten link^^)