Sortowanie tablicy w wątkach

0

Witam wszystkich,

mam problem związany z zaprojektowaniem aplikacji wielowątkowej. W uproszczeniu: chcę posortować dużą tablicę (długości N~10^6) obiektów w M wątkach (np. poprzez scalanie). Każdy z wątków operuje na swoim zakresie tablicy, zakresy nie zachodzą na siebie. Po zakończeniu działania wątków usługowych, uzyskuję tablicę częściowo posortowaną, którą już tylko scalam w wątku głównym.

Najprościej byłoby mi operować na 1 wspólnej tablicy, tym bardziej, że i tak taka tablica będzie mi potrzebna w innej części programu. Moje pytanie dotyczy wydajności takiego rozwiązania i ewentualnych błędów.

  1. Czy każdy z wątków może otrzymać kopię całej tablicy w pamięci lokalnej? Obawiam się, że to znacznie zwiększyłoby zużycie pamięci.
  2. Czy w wyniku synchronizacji (happens before) mogę utracić zmiany wprowadzone w części wątków? Wytłumaczę dokładniej:
//watek glowny
volatile MyObject[] tablica = new MyObject[N];
//....inicjalizacja tablicy
Worker[] watki = new Worker[M];
//...przydzielanie zakresow
for(int i=0;i<M;++i)watki[i].start();
for(int i=0;i<M;++i)watki[i].join();

//poniżej synchronizacja przez volatile
tablica = tablica;
//tablica widziana w wątku głównym będzie zawierała wszystkie zmiany
//czy tylko zmiany z ostatniego wątku usługowego (poprzednie zostaną nadpisane?)

//.....Watek uslugowy
private class Worker extends Thread{
public void run(){
//.....sortowanie na zakresie tablicy: tablica
}

Podsumowując, w przypadku kiedy każdy wątek posiada swoją kopię zmiennej w pamięci lokalnej, po czym następuje synchronizacja w zewnętrznym wątku, to która wartość jest widziana? I jak to się ma do zmian wprowadzonych na rozłącznych zakresach jednej tablicy?

Proszę o podpowiedź, bo utknąłem i nie wiem czy powinienem szukać innych rozwiązań.

0

Sortowanie przez scalanie odbywa się rekurencyjnie, czyli liczysz się z utworzeniem, w najlepszym wypadku dla 106 elementów 106/2 wątków, czy chodzi o rekursję problemu i wybranie sobie jakiejś rozsądnej liczby wątków (np. kilku)?
Jeżeli będziesz kopiował tablicę do każdego wątku to, jak najbardziej zwiększy to zużycie pamięci, a jeżeli wątki będą sortować po kawałku zsynchronizowanej tablicy to jaki jest cel? Monitor i tak zezwoli na działanie jednego wątku na raz, czyli wynik będzie jeszcze gorszy, bo przeskakiwanie między wątkami też zużywa zasoby.

0

A po co chcesz cokolwiek synchronizować?
Niech każdy z wątków posortuje swoją część tablicy, a na końcu sortowanie możesz dokończyć jednym wątkiem.

Moim zdaniem jednak lepiej zastosować mechanizm fork and join z Javy 7+ (http://docs.oracle.com/javase/tutorial/essential/concurrency/forkjoin.html).
Jeżeli nie wiesz jak, to zobacz implementację metody parallelSort:
http://docs.oracle.com/javase/8/docs/api/java/util/Arrays.html#parallelSort-int:A-

0

@Atlas500 wątków będzie tyle ile dostępnych rdzeni procesora. Na niższym poziomie posortowanie dowolnym algorytmem w czasie O((N/M)log2(N/M)) w głównym wątku scalanie O(Nlog2M) w sumie (N/M)(log2(NM(M-1)) < Nlog2N zawsze to jakiś zysk. Tym bardziej, że wątki będą odpalone już wcześniej, a sortowanie jest tylko jedną (ostatnią) z operacji, które wykonuje na tej tablicy.

__krzysiek85 napisał(a):

A po co chcesz cokolwiek synchronizować?
Niech każdy z wątków posortuje swoją część tablicy, a na końcu sortowanie możesz dokończyć jednym wątkiem.

Moim zdaniem jednak lepiej zastosować mechanizm fork and join z Javy 7+ (http://docs.oracle.com/javase/tutorial/essential/concurrency/forkjoin.html).
Jeżeli nie wiesz jak, to zobacz implementację metody parallelSort:
http://docs.oracle.com/javase/8/docs/api/java/util/Arrays.html#parallelSort-int:A-

Nie miałem jeszcze okazji stosować tych nowych rozwiązań, a chętnie spróbowałbym: ) Niestety, w sieci na której będę odpalał symulację jest zainstalowana wersja 6, tak że muszę się ograniczyć.

Masz rację synchronizacja w trakcie pracy wątków usługowych nie jest potrzebna (nawet jest niepożądana ze względu na wydajność). Moje pytanie dotyczy synchronizacji po zakończeniu działania wątków. Dokładnie o sensowność memory barrier za pomocą zmiennej volatile. Oczywiście sama referencja do tablicy nie musi być volatile można wykorzystać w tym celu dodatkową flagę. O ile rozumiem działanie zasady happens-before w przypadku prymitywów, to nie mam pewności jak to się ma do tablic. W każdym wątku zostaje zmodyfikowany (rozłączny) zakres tablicy, pozostałe indeksy są niezmienione. Wątki mają prawo posiadać swoją lokalną kopię tablicy. W efekcie może się zdarzyć taka sytuacja, że w pamięci istnieje M różnych wersji tablicy. W jaki sposób są one scalane przy napotkaniu memory barrier? Zwykłe nadpisanie najnowszą wersją prowadzi do sytuacji, w której zmiany wykonane w innych wątkach są tracone (bo tylko jedna z nich jest najnowsza). Być może jednak mechanizm jest inteligentniejszy? Tego nie wiem.
Nie wiem też czy wątki uzyskują swoje kopie lokalne tablicy. Dla dużych tablic byłoby to duże obciążenie pamięci, więc może kompilator JIT nie wprowadza takiej optymalizacji?

Oczywiście testowałem kod na moim komputerze. Uzyskałem prawidłowe wyniki z użyciem volatile i bez. Z tym, że z referencją volatile do tablicy czas wykonania był znacznie dłuższy. Nadal jednak nie rozumiem zasady i nie mogę mieć pewności czy kod zadziała bezbłędnie na innym sprzęcie.

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