Haskell - parMap有什么用?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Haskell - parMap有什么用?相关的知识,希望对你有一定的参考价值。

我做了一些测试:

import Control.Parallel.Strategies
import Data.Vector as V
import Data.Maybe

parMapVec :: (a -> b) -> Vector a -> Vector b
parMapVec f v = runEval $ evalTraversable rpar $ V.map f v

range :: Integer -> Integer -> Vector Integer
range x y
  | x == y = x `cons` empty
  | x < y  = x `cons` (range (x + 1) y)
  | x > y  = (range x (y + 1)) `snoc` y

fac :: Integer -> Integer
fac n
  | n < 2     = 1
  | otherwise = n * (fac $ n - 1)

main :: IO ()
main = do
  let result = runEval $ do
        let calc = parMapVec fac $ 80000 `range` 80007
        rseq calc
        return calc
  putStrLn $ show result

以及对main的以下修改,以确保我的parMapVector不是错误的:

main = do
  let result = runEval $ do
        let calc = parMap rpar fac [80000..80007]
        rseq calc
        return calc
  putStrLn $ show result

我用gch --make parVectorTest.hs -threaded -rtsopts编译并用./parVectorTest -s运行。

这是我用矢量版本发现的:

56,529,547,832 bytes allocated in the heap
10,647,896,984 bytes copied during GC
    7,281,792 bytes maximum residency (16608 sample(s))
    3,285,392 bytes maximum slop
            21 MB total memory in use (0 MB lost due to fragmentation)

                                    Tot time (elapsed)  Avg pause  Max pause
Gen  0     82708 colls,     0 par    0.828s   0.802s     0.0000s    0.0016s
Gen  1     16608 colls,     0 par   15.006s  14.991s     0.0009s    0.0084s

TASKS: 4 (1 bound, 3 peak workers (3 total), using -N1)

SPARKS: 8 (7 converted, 0 overflowed, 0 dud, 0 GC'd, 1 fizzled)

INIT    time    0.001s  (  0.001s elapsed)
MUT     time    5.368s  (  5.369s elapsed)
GC      time   15.834s  ( 15.793s elapsed)
EXIT    time    0.001s  (  0.000s elapsed)
Total   time   21.206s  ( 21.163s elapsed)

Alloc rate    10,530,987,847 bytes per MUT second

Productivity  25.3% of total user, 25.4% of total elapsed

gc_alloc_block_sync: 0
whitehole_spin: 0
gen[0].sync: 0
gen[1].sync: 0

所以这很好,除了我看到我的系统监视器上的进程执行,并且一次只有一个核心正在运行。每当打印出一个结果时,该过程将切换到不同的核心。所以我认为我的parMapVec功能有问题。但是我做了同样的事情,除了带有列表的版本:

56,529,535,488 bytes allocated in the heap
12,483,967,024 bytes copied during GC
    6,246,872 bytes maximum residency (19843 sample(s))
    2,919,544 bytes maximum slop
            20 MB total memory in use (0 MB lost due to fragmentation)

                                    Tot time (elapsed)  Avg pause  Max pause
Gen  0     79459 colls,     0 par    0.818s   0.786s     0.0000s    0.0009s
Gen  1     19843 colls,     0 par   17.725s  17.709s     0.0009s    0.0087s

TASKS: 4 (1 bound, 3 peak workers (3 total), using -N1)

SPARKS: 16 (14 converted, 0 overflowed, 0 dud, 1 GC'd, 1 fizzled)

INIT    time    0.001s  (  0.001s elapsed)
MUT     time    5.394s  (  5.400s elapsed)
GC      time   18.543s  ( 18.495s elapsed)
EXIT    time    0.000s  (  0.000s elapsed)
Total   time   23.940s  ( 23.896s elapsed)

Alloc rate    10,479,915,927 bytes per MUT second

Productivity  22.5% of total user, 22.6% of total elapsed

gc_alloc_block_sync: 0
whitehole_spin: 0
gen[0].sync: 0
gen[1].sync: 0

所以有更多垃圾收集,这是有道理的。而且还有更多的火花,我不知道如何解释。当我看到它在我的系统监视器上执行时,该程序表现出相同的行为。

我也用./parVector -s -C0.01进行了两次测试,因为this question的答案得到了基本相同的结果。我在联想Ideapad,8核,运行Ubuntu Linux 17.04。在测试时,我打开的唯一应用程序是VS Code和我的系统监视器,尽管其他进程占用了很小一部分处理能力。处理器是否必须完全闲置以获得火花?

答案

默认情况下,GHC使用单个OS线程运行所有程序,即使启用了-threaded也是如此。请注意输出中的文本“using -N1” - 它表示程序正在使用1个物理线程运行。

简而言之:通过例如+RTS -N8到你的节目。有关此标志的文档,请参阅here


从广义上讲,这是由于并行性和并发性之间的区别。 Here are some SO试图解释其中的问题。差异可归纳为:

  • 并行性:一个任务细分为类似的块,可以在某个时间点在不同的内核/ CPU上同时运行;为了提高速度
  • 并发:几个任务在概念上独立执行,使得它们的执行时间重叠,无论是在同一个线程上通过时间切片还是在单独的内核/ CPU上;通常更有效地利用共享资源

但是,这些定义有点争议;有时两者具有相反的含义,有时它们可​​以互换使用。但是,为了理解这个问题(为什么你必须传递-threaded之外的另一个标志来使'并行'程序实际并行运行)我相信它们是有用的定义。

以上是关于Haskell - parMap有什么用?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 PHP 博客上突出显示 Haskell 语法

“为什么卡尔达诺用Haskell?难道IOHK将永远运行Cardano项目?

为啥这个 Haskell 代码可以成功地处理无限列表?

haskell 生成 FFI 导出包装代码

从 Rust 调用动态链接的 Haskell 代码

Haskell代码编程