Po przeczytaniu przepełnieniem stosu pytanie Using vectors for performance improvement in Haskell opisująca szybka w miejscu quicksort w Haskell, postawiłem sobie dwa cele:Szybkie sortowanie w Haskell
Realizacja tego samego algorytmu, z medianą trzech uniknąć złe występy na wstępnie posortowanych wektorach;
Tworzenie równoległej wersji.
Oto wynik (drobne elementy zostały pominięte dla uproszczenia):
import qualified Data.Vector.Unboxed.Mutable as MV
import qualified Data.Vector.Generic.Mutable as GM
type Vector = MV.IOVector Int
type Sort = Vector -> IO()
medianofthreepartition :: Vector -> Int -> IO Int
medianofthreepartition uv li = do
p1 <- MV.unsafeRead uv li
p2 <- MV.unsafeRead uv $ li `div` 2
p3 <- MV.unsafeRead uv 0
let p = median p1 p2 p3
GM.unstablePartition (< p) uv
vquicksort :: Sort
vquicksort uv = do
let li = MV.length uv - 1
j <- medianofthreepartition uv li
when (j > 1) (vquicksort (MV.unsafeSlice 0 j uv))
when (j + 1 < li) (vquicksort (MV.unsafeSlice (j+1) (li-j) uv))
vparquicksort :: Sort
vparquicksort uv = do
let li = MV.length uv - 1
j <- medianofthreepartition uv li
t1 <- tryfork (j > 1) (vparquicksort (MV.unsafeSlice 0 j uv))
t2 <- tryfork (j + 1 < li) (vparquicksort (MV.unsafeSlice (j+1) (li-j) uv))
wait t1
wait t2
tryfork :: Bool -> IO() -> IO (Maybe (MVar()))
tryfork False _ = return Nothing
tryfork True action = do
done <- newEmptyMVar :: IO (MVar())
_ <- forkFinally action (\_ -> putMVar done())
return $ Just done
wait :: Maybe (MVar()) -> IO()
wait Nothing = return()
wait (Just done) = swapMVar done()
median :: Int -> Int -> Int -> Int
median a b c
| a > b =
if b > c then b
else if a > c then c
else a
| otherwise =
if a > c then a
else if b > c then c
else b
dla wektorów z 1.000.000 elementów, uzyskać następujące wyniki:
"Number of threads: 4"
"**** Parallel ****"
"Testing sort with length: 1000000"
"Creating vector"
"Printing vector"
"Sorting random vector"
CPU time: 12.30 s
"Sorting ordered vector"
CPU time: 9.44 s
"**** Single thread ****"
"Testing sort with length: 1000000"
"Creating vector"
"Printing vector"
"Sorting random vector"
CPU time: 0.27 s
"Sorting ordered vector"
CPU time: 0.39 s
moje pytania to:
- Dlaczego są występy? Zmniejszając się o wstępnie posortowany wektor?
- Dlaczego korzystanie z forkIO i czterech wątków nie poprawia wydajności?
jestem o pójściu do łóżka, więc teraz nie ma analizy, tylko co wyskakuje. Kiedy przeglądasz każde wywołanie rekursywne, tworzysz wiele wątków, narzuty z harmonogramem wątku przytłacza rzeczywistą pracę do wykonania. Jeśli istnieje nawet synchronizacja między różnymi wątkami uzyskującymi dostęp do danej macierzy, to całkowicie zabiłoby to wydajność nawet przy mniejszej liczbie wątków. Jeśli chcesz przyspieszyć, rozwidlenie tylko dla pierwszych kilku wywołań rekursywnych nie ma więcej wątków działających, niż masz rdzenie. –
Aby uzyskać szybki paralelizm, należy użyć 'par', a nie' forkIO'. Więcej szczegółów znajduje się w pakiecie 'parallel' [tutaj] (http://hackage.haskell.org/package/parallel-3.2.0.3). –
@GabrielGonzalez czy 'par' działa dobrze z obliczeniami, które są" tylko "operacjami IO? Ponadto, czy konieczne jest zrozumienie modułu Control.Parallel.Strategies? – Simon