次の 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 が呼び出されることがわかりました。一方、select は Python 版では呼び出されません。これが Haskell 版が遅い理由の 1 つだと思います。
Haskell バージョンのパフォーマンスを改善する方法はありますか?
私はすでに hFlush を省略しようとしましたが、確かに CPU 使用率が大幅に減少しました。ただし、このソリューションはフラッシュしないため、満足のいくものではありません。
ありがとう。
編集済み
大変お世話になりました!シーケンスとリピートをレプリケートM_に変更することで、実行時間が 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' の違いは何ですか? また、なぜ doIOWithoutFlush はシーケンスや繰り返しでも高速に実行されるのでしょうか? この動作に関する参照はありますか?
ありがとう。