2013-03-28 13 views
5

Używam 64-bitowego systemu Windows 7 na 8-rdzeniowym procesorze. Uruchomiłem następujące:Dlaczego jeden wątek jest szybszy niż wiele wątków, mimo że mają one zasadniczo ten sam narzut?

#include "stdafx.h" 
    #include <iostream> 
    #include <Windows.h> 
    #include <process.h> 
    #include <ctime> 

    using namespace std; 

    int count = 0; 
    int t = time(NULL); 

    //poop() loops incrementing count until it is 300 million. 
    void poop(void* params) { 
     while(count < 300000000) { 
      count++; 
     } 


     cout<< time(NULL) - t <<" \n"; 
    } 

    int _tmain(int argc, _TCHAR* argv[]) 
    { 
     //_beginthread(poop, 0, NULL);  
     //_beginthread(poop, 0, NULL); 
     poop(NULL); 

     cout<<"done"<<endl; 

     while(1); 

     return 0; 
    } 

Porównałem wynik, kiedy odkomentowałem beginThread. Okazuje się, że wersja z jednym gwintem osiąga to najszybciej! W rzeczywistości dodawanie kolejnych wątków powoduje, że proces trwa jeszcze dłużej. Wykonanie 300 milionów operacji sprawiło, że proces trwał 8+ sekund, co, jak sądziłem, było wystarczająco dobre, aby wykluczyć wywołania funkcji dla beginThread + inne niewielkie obciążenie.

Zrobiłem trochę badań i ogólny wniosek, że proces wielowątkowy jest wolniejszy, to koszty ogólne. Ale w tym przypadku, bez względu na to, czy uruchamiam wiele wątków, czy pojedynczych, liczba razy liczba zmiennych (która istnieje w segmencie danych, ponieważ jest to wstępnie przydzielona zmienna afaik) jest dostępna jest równa. Zasadniczo, narzut (jeśli jest to problem napowietrzny) nie wynika z faktu, że kosztuje więcej dostępu do zmiennej globalnej niż zmienna lokalna.

Patrząc na mojego menedżera zadań, proces z pojedynczym wątkiem używa 13% procesora (około 1/8 rdzenia), a dodawanie wątków zwiększa użycie procesora w krokach co 1/8. Więc jeśli chodzi o moc procesora, zakładając, że menedżer zadań dokładnie to przedstawia, dodawanie wątków używa więcej procesora. Co dalej wprowadza mnie w zakłopotanie .. jak to jest, że używam bardziej ogólnego procesora, z oddzielnymi rdzeniami, ale ogólnie biorąc trwa dłużej, aby wykonać to zadanie?

TLDR: Dlaczego tak się dzieje

+1

To wygląda jak pole minowe dla wielu wątków modyfikujących zmienną naraz. – chris

+1

tak. Treść linii poleceń w pamięci podręcznej. –

+2

Wiele wątków modyfikujących ten sam obiekt bez synchronizacji: niezdefiniowane zachowanie. –

Odpowiedz

5

Kod jest z natury złe.

count++ to trójstopniowa operacja, która odczytuje wartość, zwiększa ją, a następnie zapisuje z powrotem w zmiennej.
Jeśli dwa wątki uruchomią od razu count++ na tej samej zmiennej, jedna z nich nadpisze zmiany drugiej.

Dlatego wersja wielowątkowa zakończy się wykonaniem dodatkowej pracy, ponieważ każdy wątek przerywa postęp pozostałych wątków.

Jeśli zmienna lokalna zostanie zmieniona na count, odmierzanie czasu powinno być bardziej normalne.

Alternatywnie można użyć zablokowanej inkrementacji, która jest bezpieczna dla wątków, ale ma dodatkowy koszt do synchronizacji wątków.

+0

Ah dziękuję, mądry panie. Czy powiedziałbyś, że blokowanie inkrementu i używanie wielu wątków jest szybsze niż jeden wątek? – lululoo

+4

@lululoo: Nie, ponieważ tylko jeden wątek będzie aktualizował 'count' na raz. Właśnie o to chodzi w synchronizacji. Zwiększanie liczby całkowitej wymaga obciążenia, inkrementacji i przechowywania. To nie jest operacja atomowa. To nie jest dobry kandydat do wielowątkowego rozwiązania. Powinieneś pomyśleć o bardziej realnym problemie światowym, tj. O zadaniu, które można podzielić na odrębne i oddzielne zadania. –

3

Jak zauważyli niektórzy komentatorzy oryginalnego pytania, masz problem z poprawnością i wydajnością. Po pierwsze wszystkie wątki uzyskują równoczesny dostęp do count. Oznacza to, że nie ma gwarancji, że wątki będą w rzeczywistości wszystkie liczyć do 300 milionów. Można rozwiązać ten problem poprawności deklarując liczyć w swoim rufie funkcja

void poop(void* params) { 
    int count = 0; 
    while(count < 300000000) { 
     count++; 
    } 
    cout<< time(NULL) - t <<" \n"; 
} 

Zauważ, że to nie jest problem dla t, ponieważ jest tylko do odczytu, nie jest napisane, przez wątkach. Jednak jest to problem z cout, jak również piszesz do tego z wielu wątków.

Ponadto, jak wskazano w komentarzach, wszystkie wątki uzyskują dostęp do pojedynczej lokalizacji pamięci.Oznacza to, że gdy wątek aktualizuje się zlicza, linia pamięci podręcznej, która ją zatrzymuje, musi zostać przepłukana i ponownie załadowana. Jest to bardzo nieefektywny dostęp do pamięci. Zazwyczaj dzieje się tak, gdy uzyskujesz dostęp do kolejnych elementów w tablicy, a nie do jednej zmiennej (zły pomysł, patrz wyżej). Rozwiązaniem tego problemu jest umieszczenie tablicy w celu upewnienia się, że każdy wpis jest dokładną wielokrotnością rozmiaru linii podręcznej L1, jest to oczywiście trochę specyficzne dla docelowego procesora. Inną opcją byłoby zrestrukturyzowanie swojego algorytmu, aby albo; każdy wątek przetwarzał duży blok kolejnych elementów lub każdy element dostępu do wątku w taki sposób, że wątek nie ma dostępu do sąsiednich lokalizacji.

Podczas korzystania z systemu Windows można rozważyć użycie wyższego poziomu abstrakcji dla kodu, a nie funkcji wątków Win32. Model Parallel Patterns Library pasuje do rachunku tutaj (podobnie jak Intel's Threaded Building Blocks).

concurrency::parallel_invoke(
     [=] { poop(nullptr); }, 
     [=] { poop(nullptr); } 
    ); 

To pozwala PPL zaplanować swoje zadania w puli wątków, zamiast aplikacji mającej jawnie tworzyć wątki.

Możesz również wziąć pod uwagę, że w przypadku naprawdę małych zadań obciążenie związane z uruchamianiem dodatkowych wątków może przewyższać zyski równoległego działania.

Powiązane problemy