mnożenie macierzy w wielu wątkach wolniejsze od zwykej funkcji

0

Witam.
Ostatnio napisałem sobie prostą sieć neuronową w języku autohotkey, z racji, że jest to język interpretacyjny (ala Python) to napisem także bibliotekę dll w c++ dla najbardziej wymagających funkcji co w sposób znaczny poprawiło wydajność. Teraz chciałem iść krok dalej i robić obliczenia w wielu wątkach czego nigdy wcześniej nie robiłem. Po przeczytaniu kilku poradników i modyfikacji jednego z przykładów w internecie finalnie udało mi się uzyskać pożądany efekt jednak wydajność zamiast wzrosnąć to jeszcze spadła o ponad połowę.
Moje pytani więc brzmi, co mogę zrobić aby poprawić wydajność albo co robię źle?

tutaj jest moja pierwotna funkcja do mnożenia macierzy

double matrix_dot_ahk3(AHKObject *a, AHKObject *b,AHKObject *c)
{
    int rowsA = a->Count;
    int colsA = a->A->Value.ahkObject->Count;
    int rowsB = b->Count;
    int colsB = b->A->Value.ahkObject->Count;
    int colsC = colsB;

	for(int rowA=0;rowA<rowsA;rowA++){
        for(int colB=0;colB<colsB;colB++){
        for(int colA=0;colA<colsA;colA++){
                    c->A[rowA].Value.ahkObject->A[colB].Value.d += ahk_readMatrix(a,rowA,colA)*ahk_readMatrix(b,colA,colB);
            }
        }
	}
	return 1;
}

Teraz to samo z tą różnicą, że nie jest liczona całość na raz tylko określoną ilość rzędów z macierzy A przypisuję poszczególnym wątkom, tzn jeśli macierz A ma 400 rzędów to na każdy wątek przypada 100 innych rzędów dla 4 wątków.

double matrix_dot_ahk(AHKObject *a, AHKObject *b,AHKObject *c)
{
    int rowsA = a->Count;

    int step = ceil_(rowsA,MAX_THREAD); // ilość rzędów macierzy A na wątek
    void *pp[MAX_THREAD];
    thread_dot_param args[MAX_THREAD];
    for (int i = 0; i < MAX_THREAD; i++){
        pp[i] = (void*)&args[i];
    }
    for (int i = 0; i < MAX_THREAD; i++) {
        int from = step*i;
        int to   = from+step;

        to = to>rowsA ? rowsA : to;
        args[i].from = from;
        args[i].to = to;
        args[i].a = a;
        args[i].b = b;
        args[i].c = c;
//        cout << from << "   " << to << endl;
        if(to>=rowsA)
            break;
    }
    threads_call(MAX_THREAD,multi,pp);
    return 666;
}


void* multi(void* arg)
{
    thread_dot_param *pp = (thread_dot_param*)arg;
    int from = pp->from;
    int to = pp->to;
    if(from==to){
        pthread_exit(NULL);
        return NULL;
    }
    AHKObject *a = pp->a;
    AHKObject *b = pp->b;
    AHKObject *c = pp->c;
    int rowsA = a->Count;
    int colsA = a->A->Value.ahkObject->Count;
    int rowsB = b->Count;
    int colsB = b->A->Value.ahkObject->Count;
    int colsC = colsB;
    for(int rowA=from;rowA<to;rowA++){
    for(int colB=0;colB<colsB;colB++){
    for(int colA=0;colA<colsA;colA++){
        c->A[rowA].Value.ahkObject->A[colB].Value.d += ahk_readMatrix(a,rowA,colA)*ahk_readMatrix(b,colA,colB);
    }}}
    pthread_exit(NULL);
    return NULL;
}


void threads_call(int threadsN,void*(*funkcja)(void*),void** args){
    // declaring four threads
    pthread_t threads[threadsN];

    // Creating four threads, each evaluating its own part
    for (int i = 0; i < threadsN; i++) {
        pthread_create(&threads[i], NULL, funkcja,args[i]);
    }

    // joining and waiting for all threads to complete
    for (int i = 0; i < threadsN; i++){
        pthread_join(threads[i], NULL);
    }
}
0
  1. A faktycznie to ci w ogóle startuje na wielu corach? Może te wszystkie wątki mielą na jednym rdzeniu po prostu? ;]
  2. A jak te dane są składowane w pamięci? Czasem nie robisz tak, ze teraz zamiast czytać sekwencyjnie to czytasz kawałki które są daleko od siebie?
0
  1. tak, jak zastosowałem pętlę while(1) to program mi się zawiesił ale użycie CPU było 100% dla 4 wątków, 75% dla 3 itd (dla 4 wątkowego procesora) czyli chyba działa działa. Wyniki algorytmu także są poprawne.
  2. o ile dobrze rozumiem to chodzi ci o to
c->A[rowA].Value.ahkObject->A[colB].Value.d += ahk_readMatrix(a,rowA,colA)*ahk_readMatrix(b,colA,colB);

Wiem, że jest to dalekie od ideału ale w taki sposób nie muszę konwertować obiektu z autohotkeya co jak widać nie jest taką prostą tablicą do "normalnej" tablicy, na obecna chwilę nie to jest moim problemem.

1

Po pierwsze ten kod to jest C z klasami, a nie C++ (zawsze mnie to mierzi).
Po drugie nigdzie nie widzę testów mierzących wydajność rozwiązania.
Po trzecie, żeby wielowątkowość coś dała macierz musi być bardzo duża.
Po czwarte, optymalizację mnożenia macierzy zacząłbym od optymalizacji dostępu do pamięci, by zredukować cache miss.
Dopiero jakby to było zrobione, to wtedy zacząłbym kombinować z wieloma wątkami.

C++11 ma std::future, które powinno pomóc napisać ładny kod. Powinno być też efektywniejsze od pthread, które jest bardziej nisko poziomowe i powoduje faktyczne tworzenie wątku, które jest dość kosztowne. Tymczasem C++11 std::async powinno korzystać z thread pool (zestaw gotowych wątków, którym przydzielane są zadania).

1

na obecna chwilę nie to jest moim problemem.

Jesteś pewien? Bo widzisz wyobraź sobie że masz macierz NxM. Dla ciebie może to nie mieć żadnego znaczenia czy sobie traversujesz iterując najpierw po kolumnie czy po wierszu, ale dla CPU znaczenie jest kolosalne. Bo CPU musi wczytać dane z pamięci (co trwa długo) jeśli ich nie ma w cache. Dlatego sprytnie ładuje sobie od razu więcej pamięci, czyli nie jedną komórkę macierzy, tylko blok pamięci od tego miejsca daleko w przód. I teraz jeśli kolejny odczyt z pamięci jest niedaleko (albo w ogóle sekwencyjnie) to super, bo nie trzeba czytać z pamięci nic nowego, tylko lecimy po cache. Jeśli jednak następny odczyt jest z odległego miejsca (bo np. kolejny wiersz macierzy jest daleko) to znów trzeba czytać z pamięci.
Taki odczyt z pamięci albo nawet z cache wyższego poziomu to są rzędy wielkości różnicy (!)

Poza tym jak duże są te dane i twoje próbki czasowe? W sensie to jest czas rzędu minuty czy sekundy? Bo pamiętaj że samo startowanie wątków też cię "kosztuje", więc dla małych danych to jest niemiarodajne.

Materiał poglądowy:

#include <cstdlib>
#include <cstdio>

int main(){
    int N = 10240;
    int M = 100000;
    int* matrix = new int[N*M];
    for(int i=0;i<N;i++){
        for(int j=0;j<M;j++){
            matrix[i*M+j] = 1;
        }
    }
    
    int sum = 0;
    for(int i=0;i<N;i++){
        for(int j=0;j<M;j++){
            sum += matrix[i+j*N];
            // sum += matrix[i*M+j];
        }
    }
    
    printf("%d",sum);
    return 0;
}

Robimy dokładnie to samo, zmieniamy tylko chodzenie po wierszu/kolumnie. Porównaj sobie czasy wykonania dla tego wykomentowanego kawałka.

1 użytkowników online, w tym zalogowanych: 0, gości: 1