TParallel.For – programowanie równoległe i blokada dostępu do zmiennych wspólnych

Odpowiedz Nowy wątek
2019-07-23 09:25
0

Mam tablicę

maks: array[0..9] of  record
  ileCyfr: integer;
  liczba: int64
end;

zdefiniowaną przed pętlą Tparallel.For. Do tej tablicy wszystkie wątki zapisują jednocześnie. Jak zrobić, żeby w momencie zapisywania zablokować dostęp innych wątków do niej? Bez blokady dostaję nieprawidłowe niskie wartości. Interlocked.Add do tej tablicy nie ma zastosowania tutaj bo nie robię dodawania tylko dowolną wartość przypisuję do tablicy dzielonej.

edytowany 7x, ostatnio: furious programming, 2019-07-23 15:41

Pozostało 580 znaków

2019-07-23 17:33
0

Ktoś sugerował na forum angielskim dawno temu, że w Delphi TParallel.For nie zadziała z żadną sekcją krytyczną i że trzeba wyprowadzić tablicę lokalną z obliczeniami dla każdego wątku a potem zrobić dodatkowe obliczenia już proste biorąc która z wyprowadzonych tablic ma maksimum dla danego indeksu. To jednak komplikuje, bo naprzód nie wiem jaka jest składnia żeby wątek wyprowadził tablicę. Potem nie wiem ile będzie wątków bo to zależy od komputera ile ma rdzeni procesor. Jak bym to wiedział to reszta jest prosta.

Pozostało 580 znaków

2019-07-23 17:53
1

Jestem na wakacjach i siedzę na telefonie, nie chce analizować całego Twojego kodu i pomijając wielowątkowość to czy nie lepiej byłoby na początek zoptymalizować wyszukiwanie tych liczb?
Sprzątasz wszystkie liczby w zakresie a to trochę marnowanie czasu skoro interesują Ciebie tylko pierwiastki tej samej liczby. Znajdź min i Max (sqrt z zakresu) i sporządzają liczby

x*x;
Inc(x);

Przy wartościach tej wielkości pominiesz sprawdzanie wielu niepotrzebnych wartości.

Ps. To zerowanie wartości wektora temp powinno być po sprawdzeniu że liczba się nadaje. Kolejna strata czasu przy zerwaniu dla każdej iteracji.

edytowany 2x, ostatnio: furious programming, 2019-07-23 18:41
Faktycznie, dziękuję, przegapiłem to. Na projecteuler.net jest bardzo dużo trudnych skróceń matematycznych i tamtych to już bym nie dał rady zrobić bo moja wiedza matematyczna jest niewielka. Natomiast w tym wypadku nie chodzi mi o konkretnie ten problem tylko ogólnie jak zrobić programowanie równoległe, bo mam cztery rdzenie i mogę przyspieszyć obliczenia wielu rzeczy trzykrotnie. Odnośnie mojej ostatniej uwagi o wyprowadzeniu wartości z wątku dla dej pętli, jak to zrobić? - MariuszJ 2019-07-23 18:01
Z wątkami raczej nie pomogę w tej chwili. Idę właśnie rozpadać ognisko pod kiełbaski :) - Clarc 2019-07-23 18:05
Wektor temp muszę wyzerować bo on mi sumuje wystąpienia cyfr w liczbie i na końcu sprawdzam czy liczba się nadaje i na której szukanej cyfrze. Nie będę miał wyniku do porównania dopóki nie przejdę po kolejnych cyfrach liczby. - MariuszJ 2019-07-23 18:12
Zerowanie powinno być po sprawdzeniu warunku if IdKw(i). Inaczej zerujesz w większości wyzerowany wektor. - Clarc 2019-07-23 18:25
if IdKw(i) zostało wyrzucone zgodnie z twoją sugestią i czas wykonania skrócił się z sześciu minut do 40 milisekund. Nadal czekam na to czy ktoś zna składnię Tparallel.for jak wyprowadzić tablicę z częściowymi wynikami żeby potem z każdego wątku tablicę połączyć. Jest sporo problemów których czas przebiegu da się skrócić przynajmniej trzykrotnie a wynosi on teraz pół godziny. - MariuszJ 2019-07-23 19:37

Pozostało 580 znaków

2019-07-24 10:46
1

Gotowego rozwiązania nie podam bo siedzę na telefonie ale do wątków pod Delphi proponowałbym Omni Thread Library. Ciężko mi teraz szukać ale jest masa przykładów i bogata dokumentacja. Zerknij tutaj
http://www.omnithreadlibrary.com/book/chap08.html

Pozostało 580 znaków

2019-07-26 09:11
0
Clarc napisał(a):

Gotowego rozwiązania nie podam bo siedzę na telefonie ale do wątków pod Delphi proponowałbym Omni Thread Library. Ciężko mi teraz szukać ale jest masa przykładów i bogata dokumentacja. Zerknij tutaj
http://www.omnithreadlibrary.com/book/chap08.html

Ostatecznie zrezygnowałem z użycia Parallel.for bo zawiera błędy. Sekcje krytyczne też dawały nieprawidłowy wynik. Wynik udało mi się uzyskać z użyciem TThread. Każdy wątek dostał osobną zmienną globalną do wyprowadzenia wyników, następnie połączyłem wyniki z wątków. Dla czterech rdzeni przyspieszenie czasu przebiegu jest czterokrotne. Jeżeli już mamy program zoptymalizowany pod względem czasu wykonania, używamy najlepszego algorytmu (nie tak jak w moim przypadku kiedy się walnąłem o prostą rzecz i czas wykonania zwiększył się tysiąc razy), to możemy jeszcze użyć przyspieszenie kilkukrotnego w zależności od tego ile mamy rdzeni. Problem jest tylko z tym, że muszę powielać kod dla każdego wątku osobno. Po raz pierwszy mój program użył prawie 100% mocy procesora!! Wcześniej dochodził tylko do 25% bo działał na jednym wątku. Ucieszyło mnie to.

Tutaj jest ogólny schemat jak to zrobić. Podaję to bo może się komuś przydać.

procedure TForm1.MyButtonClick(Sender: TObject);
var 
  tasks: array of ITask; 
  i,k,: Integer; 
  wynikiRdzeni: array [1..4]of wynik; //cztery wątki
  maks: wynik;
begin 
  Setlength (tasks ,4) //w zależności od ilości rdzeni procesora
 tasks[0] := TTask.Create (procedure () 
  begin 
    tu dać fragment pętli, 
       obliczenia i wyprowadzenie wartości do zmiennej globalnej wynikiRdzeni[1]
  end); 
 tasks[0].Start; 

 tasks[1] := TTask.Create (procedure () 
   begin 
    tu dac kolejny fragment pętli
       obliczenia i wyprowadzenie wartości do zmiennej globalnej wynikiRdzeni[2]
 end); 
 tasks[1].Start; 

 tasks[2] := TTask.Create (procedure () 
   begin 
    tu dac kolejny fragment pętli
       obliczenia i wyprowadzenie wartości do zmiennej globalnej wynikiRdzeni[3]
 end); 
 tasks[2].Start; 

 tasks[3] := TTask.Create (procedure () 
   begin 
    tu dac kolejny fragment pętli
       obliczenia i wyprowadzenie wartości do zmiennej globalnej wynikiRdzeni[4]
 end); 
 tasks[3].Start; 

 TTask.WaitForAll(tasks); 
    tu dokonujemy obliczeń połaczenia wyników tablic 

a tu jest cały program

uses
  system.threading, math;

type
  podziałLiczby= array[0..9] of integer;
  zakresy= array[1..4] of record
                            dół, góra: int64
                          end;
  wynik= array[0..9] of record
                           ileCyfr: integer;
                           liczba: int64
                        end;

function podzielNaZakresy(a,b: int64): zakresy; //dzieli zakres pętli for dla rozłożenia na cztery rdzenie procesora
var
  i, iloraz: int64;
  wynik: zakresy;
begin
  iloraz:= (b-a+1)div 4;
  for i := 1 to 4 do
  begin
    wynik[i].dół:= (i-1)*iloraz+a;
    if i<4
    then
      wynik[i].góra:= (i-1)*iloraz+a+iloraz-1
    else
      wynik[i].góra:= b;
  end;
  result:=wynik
end;

function rozłóżLiczbę(i: int64):podziałLiczby; //liczy wystąpienia cyfr w liczbie
var
  k,c : integer;
  s: string;
  temp: podziałLiczby;
begin
  for k := 0 to 9 do
    temp[k]:= 0;
  s:=IntToStr(i);
  for k:= 0 to 9 do
  begin
    c:= StrToInt(s[k+1]);
    temp[c]:= temp[c]+1;
  end;
  result:= temp;
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  maks: wynik;
  wynikiRdzeni: array[1..4] of wynik;
  zakresyRdzeni: zakresy;
  tasks: array of ITask;
  i,k: int64;
  hours, minutes, seconds, millisec: word;
  start,koniec,timeDiff: tdatetime;
begin
  start:= now;
  for i:=0 to 9 do
    maks[i].ileCyfr:= 0;
  for i := 1 to 4 do
    for k := 0 to 9 do
      wynikiRdzeni[i,k].ileCyfr:=0;
  Setlength(tasks ,4);
  zakresyRdzeni:= podzielNaZakresy(1000000000,9999999999);

  tasks[0]:= TTask.Create (procedure ()
  var
    temp: podziałLiczby;
    i,k: int64;
  begin
    For i:= zakresyRdzeni[1].dół to zakresyRdzeni[1].góra do
    begin
      if sqr(round(sqrt(i))) = i
      then
      begin
        temp:= rozłóżLiczbę(i);
        for k:= 0 to 9 do
          if temp[k]> wynikiRdzeni[1,k].ileCyfr
          then
          begin
            wynikiRdzeni[1,k].ileCyfr:= temp[k];
            wynikiRdzeni[1,k].liczba:= i
          end;
      end;
    end
  end);
  tasks[0].Start;

  tasks[1]:= TTask.Create (procedure ()
  var
    temp: podziałLiczby;
    i,k: int64;
  begin
    For i:= zakresyRdzeni[2].dół to zakresyRdzeni[2].góra do
    begin
      if sqr(round(sqrt(i))) = i
      then
      begin
        temp:= rozłóżLiczbę(i);
        for k:= 0 to 9 do
          if temp[k]> wynikiRdzeni[2,k].ileCyfr
          then
          begin
            wynikiRdzeni[2,k].ileCyfr:= temp[k];
            wynikiRdzeni[2,k].liczba:= i
          end;
      end;
    end
  end);
  tasks[1].Start;

  tasks[2]:= TTask.Create (procedure ()
  var
    temp: podziałLiczby;
    i,k: int64;
  begin
    For i:= zakresyRdzeni[3].dół to zakresyRdzeni[3].góra do
    begin
      if sqr(round(sqrt(i))) = i
      then
      begin
        temp:= rozłóżLiczbę(i);
        for k:= 0 to 9 do
          if temp[k]> wynikiRdzeni[3,k].ileCyfr
          then
          begin
            wynikiRdzeni[3,k].ileCyfr:= temp[k];
            wynikiRdzeni[3,k].liczba:= i
          end;
      end;
    end
  end);
  tasks[2].Start;

  tasks[3]:= TTask.Create (procedure ()
  var
    temp: podziałLiczby;
    i,k: int64;
  begin
    For i:= zakresyRdzeni[4].dół to zakresyRdzeni[4].góra do
    begin
      if sqr(round(sqrt(i))) = i
      then
      begin
        temp:= rozłóżLiczbę(i);
        for k:= 0 to 9 do
          if temp[k]> wynikiRdzeni[4,k].ileCyfr
          then
          begin
            wynikiRdzeni[4,k].ileCyfr:= temp[k];
            wynikiRdzeni[4,k].liczba:= i
          end;
      end;
    end
  end);
  tasks[3].Start;

  TTask.WaitForAll(tasks);

  //zbieramy dane z wątków razem
  for i:= 0 to 9 do
    for k:=1 to 4 do
      if wynikiRdzeni[k,i].ileCyfr>maks[i].ileCyfr
      then
      begin
        maks[i].ileCyfr:= wynikiRdzeni[k,i].ileCyfr;
        maks[i].liczba:= wynikiRdzeni[k,i].liczba
      end;

  koniec:= now;
  timeDiff:= koniec - start;
  DecodeTime(timeDiff, hours, minutes, seconds, millisec);
  memo1.Lines.Add('godziny='+IntToStr(hours));
  memo1.Lines.Add('minuty='+IntToStr(minutes));
  memo1.Lines.Add('sekundy='+IntToStr(seconds));
  memo1.Lines.Add('milisekundy='+IntToStr(millisec));
  for k:= 0 to 9 do
    if maks[k].ileCyfr > 0
    then
      memo1.Lines.Add('najwięcej cyfr dla  '+IntToStr(k)+'  wynosi  '+
        IntToStr(maks[k].ileCyfr)+'  dla  '+IntToStr(maks[k].liczba))
end;
edytowany 1x, ostatnio: cerrato, 2019-07-26 09:12

Pozostało 580 znaków

2019-07-26 09:24
0

muszę powielać kod dla każdego wątku osobno.

Dlaczego musisz?

Edit: dodatkowo masz tutaj false sharing.


edytowany 2x, ostatnio: Patryk27, 2019-07-26 09:28
Bo nie znam składni jak to zapisać przy pomocy jednej procedury. Każdy wątek dostaje inny zakres wejściowy i zapisuje do innej zmiennej wyjściowej. Skróciłem część wspólną do funkcji ale nie wiem jak więcej skrócić. Jeśli ta procedura wątkowa może być zdefiniowana z parametrami wejściowymi i wyjściowymi to wtedy dałoby się te wątki wywołać w pętli czterokrotnej zamiast kopiować kod cztery razy, ale nie mogłem znaleźć dokumentacji, czy tak wolno robić. - MariuszJ 2019-07-26 09:26
@MariuszJ: na temat odpowiadaj w postach. - furious programming 2019-07-26 16:14

Pozostało 580 znaków

Odpowiedz
Liczba odpowiedzi na stronę

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

Robot: CCBot