2015-04-21 17 views
5

o to minimalny przykładem tego, co mi przeszkadzaC++ OpenMP z shared_pointer

#include <iostream> 
#include <memory> 
#include"omp.h" 

class A{ 
    public: 
     A(){std::cout<<this<<std::endl;} 
}; 

int main(){ 
#pragma omp parallel for 
    for(unsigned int i=0;i<4;i++){ 
     std::shared_ptr<A> sim(std::make_shared<A>()); 
    } 
    for(unsigned int i=0;i<4;i++){ 
     std::shared_ptr<A> sim(std::make_shared<A>()); 
    } 
} 

Jeśli biegnę że kod kilka razy mogę dostać tego rodzaju Wynik:

0xea3308 
0xea32d8 
0xea3338 
0x7f39f80008c8 
0xea3338 
0xea3338 
0xea3338 
0xea3338 

Co ja realizowany jest że 4 ostatnie wyjście ma zawsze taką samą liczbę znaków: (8). Ale z jakiegoś powodu zdarza się (nie zawsze), że jedno lub więcej spośród czterech pierwszych wyników zawiera więcej (14) znaków. Wygląda na to, że użycie openMP zmienia "naturę" wskaźnika (to jest moje naiwne zrozumienie). Ale czy to normalne zachowanie? Czy powinienem oczekiwać dziwnego zachowania?

EDIT

here jest na żywo test, który pokazuje ten sam problem w nieco bardziej skomplikowanej wersji kodu

+5

a dół 5 sekund po wysłaniu? czemu ? – PinkFloyd

Odpowiedz

3

Takie zachowanie jest całkowicie uzasadnione, zobaczmy, co się dzieje.

pętla szeregowa

W każdej iteracji dostajesz jedną A, który jest tworzony na stercie, a jeden jest uzyskiwanie zniszczone. Operacje te są uporządkowane w taki sposób:

  1. budowa
  2. zniszczenie
  3. budowa
  4. zniszczenie
  5. ... (i tak dalej)

Od A s tworzone są na stercie przechodzą przez alokator pamięci. Kiedy alokator pamięci otrzyma żądanie nowej pamięci, jak w kroku 3, najpierw (w wielu przypadkach) obejrzy ostatnio zwolnioną pamięć. Widzi, że ostatnia operacja była pamięcią wolną od dokładnie odpowiedniego rozmiaru (krok 2), a zatem ponownie zabierze tę porcję pamięci. Ta procedura będzie powtarzać się w każdej iteracji. Tak więc seryjna pętla (zwykle, ale niekoniecznie) poda ci ten sam adres w kółko.

Pętla równoległa

Pomyślmy teraz o równoległej pętli. Ponieważ nie ma synchronizacji, kolejność przydzielania pamięci i deallokacji nie jest określona. Dlatego możliwe jest ich przeplatanie w dowolny sposób, jaki możesz sobie wyobrazić. Tak więc przydzielający pamięć nie będzie mógł użyć tej samej sztuczki, co poprzednio, aby zawsze rozdawać tę samą pamięć.Przykład zamawiania może być, na przykład, że wszystkie cztery A s get zbudowane przed wszyscy dostać zniszczone - coś takiego:

  1. budowlany
  2. budowa
  3. budowa
  4. budowa
  5. zniszczenie
  6. zniszczenie
  7. zniszczenie
  8. zniszczenie

podzielnika pamięć będzie zatem musiał służyć 4 nowe kawałki pamięci zanim może trochę plecy i zacząć recyklingu.

Zachowanie wersji opartej na stosie jest nieco bardziej deterministyczne, ale może zależeć od optymalizacji kompilatora. W przypadku wersji szeregowej za każdym razem, gdy obiekt jest tworzony/niszczony, wskaźnik stosu jest regulowany. Ponieważ nic się między nimi nie dzieje, nadal będzie się tworzyć w tej samej lokalizacji.

W wersji równoległej każdy wątek ma własny stos we wspólnym systemie pamięci. Dlatego każdy wątek będzie tworzyć obiekty w innym miejscu pamięci i nie będzie możliwe "ponowne przetwarzanie".

Zachowanie, które widzisz, nie jest w żaden sposób dziwne ani nie jest gwarantowane. Zależy to od ilości fizycznych rdzeni, liczby uruchomionych wątków, liczby iteracji, z których korzystasz - ogólnie warunków pracy.

Dolna linia: wszystko jest w porządku, nie powinieneś czytać za dużo.

+1

Naprawdę fajna odpowiedź, thx. Mogę teraz zrozumieć, dlaczego w wersji szeregowej drukuje zawsze to samo i dlaczego w paralelach tak się nie dzieje. ale to nie odpowiada, dlaczego czwarta couta zawiera 14 znaków, a drzewo pierwsze tylko 8 (wzięte z mojego przykładu). Jakie jest znaczenie krótkich lub długich "nazw" dla wskaźnika? – PinkFloyd

+1

To byłaby tylko lokalizacja w pamięci. Stos znajduje się w górnej części obszaru pamięci i rośnie w dół. Długi adres oznaczałby, że położenie sterty zostało znalezione gdzieś bliżej stosu. – jepio

1

myślę, że to zależy od środowiska nie jest revelant i nie powinny być traktowane jako dziwne zachowanie. Korzystanie MS VS 2015 podgląd, Twój kod daje mi następujące (z włączonym OMP):

0082C3DC 
0082C41C 
0082C49C          
0082C45C          
0082C41C          
0082C41C          
0082C41C     
0082C41C 
+0

przeważnie dostaję taki wynik, ale czasami dostaję ten rodzaj, który pokazałem w moim pytaniu ... ile razy próbowałeś uruchomić mój kod? – PinkFloyd

+0

Zrobiłem to 10 razy, nigdy nie spotkałem twojego zachowania. Czego używasz ? Spróbuję użyć GCC. – coincoin

+0

gcc 4.7.2 na Linuksie – PinkFloyd