2015-04-23 18 views
11

Niedawno odpowiedziałem na pytanie dotyczące optymalizacji prawdopodobnej, równoległej metody generowania każdej permutacji dowolnych numerów bazowych. Zamieściłem odpowiedź podobną do tej, słaba realizacja listy zablokowanych kod parallelized, a ktoś prawie natychmiast zwróciłem na to uwagę:Parallel Framework i unikanie fałszywego współdzielenia

To jest dość dużo gwarancją daje fałszywy podział i będzie prawdopodobnie wiele razy wolniej. (Kredyt do gjvdkamp)

i mieli rację, to było śmierć powolny. Powiedział, że badałem temat i znalazłem jakieś interesting material and suggestions do walki z nim. Jeśli rozumiem to poprawnie, gdy wątki uzyskują dostęp do sąsiedniej pamięci (na przykład do tablicy, która prawdopodobnie jest zgodna z ConcurrentStack), prawdopodobne jest fałszywe udostępnianie.


dla kodu poniżej linii poziomej, o Bytes jest:

struct Bytes { 
    public byte A; public byte B; public byte C; public byte D; 
    public byte E; public byte F; public byte G; public byte H; 
} 

Dla własnego testów, chciałem dostać równoległą wersję tego biegania i być prawdziwie szybciej, więc stworzyłem prosty przykład oparty na oryginalnym kodzie. 6 jako limits[0] był leniwy wybór z mojej strony - mój komputer ma 6 rdzeni.

pojedynczy wątek blokŚredni czas pracy: 10s0059ms

var data = new List<Bytes>(); 
    var limits = new byte[] { 6, 16, 16, 16, 32, 8, 8, 8 }; 

    for (byte a = 0; a < limits[0]; a++) 
    for (byte b = 0; b < limits[1]; b++) 
    for (byte c = 0; c < limits[2]; c++) 
    for (byte d = 0; d < limits[3]; d++) 
    for (byte e = 0; e < limits[4]; e++) 
    for (byte f = 0; f < limits[5]; f++) 
    for (byte g = 0; g < limits[6]; g++) 
    for (byte h = 0; h < limits[7]; h++) 
    data.Add(new Bytes { 
     A = a, B = b, C = c, D = d, 
     E = e, F = f, G = g, H = h 
    }); 

parallelized, biedny realizacjiRun Time AVG: 81s729ms, ~ 8700 twierdzenia

var data = new ConcurrentStack<Bytes>(); 
    var limits = new byte[] { 6, 16, 16, 16, 32, 8, 8, 8 }; 

    Parallel.For(0, limits[0], (a) => { 
    for (byte b = 0; b < limits[1]; b++) 
    for (byte c = 0; c < limits[2]; c++) 
    for (byte d = 0; d < limits[3]; d++) 
    for (byte e = 0; e < limits[4]; e++) 
    for (byte f = 0; f < limits[5]; f++) 
    for (byte g = 0; g < limits[6]; g++) 
    for (byte h = 0; h < limits[7]; h++) 
     data.Push(new Bytes { 
     A = (byte)a,B = b,C = c,D = d, 
     E = e,F = f,G = g,H = h 
     }); 
    }); 

parallelized, ?? RealizacjaRun czas avg: 5s833ms, 92 twierdzenia

var data = new ConcurrentStack<List<Bytes>>(); 
    var limits = new byte[] { 6, 16, 16, 16, 32, 8, 8, 8 }; 

    Parallel.For (0, limits[0],() => new List<Bytes>(), 
    (a, loop, localList) => { 
     for (byte b = 0; b < limits[1]; b++) 
     for (byte c = 0; c < limits[2]; c++) 
     for (byte d = 0; d < limits[3]; d++) 
     for (byte e = 0; e < limits[4]; e++) 
     for (byte f = 0; f < limits[5]; f++) 
     for (byte g = 0; g < limits[6]; g++) 
     for (byte h = 0; h < limits[7]; h++) 
     localList.Add(new Bytes { 
      A = (byte)a, B = b, C = c, D = d, 
      E = e, F = f, G = g, H = h 
     }); 
     return localList; 
    }, x => { 
    data.Push(x); 
    }); 

Jestem zadowolony, że dostał implementację, która jest szybsza niż jedną wersję gwintowany. Oczekiwałem wyniku bliższego około 10 s/6, czyli około 1,6 s, ale to prawdopodobnie naiwne oczekiwanie.

Moje pytanie brzmi: dla równoległej implementacji, która jest rzeczywiście szybsza od wersji jednowątkowej, czy istnieją dodatkowe optymalizacje, które można zastosować do operacji? Zastanawiam się nad optymalizacją związaną z równoległością, a nie ulepszeniami algorytmu używanego do obliczania wartości. Konkretnie:

  • wiem o optymalizacji przechowywania i zaludniać jako struct zamiast byte[], ale nie jest to związane z parallelization (lub nie?)
  • wiem, że pożądana wartość może być leniwy oceniana z dodatek sumujący, ale taki sam jak optymalizacja struct.
+0

Czy jesteś lepiej opublikowania tego programistów w [] (http://programmers.stackexchange.com/)? Lepiej jednak zrobić 1 a Golf [wyzwanie] (http://codegolf.stackexchange.com/) – lloyd

+1

@lloydm jaki jest problem z tym pytaniem w stackoverflow? To świetnie, że jest tu przynajmniej kilka interesujących, trudnych pytań, a nie tylko milion komunikatów o błędach lub problemów z składnią. – Prokurors

+0

@Prokurory Nie ma wątpliwości, że to interesujące i wymagające. Dowiedziałem się o fałszywym udostępnianiu. Po ponownym przeczytaniu poprawnych pytań zgadzam się, że zaznacza pola jako prawidłowe pytanie. – lloyd

Odpowiedz

1

Po pierwsze, moje początkowe założenia dotyczące Parallel.For() i Parallel.ForEach() myliłem.

Biedny realizacja równoległy najprawdopodobniej ma 6 wątki wszystkie próby zapisu do jednego CouncurrentStack() naraz. Dobra implementacja ze względu na loci wątków (wyjaśnione poniżej) uzyskuje dostęp do wspólnej zmiennej tylko raz na zadanie, prawie eliminując wszelkie spory.

Podczas korzystania Parallel.For() i Parallel.ForEach(), ty nie po prostu w linii zastąpić for lub foreach pętlę z nimi. To nie znaczy, że nie może to być ślepa poprawa, ale bez zbadania problemu i oprzyrządowania go, używanie go rzuca wielowątkowość na problem, ponieważ może to przyspieszyć.

** Parallel.For() i Parallel.ForEach() ma przeciążeń, które pozwalają stworzyć stan lokalnego dla Task ostatecznie stworzyć i uruchomić wyrażenie przed i po wykonaniu każdej iteracji za.

Jeśli masz operacja parallelize z Parallel.For() lub Parallel.ForEach(), prawdopodobnie to dobry pomysł, aby korzystać z tej przeciążeniem:

public static ParallelLoopResult For<TLocal>(
    int fromInclusive, 
    int toExclusive, 
    Func<TLocal> localInit, 
    Func<int, ParallelLoopState, TLocal, TLocal> body, 
    Action<TLocal> localFinally 
) 

Na przykład wywołanie For() podsumować wszystkie liczby całkowite od 1 do 100,

var total = 0; 

Parallel.For(0, 101,() => 0, // <-- localInit 
(i, state, localTotal) => { // <-- body 
    localTotal += i; 
    return localTotal; 
}, localTotal => { <-- localFinally 
    Interlocked.Add(ref total, localTotal); 
}); 

Console.WriteLine(total); 

localInit powinna być lambda, która inicjalizuje lokalny typ stanu, który jest przekazywany do lambdas body i localFinally. Proszę zauważyć, że nie zalecam implementacji sumy od 1 do 100 przy użyciu zrównoleglania, ale wystarczy prosty przykład, aby ten przykład był krótki.

Powiązane problemy