2

次の 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 はシーケンスや繰り返しでも高速に実行されるのでしょうか? この動作に関する参照はありますか?

ありがとう。

4

3 に答える 3

8

すべての書き込みで hFlush を呼び出すのは間違っているようです。

この単純な変更により、厳密なバイト文字列を使用するforM_replicateM_、明示的なsequenceおよびブロック バッファリングの代わりに、ランタイムが 16.2 秒から 0.3 秒に短縮されます。

{-# 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

書き込みを調整するバイト文字列サブシステムに依存して、遅延バイト文字列の 1 回の書き込みを使用する方がより慣用的ですが。

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.27 秒)

于 2012-11-13T15:17:06.850 に答える
6

Python コードについてはよくわかりませんが ( は何doIO()ですか?)、Haskell を改善する明らかな方法は のsequence_代わりにを使用sequenceすることなので、 の膨大なリストを作成する必要はありません()。この小さな変更により、私のマシンでは 6 ~ 7 倍速くなりました。

(その行を表現するより簡単な方法は、replicateM_ 1000000 (L.hPutStr stdout str >> hFlush stdout)です。)

システムコールの数はかなりのものかもしれません -- GHC の RTS はノンブロッキング I/O を行い、不要なselect呼び出しを行う可能性があります -- しかし、あなたの数字を見ると、この変更はそれを Python の範囲に入れるのに十分かもしれません。独自の。

于 2012-11-13T15:17:31.863 に答える
5

大きな問題は、

sequence (take 1000000 (repeat (L.hPutStr stdout str >> hFlush stdout)))

リストで実行されたアクションの結果を収集しIOます。結果を破棄すると、

sequence_ (take 1000000 (repeat (L.hPutStr stdout str >> hFlush stdout)))

はるかに高速になり、割り当てが少なくなります。

于 2012-11-13T15:21:11.513 に答える