`par`是否会创建另一个线程?

时间:2012-05-23 17:27:39

标签: multithreading haskell parallel-processing multicore

我对par的理解是它将在另一个核心中创建一个线程来执行。

但是我没有通过以下测试代码证明这种理解,因为显示的结果似乎只有一个线程正在运行。

你能帮我弄清楚这里有什么问题吗?

import Control.Monad
import Control.Parallel
import Control.Concurrent
import System.IO.Unsafe

fib :: Int -> Int 
fib 0 = 1 
fib 1 = 1 
fib n = (fib (n-1)) + (fib (n - 2))

test :: String -> [Int] -> IO () 
test _ [] = return () 
test name (a:xs) = do 
    tid <- myThreadId 
    print $ (show tid) ++ "==>" ++ (show a) ++ "==>" ++ (show $ fib a) ++ "==>" ++ name
    x `par` y 
  where x = test "2" xs
        y = test "3" (tail xs)

main = test "1" [10..35]

编译:

ghc --make -threaded -rtsopts test-par.hs
time ./test-par +RTS -N2

结果

"ThreadId 3==>10==>89==>1"
"ThreadId 3==>12==>233==>3"
"ThreadId 3==>14==>610==>3"
"ThreadId 3==>16==>1597==>3"
"ThreadId 3==>18==>4181==>3"
"ThreadId 3==>20==>10946==>3"
"ThreadId 3==>22==>28657==>3"
"ThreadId 3==>24==>75025==>3"
"ThreadId 3==>26==>196418==>3"
"ThreadId 3==>28==>514229==>3"
"ThreadId 3==>30==>1346269==>3"
"ThreadId 3==>32==>3524578==>3"
"ThreadId 3==>34==>9227465==>3"

real    0m1.131s
user    0m0.668s
sys 0m0.492s

我有多少核心?

cat /proc/cpuinfo | grep processor | wc -l
2

-------------------------------- update

我认为this paper by Simon Marlow是这类新手问题的一个很好的参考。

1 个答案:

答案 0 :(得分:13)

不,par不保证创建另一个线程。

它在运行时中注册 spark 可能导致计算在不同的线程中执行,具体取决于计算机的动态工作负载。

创建一个spark是非常便宜的,所以你可以比线程创建更多(1000x),运行时只会尝试让你的所有核心都忙。

在您的情况下,您的x计算被注册为火花,然后立即被丢弃(您再也不会参考它)。所以垃圾收集器可以删除它。

要并行化递归函数,您通常只需要使用par一定深度。

示例 - 具有截止深度的递归函数:

import Control.Parallel
import Control.Monad
import Text.Printf

cutoff = 35

fib' :: Int -> Integer
fib' 0 = 0
fib' 1 = 1
fib' n = fib' (n-1) + fib' (n-2)

fib :: Int -> Integer
fib n | n < cutoff = fib' n
      | otherwise  = r `par` (l `pseq` l + r)
 where
    l = fib (n-1)
    r = fib (n-2)

main = forM_ [0..45] $ \i ->
            printf "n=%d => %d\n" i (fib i)

运行方式:

$ ghc -O2 -threaded --make A.hs -rtsopts -fforce-recomp
$ ./A +RTS -N16 -s

产生并行工作量1248.9%,超过顺序(即12.48x):

                                   Tot time (elapsed)  Avg pause  Max pause
  Gen  0      6264 colls,  6263 par    5.23s    0.41s     0.0001s    0.0109s
  Gen  1         1 colls,     1 par    0.00s    0.00s     0.0002s    0.0002s

  Parallel GC work balance: 7.09 (1797194 / 253525, ideal 16)

                        MUT time (elapsed)       GC time  (elapsed)
  Task  0 (worker) :    7.56s    (  9.36s)       0.92s    (  0.89s)
  Task  1 (worker) :    0.12s    ( 10.21s)       0.02s    (  0.05s)
  Task  2 (bound)  :    8.39s    (  9.51s)       0.70s    (  0.74s)
  Task  3 (worker) :    0.00s    (  0.00s)       0.00s    (  0.00s)
  Task  4 (worker) :    7.17s    (  9.60s)       0.53s    (  0.66s)
  Task  5 (worker) :    7.28s    (  9.55s)       0.56s    (  0.71s)
  Task  6 (worker) :    7.48s    (  9.52s)       0.56s    (  0.74s)
  Task  7 (worker) :    7.11s    (  9.54s)       0.66s    (  0.72s)
  Task  8 (worker) :    7.41s    (  9.62s)       0.70s    (  0.64s)
  Task  9 (worker) :    7.69s    (  9.48s)       0.66s    (  0.78s)
  Task 10 (worker) :    7.56s    (  9.51s)       0.56s    (  0.75s)
  Task 11 (worker) :    7.69s    (  9.42s)       0.86s    (  0.84s)
  Task 12 (worker) :    7.42s    (  9.40s)       0.92s    (  0.86s)
  Task 13 (worker) :    7.28s    (  9.39s)       0.91s    (  0.86s)
  Task 14 (worker) :    7.44s    (  9.38s)       0.91s    (  0.87s)
  Task 15 (worker) :    7.25s    (  9.33s)       1.11s    (  0.93s)
  Task 16 (worker) :    7.94s    (  9.33s)       0.97s    (  0.93s)
  Task 17 (worker) :    7.59s    (  9.37s)       1.06s    (  0.88s)

  SPARKS: 597 (446 converted, 0 dud, 1 GC'd, 150 fizzled)

  Productivity  96.1% of total user, 1245.3% of total elapsed

我们创造了597个火花,其中446个被转换为线程。

如果您明确想要手动创建和沟通线程,可以通过forkIOMVars来完成。