我发现以下Haskell代码使用100%CPU,并且在我的Linux服务器上完成大约需要14秒。
{-# LANGUAGE OverloadedStrings #-}
module Main where
import qualified Data.ByteString.Lazy.Char8 as L
import System.IO
str = L.pack "FugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFuga\n"
main = do
hSetBuffering stdout (BlockBuffering (Just 1000))
sequence (take 1000000 (repeat (L.hPutStr stdout str >> hFlush stdout)))
return ()
另一方面,非常相似的Python代码在大约3秒内完成相同的任务。
import sys
str = "FugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFuga\n"
def main():
for i in xrange(0, 1000000):
print str,
sys.stdout.flush()
# doIO()
main()
通过使用strace,我发现每次在Haskell版本中调用hFlush时都会调用select。另一方面,在Python版本中不调用select。我想这是Haskell版本很慢的原因之一。
有没有办法提高Haskell版本的性能?
我已经尝试省略hFlush,它肯定会大大降低CPU使用率。但是这种解决方案并不令人满意,因为它没有冲洗。
感谢。
EDITED
非常感谢你的帮助!通过更改序列并重复到replicateM_,运行时间从14秒减少到3.8秒。
但现在我有另一个问题。我问了上面的问题,因为当我从上面的程序中删除hFlush时,它运行得很快,尽管它使用序列重复I / O并重复。
为什么只有序列和hFlush的组合使它变慢?
为了确认我的新问题,我按照以下方式更改了程序以进行性能分析。
{-# LANGUAGE OverloadedStrings #-}
module Main where
import qualified Data.ByteString.Char8 as S
import System.IO
import Control.Monad
str = S.pack "FugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFuga\n"
doIO = S.hPutStr stdout str >> hFlush stdout
doIO' = S.hPutStr stdout str >> hFlush stdout
doIOWithoutFlush = S.hPutStr stdout str
main = do
hSetBuffering stdout (BlockBuffering (Just 1000))
sequence (take 1000000 (repeat doIO))
replicateM_ 1000000 doIO'
sequence (take 1000000 (repeat doIOWithoutFlush))
return ()
通过编译和运行如下:
$ ghc -O2 -prof -fprof-auto Fuga.hs
$ ./Fuga +RTS -p -RTS > /dev/null
我得到了以下结果。
COST CENTRE MODULE %time %alloc
doIO Main 74.7 35.8
doIO' Main 21.4 35.8
doIOWithoutFlush Main 2.6 21.4
main Main 1.3 6.9
doIO和doIO之间的区别是什么?谁做同样的任务?为什么IOWithoutFlush甚至按顺序快速运行并重复?有没有关于这种行为的参考?
感谢。
答案 0 :(得分:8)
每次写入时调用hFlush似乎都是错误的。
这个简单的更改,使用严格的字节串,forM_
或replicateM_
而不是显式sequence
,以及块缓冲,将运行时间从16.2s减少到0.3s
{-# LANGUAGE OverloadedStrings #-}
module Main where
import qualified Data.ByteString.Char8 as S
import Control.Monad
import System.IO
str = S.pack "FugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFuga\n"
main = replicateM_ 1000000 $ S.putStr str
虽然更惯用的是使用一个惰性字节串的单次写入,依靠bytestring子系统来协调写入。
import qualified Data.ByteString.Char8 as S
import qualified Data.ByteString.Lazy.Char8 as L
import Control.Monad
import System.IO
str :: S.ByteString
str = S.pack "FugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFuga\n"
main = L.putStr $ L.fromChunks (replicate 1000000 str)
性能略有改善(0.27s)
答案 1 :(得分:6)
我不确定Python代码(什么是doIO()
?),但改进Haskell的一个显而易见的方法是使用sequence_
代替sequence
,所以它没有'需要建立庞大的()
列表。这个微小的变化使我的机器上的速度提高了6-7倍。
(表达该行的更简单方法是replicateM_ 1000000 (L.hPutStr stdout str >> hFlush stdout)
。)
可能是系统调用的数量很大 - GHC的RTS确实进行了非阻塞I / O,并且可能会进行不必要的select
调用 - 但是根据您的数字,这个变化可能就足够了将它自己带入Python范围。
答案 2 :(得分:5)
最大的问题是
sequence (take 1000000 (repeat (L.hPutStr stdout str >> hFlush stdout)))
收集IO
- 在列表中执行的操作的结果。如果您丢弃结果,
sequence_ (take 1000000 (repeat (L.hPutStr stdout str >> hFlush stdout)))
它会更快,分配更少。