starałem się dowiedzieć najszybszy sposób to zrobić mnożenie macierzy i próbowałem 3 różne sposoby:Dlaczego mnożenie macierzy jest szybsze przy użyciu numpy niż w przypadku ctypes w Pythonie?
- Czysta wdrożenie Pythona: tutaj żadnych niespodzianek.
- realizacja Numpy pomocą
numpy.dot(a, b)
- łączenia się z C, stosując
ctypes
moduł w Pythonie.
Jest to kod C, która przekształca się w udostępnionej biblioteki:
#include <stdio.h>
#include <stdlib.h>
void matmult(float* a, float* b, float* c, int n) {
int i = 0;
int j = 0;
int k = 0;
/*float* c = malloc(nay * sizeof(float));*/
for (i = 0; i < n; i++) {
for (j = 0; j < n; j++) {
int sub = 0;
for (k = 0; k < n; k++) {
sub = sub + a[i * n + k] * b[k * n + j];
}
c[i * n + j] = sub;
}
}
return ;
}
a kod Pythona, który wywołuje go:
def C_mat_mult(a, b):
libmatmult = ctypes.CDLL("./matmult.so")
dima = len(a) * len(a)
dimb = len(b) * len(b)
array_a = ctypes.c_float * dima
array_b = ctypes.c_float * dimb
array_c = ctypes.c_float * dima
suma = array_a()
sumb = array_b()
sumc = array_c()
inda = 0
for i in range(0, len(a)):
for j in range(0, len(a[i])):
suma[inda] = a[i][j]
inda = inda + 1
indb = 0
for i in range(0, len(b)):
for j in range(0, len(b[i])):
sumb[indb] = b[i][j]
indb = indb + 1
libmatmult.matmult(ctypes.byref(suma), ctypes.byref(sumb), ctypes.byref(sumc), 2);
res = numpy.zeros([len(a), len(a)])
indc = 0
for i in range(0, len(sumc)):
res[indc][i % len(a)] = sumc[i]
if i % len(a) == len(a) - 1:
indc = indc + 1
return res
musiałbym założyć, że wersja użyciu C byłoby szybciej ... i bym się zgubił! Poniżej jest mój punkt odniesienia, który wydaje się wskazywać, że albo zrobił to nieprawidłowo, albo że numpy
jest głupio szybki:
Chciałbym zrozumieć, dlaczego wersja numpy
jest szybszy niż w wersji ctypes
I” Nawet nie mówię o czystej implementacji Pythona, ponieważ jest to oczywiste.
Ładne pytanie - okazuje się, że np.dot() jest również szybszy niż naiwna implementacja GPU w C. – user2398029
Jedną z największych rzeczy, które spowalniają naiwną C matmul, jest wzorzec dostępu do pamięci. 'b [k * n + j];' wewnątrz wewnętrznej pętli (ponad 'k') ma krok' n', więc dotyka każdego wiersza innej linii pamięci podręcznej. Twoja pętla nie może automatycznie wektoryzować za pomocą SSE/AVX. ** Rozwiąż to poprzez przeniesienie 'b' z góry, co kosztuje O (n^2) czas i zwraca się w zmniejszonej pamięci podręcznej chybi podczas gdy robisz O (n^3) ładuje z' b'. ** To nadal być naiwną implementacją bez blokowania pamięci podręcznej (np. –
Ponieważ używasz 'int sum' (z jakiegoś powodu ...), twoja pętla mogłaby się wektoryzować bez' -ffast-matath', gdyby wewnętrzna pętla uzyskiwała dostęp do dwóch tablic sekwencyjnych. FP matematyka nie jest asocjacyjna, więc kompilatory nie mogą zmienić kolejności operacji bez '-ffast-matath', ale matematyka całkowita jest asocjacyjna (i ma mniejsze opóźnienie niż dodanie FP, co pomaga, jeśli nie zamierzasz zoptymalizować swojej pętli z wiele akumulatorów lub inne ukryte elementy opóźniające). 'float' ->' konwersja 'int' kosztuje mniej więcej tyle samo co FP' add' (tak naprawdę przy użyciu FP dodaj ALU na procesorach Intela), więc nie jest to warte zoptymalizowanego kodu. –