2012-06-22 10 views
5

Używam wątków w C++, w szczególności za pomocą ich do równoległego działania mapy.C++ over wątku

Oto kod:

#include <thread> 
#include <iostream> 
#include <cstdlib> 
#include <vector> 
#include <math.h> 
#include <stdio.h> 

double multByTwo(double x){ 
    return x*2; 
} 

double doJunk(double x){ 
    return cos(pow(sin(x*2),3)); 
} 

template <typename T> 
void map(T* data, int n, T (*ptr)(T)){ 
    for (int i=0; i<n; i++) 
    data[i] = (*ptr)(data[i]); 
} 

template <typename T> 
void parallelMap(T* data, int n, T (*ptr)(T)){ 
    int NUMCORES = 3; 
    std::vector<std::thread> threads; 
    for (int i=0; i<NUMCORES; i++) 
    threads.push_back(std::thread(&map<T>, data + i*n/NUMCORES, n/NUMCORES, ptr)); 
    for (std::thread& t : threads) 
    t.join(); 
} 

int main() 
{ 
    int n = 1000000000; 
    double* nums = new double[n]; 
    for (int i=0; i<n; i++) 
    nums[i] = i; 

    std::cout<<"go"<<std::endl; 

    clock_t c1 = clock(); 

    struct timespec start, finish; 
    double elapsed; 

    clock_gettime(CLOCK_MONOTONIC, &start); 

    // also try with &doJunk 
    //parallelMap(nums, n, &multByTwo); 
    map(nums, n, &doJunk); 

    std::cout << nums[342] << std::endl; 

    clock_gettime(CLOCK_MONOTONIC, &finish); 

    printf("CPU elapsed time is %f seconds\n", double(clock()-c1)/CLOCKS_PER_SEC); 

    elapsed = (finish.tv_sec - start.tv_sec); 
    elapsed += (finish.tv_nsec - start.tv_nsec)/1000000000.0; 

    printf("Actual elapsed time is %f seconds\n", elapsed); 
} 

z multByTwo wersja równoległego jest rzeczywiście nieznacznemu zmniejszeniu (1.01 sekund w porównaniu .95 czasie rzeczywistym) oraz z doJunk szybszym (51 w porównaniu do 136 w czasie rzeczywistym). Oznacza to dla mnie, że

  1. zrównoleglanie pracuje, a
  2. tam jest naprawdę duży narzut z deklarowania nowe wątki. Wszelkie przemyślenia, dlaczego obciążenie jest tak duże i jak mogę tego uniknąć?
+2

Należy zauważyć, że niekoniecznie jest to specyficzne dla * natywnych wątków w C++ *, ale * implementacja * i kompilator, którego używasz. W związku z tym trudno jest udzielić jednoznacznej odpowiedzi. – zxcdw

+0

Jakiego sprzętu używasz tego kodu? Typ procesora i liczba gniazd? BARAN? OS? Wersja kompilatora? –

Odpowiedz

7

Zgadnij: prawdopodobnie zobaczysz, że kod multByTwo jest tak szybki, że osiągasz nasycenie pamięci. Kod nigdy nie będzie działał szybciej bez względu na to, ile mocy procesora na niego rzucisz, ponieważ działa już tak szybko, jak tylko może uzyskać bity do iz pamięci RAM.

+0

To wygląda poprawnie. OP ma zestaw danych 8 GB. 8 GB w 1,01 sekundy brzmi idealnie dla high-endowego Nehalem lub low-endowego procesora generacji Sandy Bridge. – Mysticial

0

Nawożenie nowych wątków może być kosztowną operacją w zależności od platformy. Najprostszym sposobem na uniknięcie tego narzutu jest odrodzenie kilku wątków podczas uruchamiania programu i posiadanie kolejki zadań. Wierzę, że std :: async zrobi to za Ciebie.

+1

OP tylko je odradza - a zadanie jest dość duże 'n = 1000000000'. Więc nie sądzę, żeby tak było. – Mysticial

+0

Moja zła.Nie czytając wystarczająco uważnie :-P –

+0

Wierzę, że wynik końcowy będzie taki sam, jeśli liczba wątków jest mniejsza niż liczba zwrócona przez std :: thread :: hardware_concurrency() – manasij7479

2

Wiele wątków może wykonać więcej pracy w krótszym czasie na maszynie wielordzeniowej.

Poza tym po prostu zmieniają się w sposób okrągły i robin.

+0

NIE ZGADZAM SIĘ, ŻE TO PRAWDZIWE OŚWIADCZENIE !!! Spójrz na moją odpowiedź !!! – trumpetlicks

+0

Nie mówię o "postrzeganej" wydajności i interfejsie użytkownika. Mówię o prawdziwej pracy. Jeśli jest tylko jeden procesor, tylko jeden wątek może działać jednocześnie. –

+0

To prawda, ale jak OS przypisuje czas do wątków, robi ogromną różnicę. Widziałem to w realnym świecie dzięki aplikacjom, które musiałem pisać w szkole (jeszcze przed wieloma rdzeniami), że wydajność została znacznie zwiększona poprzez ich przewijanie. Spójrz na moją odpowiedź Mówię o efekcie round robin, nie używam tego terminu, ale wyjaśniono, dlaczego wielowątkowa aplikacja otrzyma więcej czasu na skrócenie czasu procesora !!! – trumpetlicks

3

Nie określono sprzętu do przetestowania programu ani wersji kompilatora i systemu operacyjnego. Wypróbowałem twój kod na naszych cztero-gniazdowych systemach Intel Xeon pod 64-bitowym Scientific Linux z g++ 4.7 skompilowany ze źródła.

Pierwszy na starszym systemie Xeon X7350 mam następujący taktowania:

multByTwo z map

CPU elapsed time is 6.690000 seconds 
Actual elapsed time is 6.691940 seconds 

multByTwo z parallelMap na 3 rdzeniach

CPU elapsed time is 7.330000 seconds 
Actual elapsed time is 2.480294 seconds 

Równoległy SpeedUp jest 2,7x.

doJunk z map

CPU elapsed time is 209.250000 seconds 
Actual elapsed time is 209.289025 seconds 

doJunk z parallelMap 3 rdzenie

CPU elapsed time is 220.770000 seconds 
Actual elapsed time is 73.900960 seconds 

równoległy przyspieszenie jest 2.83x.

Należy zauważyć, że X7350 pochodzi z dość starej rodziny pre-Nehalem "Tigerton" z magistralą FSB i współdzielonym kontrolerem pamięci zlokalizowanym na północnym moście. Jest to czysty system SMP bez efektów NUMA.

Następnie uruchamiam twój kod na czterogniazdowym Intel X7550. Są to Xeony Nehalem ("Beckton") ze sterownikiem pamięci zintegrowanym z procesorem, a więc cztero-węzłowym systemem NUMA. Wątki uruchomione na jednym gnieździe i dostęp do pamięci znajdującej się w innym gnieździe będą działać nieco wolniej. To samo dotyczy również procesu szeregowego, który może zostać przeniesiony do innego gniazda za pomocą jakiejś głupiej decyzji programu planującego. Wiązania w takim systemie jest bardzo ważne, ponieważ można zobaczyć od taktowania:

multByTwo z map

CPU elapsed time is 4.270000 seconds 
Actual elapsed time is 4.264875 seconds 

multByTwo z map związany z węzłem NUMA 0

CPU elapsed time is 4.160000 seconds 
Actual elapsed time is 4.160180 seconds 

multByTwo z map związany Węzeł NUMA 0 i gniazdo procesora 1

CPU elapsed time is 5.910000 seconds 
Actual elapsed time is 5.912319 seconds 

mutlByTwo z parallelMap na trzy rdzenie

CPU elapsed time is 7.530000 seconds 
Actual elapsed time is 3.696616 seconds 

równoległe przyspieszenie tylko 1.13x (w stosunku do przyśpieszeniu szeregowego węzła-związany). Teraz z wiązania:

multByTwo z parallelMap na 3 rdzeniach związanych z węzłem NUMA 0

CPU elapsed time is 4.630000 seconds 
Actual elapsed time is 1.548102 seconds 

Parallel SpeedUp jest 2.69x - aż do Tigerton procesorów.

multByTwo z parallelMap na trzy rdzenie związanych z węzłem NUMA 0 i gniazda procesora 1

CPU elapsed time is 5.190000 seconds 
Actual elapsed time is 1.760623 seconds 

równoległe przyspieszenie jest 2.36x - 88% w poprzednim przypadku.

(byłem zbyt niecierpliwy czekać na kod doJunk skończyć na stosunkowo wolniej Nehalems ale spodziewałbym się nieco lepszą wydajność, jak było w przypadku Tigerton)

Jest jedno zastrzeżenie z NUMA wiążące chociaż. Jeśli wymusisz na przykład powiązanie z węzłem NUMA 0 z numactl --cpubind=0 --membind=0 ./program ograniczy to alokację pamięci tylko do tego węzła, aw danym systemie pamięć dołączona do procesora 0 może nie wystarczyć i najprawdopodobniej wystąpi awaria czasu wykonania.

Jak widać, istnieją czynniki, inne niż narzut z tworzenia wątków, które mogą znacząco wpłynąć na czas wykonywania kodu. Również w bardzo szybkich systemach obciążenie może być zbyt wysokie w porównaniu z pracą obliczeniową wykonywaną przez każdy wątek. Dlatego przy zadawaniu pytań dotyczących wydajności równoległej należy zawsze podawać jak najwięcej szczegółów na temat sprzętu i środowiska użytego do pomiaru wydajności.

+0

Dzięki za szczegółową odpowiedź! – andyInCambridge