7

改訂された要約

了解しました。システムコールは確かにGCに関連しているようです。根本的な問題は、GCが頻繁に発生していることです。これは、プロファイリングでわかるように、splitWhenとpackの使用に関連しているようです。

splitWhenの実装は、チャンクのバッファーを構築するときに、各チャンクを遅延テキストから厳密なテキストに変換し、それらすべてを連結します。それはたくさん割り当てることになります。

packは、あるタイプから別のタイプに変換するため、割り当てる必要があり、それは私の内部ループにあるので、それも理にかなっています。

元の問題

私はhaskell列挙子ベースのIOでいくつかの驚くべきsyscallアクティビティに出くわしました。誰かがそれに光を当てることができることを願っています。

私はかつて数ヶ月間書いたクイックperlスクリプトのhaskellバージョンをオンとオフでいじっています。スクリプトは各行からjsonを読み込み、特定のフィールドが存在する場合はそれを出力します。

これがperlバージョンと私がそれをどのように実行しているかです。

cat ~/sample_input | perl -lpe '($_) = grep(/type/, split(/,/))' > /dev/null

これがhaskellバージョンです(perlバージョンと同様に呼び出されます)。

{-# LANGUAGE OverloadedStrings #-}
import qualified Data.Enumerator as E
import qualified Data.Enumerator.Internal as EI
import qualified Data.Enumerator.Text as ET
import qualified Data.Enumerator.List as EL
import qualified Data.Text as T
import qualified Data.Text.IO as TI
import Data.Functor
import Control.Monad
import qualified Data.Text.Lazy as TL
import qualified Data.Text.Lazy.IO as TLI
import System.Environment
import System.IO (stdin, stdout)
import GHC.IO.Handle (hSetBuffering, BufferMode(BlockBuffering))

fieldEnumerator field = enumStdin E.$= splitOn [',','\n'] E.$= grabField field

enumStdin = ET.enumHandle stdin

splitOn :: [Char] -> EI.Enumeratee T.Text T.Text IO b
splitOn chars = (ET.splitWhen (`elem` chars))

grabField :: String -> EI.Enumeratee T.Text T.Text IO b
grabField = EL.filter . T.isInfixOf . T.pack

intercalateNewlines = EL.mapM_ (\field -> (TI.putStrLn field >> (putStr "\n\n")))

runE enum = E.run_ $ enum E.$$ intercalateNewlines

main = do
  (field:_) <- getArgs
  runE $ fieldEnumerator field

驚いたことに、haskellバージョンのトレースは次のようになっています(実際のJSONは仕事からのデータであるため抑制されています)が、perlバージョンは私が期待することを実行します。一連の読み取りとそれに続く書き込みが繰り返されます。

55333/0x8816f5:    366125       3      0 sigprocmask(0x1, 0x10069BFA8, 0x10069BFAC)      = 0x0 0
55333/0x8816f5:    366136       3      0 sigprocmask(0x3, 0x10069BFAC, 0x0)      = 0x0 0
55333/0x8816f5:    367209       3      0 sigprocmask(0x1, 0x10069BFA8, 0x10069BFAC)      = 0x0 0
55333/0x8816f5:    367218       2      0 sigprocmask(0x3, 0x10069BFAC, 0x0)      = 0x0 0
55333/0x8816f5:    368449       3      0 sigprocmask(0x1, 0x10069BFA8, 0x10069BFAC)      = 0x0 0
55333/0x8816f5:    368458       2      0 sigprocmask(0x3, 0x10069BFAC, 0x0)      = 0x0 0
55333/0x8816f5:    369525       3      0 sigprocmask(0x1, 0x10069BFA8, 0x10069BFAC)      = 0x0 0
55333/0x8816f5:    369534       2      0 sigprocmask(0x3, 0x10069BFAC, 0x0)      = 0x0 0
55333/0x8816f5:    370610       3      0 sigprocmask(0x1, 0x10069BFA8, 0x10069BFAC)      = 0x0 0
55333/0x8816f5:    370620       2      0 sigprocmask(0x3, 0x10069BFAC, 0x0)      = 0x0 0
55333/0x8816f5:    371735       3      0 sigprocmask(0x1, 0x10069BFA8, 0x10069BFAC)      = 0x0 0
55333/0x8816f5:    371744       2      0 sigprocmask(0x3, 0x10069BFAC, 0x0)      = 0x0 0
55333/0x8816f5:    371798       5      2 select(0x1, 0x7FFF5FBFBA70, 0x7FFF5FBFB9F0, 0x0, 0x7FFF5FBFBAF0)        = 1 0
55333/0x8816f5:    371802       3      1 read(0x0, SOME_JSON, 0x1FA0)      = 8096 0
55333/0x8816f5:    372907       3      0 sigprocmask(0x1, 0x10069BFA8, 0x10069BFAC)      = 0x0 0
55333/0x8816f5:    372918       3      0 sigprocmask(0x3, 0x10069BFAC, 0x0)      = 0x0 0
55333/0x8816f5:    374063       3      0 sigprocmask(0x1, 0x10069BFA8, 0x10069BFAC)      = 0x0 0
55333/0x8816f5:    374072       2      0 sigprocmask(0x3, 0x10069BFAC, 0x0)      = 0x0 0
55333/0x8816f5:    375147       3      0 sigprocmask(0x1, 0x10069BFA8, 0x10069BFAC)      = 0x0 0
55333/0x8816f5:    375156       2      0 sigprocmask(0x3, 0x10069BFAC, 0x0)      = 0x0 0
55333/0x8816f5:    376283       3      0 sigprocmask(0x1, 0x10069BFA8, 0x10069BFAC)      = 0x0 0
55333/0x8816f5:    376292       2      0 sigprocmask(0x3, 0x10069BFAC, 0x0)      = 0x0 0
55333/0x8816f5:    376809       6      2 select(0x1, 0x7FFF5FBFBA70, 0x7FFF5FBFB9F0, 0x0, 0x7FFF5FBFBAF0)        = 1 0
55333/0x8816f5:    376814       5      3 read(0x0, SOME_JSON, 0x1FA0)      = 8096 0
55333/0x8816f5:    377378       3      0 sigprocmask(0x1, 0x10069BFA8, 0x10069BFAC)      = 0x0 0
55333/0x8816f5:    377387       3      0 sigprocmask(0x3, 0x10069BFAC, 0x0)      = 0x0 0
55333/0x8816f5:    378537       3      0 sigprocmask(0x1, 0x10069BFA8, 0x10069BFAC)      = 0x0 0
55333/0x8816f5:    378546       2      0 sigprocmask(0x3, 0x10069BFAC, 0x0)      = 0x0 0
55333/0x8816f5:    379598       3      0 sigprocmask(0x1, 0x10069BFA8, 0x10069BFAC)      = 0x0 0
55333/0x8816f5:    379604       3      0 sigreturn(0x7FFF5FBFF9A0, 0x1E, 0x1)        = 0 Err#-2
55333/0x8816f5:    379613       2      0 sigprocmask(0x3, 0x10069BFAC, 0x0)      = 0x0 0
55333/0x8816f5:    380667       3      0 sigprocmask(0x1, 0x10069BFA8, 0x10069BFAC)      = 0x0 0
55333/0x8816f5:    380678       2      0 sigprocmask(0x3, 0x10069BFAC, 0x0)      = 0x0 0
55333/0x8816f5:    381862       3      0 sigprocmask(0x1, 0x10069BFA8, 0x10069BFAC)      = 0x0 0
55333/0x8816f5:    381871       3      0 sigprocmask(0x3, 0x10069BFAC, 0x0)      = 0x0 0
55333/0x8816f5:    382032       6      2 select(0x1, 0x7FFF5FBFBA70, 0x7FFF5FBFB9F0, 0x0, 0x7FFF5FBFBAF0)        = 1 0
55333/0x8816f5:    382036       4      2 read(0x0, SOME_JSON, 0x1FA0)        = 8096 0
55333/0x8816f5:    383064       3      0 sigprocmask(0x1, 0x10069BFA8, 0x10069BFAC)      = 0x0 0
55333/0x8816f5:    383073       2      0 sigprocmask(0x3, 0x10069BFAC, 0x0)      = 0x0 0
55333/0x8816f5:    384118       3      0 sigprocmask(0x1, 0x10069BFA8, 0x10069BFAC)      = 0x0 0
55333/0x8816f5:    384127       2      0 sigprocmask(0x3, 0x10069BFAC, 0x0)      = 0x0 0
55333/0x8816f5:    385206       3      0 sigprocmask(0x1, 0x10069BFA8, 0x10069BFAC)      = 0x0 0
55333/0x8816f5:    385215       2      0 sigprocmask(0x3, 0x10069BFAC, 0x0)      = 0x0 0
55333/0x8816f5:    386348       3      0 sigprocmask(0x1, 0x10069BFA8, 0x10069BFAC)      = 0x0 0
55333/0x8816f5:    386358       3      0 sigprocmask(0x3, 0x10069BFAC, 0x0)      = 0x0 0
55333/0x8816f5:    387468       3      0 sigprocmask(0x1, 0x10069BFA8, 0x10069BFAC)      = 0x0 0
55333/0x8816f5:    387477      11      0 sigprocmask(0x3, 0x10069BFAC, 0x0)      = 0x0 0
55333/0x8816f5:    387614       6      2 select(0x1, 0x7FFF5FBFBA70, 0x7FFF5FBFB9F0, 0x0, 0x7FFF5FBFBAF0)        = 1 0
55333/0x8816f5:    387620       5      3 read(0x0, SOME_JSON, 0x1FA0)        = 8096 0
55333/0x8816f5:    388597       3      0 sigprocmask(0x1, 0x10069BFA8, 0x10069BFAC)      = 0x0 0
55333/0x8816f5:    388606       2      0 sigprocmask(0x3, 0x10069BFAC, 0x0)      = 0x0 0
55333/0x8816f5:    389707       3      0 sigprocmask(0x1, 0x10069BFA8, 0x10069BFAC)      = 0x0 0
55333/0x8816f5:    389716       2      0 sigprocmask(0x3, 0x10069BFAC, 0x0)      = 0x0 0
55333/0x8816f5:    390261       7      3 select(0x2, 0x7FFF5FBFBA70, 0x7FFF5FBFB9F0, 0x0, 0x7FFF5FBFBAF0)        = 1 0
55333/0x8816f5:    390273       6      3 write(0x1, SOME_OUTPUT, 0x1FA0)      = 8096 0
4

4 に答える 4

7

sigprocmask の割り当てや (オーバーヘッドから?) 呼び出しについて心配していますか?

前者でパッケージを使用したい場合、enumeratorこの小さな変更により 4k テスト セットが約 50% 改善されます。

splitOn :: EI.Enumeratee T.Text T.Text IO b
splitOn = EL.concatMap (T.split fastSplit)

fastSplit :: Char -> Bool
fastSplit ','  = True
fastSplit '\n' = True
fastSplit _    = False

前 (からの統計+RTS -sstderr -RTS):

       ヒープに割り当てられた 8,212,680 バイト
         GC 中にコピーされた 696,184 バイト
         148,656 バイトの最大常駐 (1 サンプル)
          30,664 バイトの最大スロップ
               合計 2 MB の使用メモリ (断片化により 0 MB が失われる)

                                    合計時間 (経過) 平均一時停止 最大一時停止
  Gen 0 15 列、0 パー 0.00 秒 0.00 秒 0.0001 秒 0.0005 秒
  Gen 1 1 列、0 パー 0.00 秒 0.00 秒 0.0010 秒 0.0010 秒

後:

       ヒープに割り当てられた 3,838,048 バイト
         GC 中にコピーされた 689,592 バイト
         148,368 バイトの最大常駐 (1 サンプル)
          27,040 バイトの最大スロップ
               合計 2 MB の使用メモリ (断片化により 0 MB が失われる)

                                    合計時間 (経過) 平均一時停止 最大一時停止
  Gen 0 6 colls、0 par 0.00s 0.00s 0.0001s 0.0003s
  Gen 1 1 列、0 パー 0.00 秒 0.00 秒 0.0006 秒 0.0006 秒

これはかなり合理的な改善ですが、間違いなく何かが望まれています. あまりにも多くの列挙子をキックするのではなく、単にキックのためにconduit-0.4.1で書き直すことに挑戦しました。それは同等のはずです...

import Data.Conduit as C
import qualified Data.Conduit.Binary as Cb
import qualified Data.Conduit.List as Cl
import qualified Data.Conduit.Text as Ct
import qualified Data.Text as T
import qualified Data.Text.IO as TI
import Control.Monad.Trans (MonadIO, liftIO)
import System.Environment
import System.IO (stdin)

grabField :: Monad m => String -> Conduit T.Text m T.Text
grabField = Cl.filter . T.isInfixOf . T.pack

printField :: MonadIO m => T.Text -> m ()
printField field = liftIO $ do
  TI.putStrLn field
  putStr "\n\n"

fastSplit :: Char -> Bool
fastSplit ','  = True
fastSplit '\n' = True
fastSplit _    = False

main :: IO ()
main = do
  field:_ <- getArgs
  runResourceT $ Cb.sourceHandle stdin
              $$ Ct.decode Ct.utf8
              =$ Cl.concatMap (T.split fastSplit)
              =$ grabField field
              =$ Cl.mapM_ printField

...しかし、何らかの理由でより少ないメモリを割り当てて保持します:

         ヒープに割り当てられた 835,688 バイト
           GC 中に 8,576 バイトがコピーされました
          87,200 バイトの最大常駐 (1 サンプル)
          19,968 バイトの最大スロップ
               合計 1 MB の使用メモリ (断片化により 0 MB が失われる)

                                    合計時間 (経過) 平均一時停止 最大一時停止
  Gen 0 1 colls、0 par 0.00s 0.00s 0.0000s 0.0000s
  Gen 1 1 列、0 パー 0.00 秒 0.00 秒 0.0008 秒 0.0008 秒
于 2012-04-23T06:17:33.087 に答える
7

これをコメントからトップレベルに昇格させる:

FWIW、私はランタイムを調べています (これについては IRC でも議論しています)。sigprocmask の用途は GC と tty ドライバーの 2 つだけです。後者の可能性は低いので、プロファイリングを行って GC を大量に実行していることを確認し、その理由を突き止めることをお勧めします。

そして、(IRC から) 0.5MB のデータに対して 90MB の割り当てを行っており、ガベージ コレクターが実際にかなり頻繁にトリガーされていることがわかりました。これで、列挙子が余分な割り当てを行う理由がわかりました。

于 2012-04-23T03:45:01.610 に答える
4

これらの sigsetmask 間で読み取られるデータの量が多い場合、最初に思いつくのは、gc が実行される前にランタイムが sigsetmask を実行しているため、一貫性のない状態でヒープが gc に割り込まれないということです。

于 2012-04-23T03:01:00.030 に答える
3

コメント以上で回答未満: GHC ソースを grepposix/TTY.cすると、(TERMIOS コード) が表示され、sm/GC.c(経由で{,un}blockUserSignals) 最も可能性の高い候補が表示されます。デバッグ シンボルを使用して GHC をコンパイルするか、ダミーの (一意の) システム コールをいくつか投入して、2 つのシステム コール プロファイルを区別して見つけられるようにすることができます。別の安価なテストは、端末の相互作用を削除することです。マスキング動作が消えれば、GC をサポートする穏やかな証拠になります (答えはありません)。

編集:一部のライブラリコードも呼び出すことができることを認める必要がありますsigprocmask。ソースの可能性は低いので無視しましたが、実際には問題になる可能性があります!

于 2012-04-23T03:35:44.090 に答える