Es un trabajo de corte para parVector
pero esto funcionó para mí:
import qualified Data.Vector as V
import Control.Parallel.Strategies
import Control.Parallel
import Control.DeepSeq
ack :: Int -> Int -> Int
ack 0 n = n+1
ack m 0 = ack (m-1) 1
ack m n = ack (m-1) (ack m (n-1))
main = do
let vec = V.enumFromN 1 1000
let res = (V.map (ack 2) vec) `using` parVector
print res
parVector :: NFData a => Strategy (V.Vector a)
parVector vec = eval vec `seq` Done vec
where
chunkSize = 1
eval v
| vLen == 0 =()
| vLen <= chunkSize = rnf (v V.! 0) -- FIX this to handle chunks > 1
| otherwise = eval (V.take half v) `par` eval (V.drop half v)
where vLen = V.length v
half = vLen `div` 2
Y la ejecución de este código:
[[email protected] Test]$ ghc --make -O2 -threaded t.hs
... dumb warning ...
[[email protected] Test]$ time ./t +RTS -N1 >/dev/null
real 0m1.962s user 0m1.951s sys 0m0.009s
[[email protected] Test]$ time ./t +RTS -N2 >/dev/null
real 0m1.119s user 0m2.221s sys 0m0.005s
Cuando ejecuto el código con Integer
en lugar de Int
en el escriba la firma:
[[email protected] Test]$ time ./t +RTS -N2 >/dev/null
real 0m4.754s
user 0m9.435s
sys 0m0.028s
[[email protected] Test]$ time ./t +RTS -N1 >/dev/null
real 0m9.008s
user 0m8.952s
sys 0m0.029s
Rock!
EDIT: Y una solución que es un poco más cerca de su intento anterior es más limpio (que no utiliza las funciones de tres módulos separados) y funciona muy bien:
parVector :: NFData a => Strategy (V.Vector a)
parVector vec =
let vLen = V.length vec
half = vLen `div` 2
minChunk = 10
in if vLen > minChunk
then do
let v1 = V.unsafeSlice 0 half vec
v2 = V.unsafeSlice half (vLen - half) vec
parVector v1
parVector v2
return vec
else
evalChunk (vLen-1) >>
return vec
where
evalChunk 0 = rpar (rdeepseq (vec V.! 0)) >> return vec
evalChunk i = rpar (rdeepseq (vec V.! i)) >> evalChunk (i-1)
cosas que aprender de esta solución:
- utiliza la mónada
Eval
, que es estricta por lo que estamos seguros de provocar todo (en comparación a envolver las cosas en let
y recordar a utilizar patrones de explosión).
- Contrariamente a su ejecución propuesto que (a) no construye un nuevo vector, lo cual es costoso (b)
evalChunk
fuerzas de evaluación de cada elemento utilizando rpar
y rdeepseq
(no creo rpar vec
fuerzas de cualquiera de los elementos del vector) .
- Contrariamente a mi creencia,
slice
toma un índice y una longitud de inicio, no un índice de inicio y final. Oops!
- Todavía necesitamos importar
Control.DeepSeq (NFData)
, pero he enviado por correo electrónico la lista de bibliotecas para intentar solucionar ese problema.
El rendimiento parece similar a la primera solución parVector
en esta respuesta, por lo que no voy a publicar números.
Poniéndose un poco ansioso por que el DPH muestre algo de fruta, ¿verdad? –
Bueno, más o menos. Me gustaría intentar escribir código numérico en Haskell, y aún no entiendo qué debería usar para eso. – sastanin
No creo que su versión de parVector funcionaría: 'rseq' no evaluaría ninguno de los elementos (es solo WHNF) y' V.concat' es una operación O (n) innecesaria; estamos intentando forzar el cálculo de los elementos, no es necesario construir un nuevo vector. –