更糟糕的是与顺序相关的并发性能

时间:2017-04-23 10:03:20

标签: haskell concurrency

我已经编写了一个函数,通过滤波器将文件从48kHz上采样到192kHz:

upsample :: Coefficients -> FilePath -> IO ()

它采用滤波器系数,文件的路径(必须是上采样)并将结果写入新文件。

我必须对许多文件进行上传,因此我使用forConcurrently_中的Control.Concurrent.Async编写了一个并行上传完整目录的函数:

upsampleDirectory :: Directory -> FilePath -> IO ()
upsampleDirectory dir coefPath = do
  files <- getAllFilesFromDirectory dir
  coefs <- loadCoefficients coefPath
  forConcurrently_ files $ upsample coefs

我使用-threaded选项进行编译并使用+RTS -N2运行。我看到的是,顺序上采样2个文件比并行对两个文件进行上采样要快。

上采样file1.wav需要18.863秒。 上采样file2.wav需要18.707秒。 使用file1.wavfile2.wav对目录进行上采样需要66.250秒。

我做错了什么?

我试图保持这篇文章的简洁,所以请问我是否需要有关某些功能的更多详细信息。

1 个答案:

答案 0 :(得分:2)

这里有几种可能性。首先,让自己100%确定您使用+RTS -N2 -RTS实际运行程序。我无法告诉你我已经多少次对并行程序进行基准测试并写了:

stack exec myprogram +RTS -N2 -RTS

取代:

stack exec myprogram -- +RTS -N2 -RTS

让我自己无可救药地困惑。 (第一个版本在两个处理器上运行堆栈可执行文件但在一个处理器上运行目标可执行文件!)可能在print $ getNumCapabilities程序的开头添加main以确保。< / p>

确认您在两个处理器上运行后,下一个最可能的问题是您的实现没有在恒定空间中运行并且正在炸毁堆。这是我用来尝试复制问题的简单测试程序。 (随意使用我真棒的上采样过滤器!)

module Main where

import Control.Concurrent.Async
import System.Environment
import qualified Data.ByteString as B

upsample :: FilePath -> IO ()
upsample fp = do c <- B.readFile fp
                 let c' = B.pack $ concatMap (replicate 4) $ B.unpack c
                 B.writeFile (fp ++ ".out") c'

upsampleFiles :: [FilePath] -> IO ()
upsampleFiles files = do
  forConcurrently_ files $ upsample

main :: IO ()
main = upsampleFiles =<< getArgs   -- sample all file on command line

当我在一个70meg测试文件上运行它时,它在14秒内运行。当我在两个副本上并行运行它时,它运行了一分多钟才开始像疯了一样交换,我不得不杀死它。切换到:

import qualified Data.ByteString.Lazy as B

单个文件在3.7秒内运行,在单个处理器上运行两个副本7.8秒,在两个+RTS -N2处理器上运行两个副本4.0秒。

确保您正在使用优化进行编译,对程序进行概要分析,并确保它在一个恒定(或至少是合理的)堆空间中运行。上面的程序以100k字节的常量堆运行。使用严格ByteString进行读取和使用惰性ByteString进行写入的类似版本将整个文件读入内存,但堆几乎立即增长到70megs(文件大小)的一小部分一秒钟,然后在处理文件时保持不变。

无论你的过滤器有多复杂,如果你的程序增加了千兆字节的堆,实现就会被破坏,你需要在担心性能,并行或其他方面之前修复它。