我编写了一个函数,通过滤波器将文件从 48kHz 上采样到 192kHz:
upsample :: Coefficients -> FilePath -> IO ()
它获取滤波器系数、文件路径(必须进行上采样)并将结果写入新文件。
我必须对许多文件进行上采样,因此我编写了一个函数,使用 Control.Concurrent.Async
中的 forConcurrently_
并行对整个目录进行上采样:
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.wav
和 file2.wav
对目录进行上采样需要 66.250 秒。
我做错了什么?
我试图让这篇文章保持简洁,所以如果您需要有关某些功能的更多详细信息,请询问我。
最佳答案
这里有几种可能性。首先,确保自己 100% 确定您确实正在使用 +RTS -N2 -RTS
运行程序。 。我无法告诉你我对并行程序进行了多少次基准测试并编写了:
stack exec myprogram +RTS -N2 -RTS
代替:
stack exec myprogram -- +RTS -N2 -RTS
让自己陷入无可救药的困惑。 (第一个版本在两个处理器上运行堆栈可执行文件,但在一个处理器上运行目标可执行文件!)也许添加 print $ getNumCapabilities
在您的 main
的开头程序以确定。
确认您在两个处理器上运行后,下一个最可能的问题是您的实现没有在恒定空间中运行并且正在炸毁堆。这是我用来尝试复制您的问题的一个简单的测试程序。 (您可以随意使用我很棒的上采样过滤器!)
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 秒,两个处理器上的两个副本的运行时间为 4.0 秒(+RTS -N2
) .
确保您在编译时启用了优化,分析您的程序,并确保它在恒定(或至少合理)的堆空间中运行。上面的程序在恒定的 100k 字节堆中运行。使用严格的 ByteString
的类似版本用于阅读和懒惰ByteString
用于写入将整个文件读取到内存中,但堆几乎立即在不到一秒的时间内增长到 70megs(文件的大小),然后在处理文件时保持不变。
无论您的过滤器有多复杂,如果您的程序的堆增长到千兆字节,那么实现就会被破坏,并且您需要在担心性能、并行或其他方面之前修复它。
关于haskell - 并发性能比顺序性能差,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43569721/