2012-03-05 4 views
15

バイナリレコードのセットがファイルに格納されており、Data.ByteString.LazyとData.Binary.Getを使用してそれらを読み込んでいます。現在の実装では、8Mbファイルを解析するのに6秒かかります。haskellのバイナリファイルの解析性能が悪い

import qualified Data.ByteString.Lazy as BL 
import Data.Binary.Get 

data Trade = Trade { timestamp :: Int, price :: Int , qty :: Int } deriving (Show) 

getTrades = do 
    empty <- isEmpty 
    if empty 
    then return [] 
    else do 
     timestamp <- getWord32le   
     price <- getWord32le 
     qty <- getWord16le   
     rest <- getTrades 
     let trade = Trade (fromIntegral timestamp) (fromIntegral price) (fromIntegral qty) 
     return (trade : rest) 

main :: IO() 
main = do 
    input <- BL.readFile "trades.bin" 
    let trades = runGet getTrades input 
    print $ length trades 

これを高速化するにはどうすればよいですか?

+0

実際の世界ではプロファイリングに関するhaskellの章があり、so.comでは[haskell] + [performance]とタグ付けされたいくつかの質問があります。これはおそらくヨーヨーの助けになりますu。 – epsilonhalbe

+0

@epsilonhalbeありがとうございました。私は良い検索をしていました。このパターンは、Data.Binary.Getのドキュメントの1つです。私はそれが「ほとんどトレイル再帰」の問題だと思うが、それを理解するのは私の外にある。 –

+0

これはData.Binary.Getが厳密に見えるので難しいです - 私はより良い怠惰を得ることについて以前のコメントをしましたが、適用できないので削除しました。ダニエルフィッシャーの答えは、あなたが厳しいより良い仕事をする方法を示しています。 –

答えて

17

あなたのコードはここで1秒未満で8MBのファイルをデコードします(ghc-7.4.1)。もちろん私は-O2でコンパイルしました。

しかし、それには過度のスタックスペースが必要でした。あなたは適切な場所に、より厳格を追加し、解析された-SO-を収集するために、アキュムレータを使用して必要な

  • 時間
  • スタック空間
  • ヒープ領域

を減らすことができます遠い取引。

{-# LANGUAGE BangPatterns #-} 
module Main (main) where 

import qualified Data.ByteString.Lazy as BL 
import Data.Binary.Get 

data Trade = Trade { timestamp :: {-# UNPACK #-} !Int 
        , price :: {-# UNPACK #-} !Int 
        , qty :: {-# UNPACK #-} !Int 
        } deriving (Show) 

getTrades :: Get [Trade] 
getTrades = go [] 
    where 
    go !acc = do 
     empty <- isEmpty 
     if empty 
     then return $! reverse acc 
     else do 
      !timestamp <- getWord32le 
      !price <- getWord32le 
      !qty <- getWord16le 
      let !trade = Trade (fromIntegral timestamp) (fromIntegral price) (fromIntegral qty) 
      go (trade : acc) 

main :: IO() 
main = do 
    input <- BL.readFile "trades.bin" 
    let trades = runGet getTrades input 
    print $ length trades 

厳しさとアンパックには作業がすでに忘れ去られている必要がありますByteStringの一部を参照することで、後であなたを噛まないように戻ってきて元に戻す残っていないことを確認します。

レイジーフィールドを使用する場合はTradeが必要な場合でも、より厳密なフィールドを持つタイプをデコードし、より厳しいデコードの恩恵を受けるためにmapの結果リストを変換できます。

しかし、このコードではまだ多くの時間をガベージコレクションに費やしているため、さらに改善が必要な場合があります。

+3

素晴らしい答えをありがとう!あなたはノブレベルを少し上げました。 –

19

わずかにリファクタリング(基本的に左向き)すると、パフォーマンスが大幅に向上し、8388600バイトのファイルをかなり解析してGCオーバーヘッドが低下します。

{-# LANGUAGE BangPatterns #-} 
module Main (main) where 

import qualified Data.ByteString.Lazy as BL 
import Data.Binary.Get 

data Trade = Trade 
    { timestamp :: {-# UNPACK #-} !Int 
    , price  :: {-# UNPACK #-} !Int 
    , qty  :: {-# UNPACK #-} !Int 
    } deriving (Show) 

getTrade :: Get Trade 
getTrade = do 
    timestamp <- getWord32le 
    price  <- getWord32le 
    qty  <- getWord16le 
    return $! Trade (fromIntegral timestamp) (fromIntegral price) (fromIntegral qty) 

countTrades :: BL.ByteString -> Int 
countTrades input = stepper (0, input) where 
    stepper (!count, !buffer) 
    | BL.null buffer = count 
    | otherwise  = 
     let (trade, rest, _) = runGetState getTrade buffer 0 
     in stepper (count+1, rest) 

main :: IO() 
main = do 
    input <- BL.readFile "trades.bin" 
    let trades = countTrades input 
    print trades 

関連するランタイム統計情報。割り当て数が近いにもかかわらず、GCと最大ヒープサイズはリビジョン間でかなり異なっています。

ここでのすべての例は、GHC 7.4.1 -O2で構築されています。過度のスタック領域の使用状況に+ RTS -K1G -RTSで実行

元のソース、:

 
    426,003,680 bytes allocated in the heap 
    443,141,672 bytes copied during GC 
     99,305,920 bytes maximum residency (9 sample(s)) 
      203 MB total memory in use (0 MB lost due to fragmentation) 

    Total time 0.62s ( 0.81s elapsed) 

    %GC  time  83.3% (86.4% elapsed) 

ダニエルのリビジョン:

 
    357,851,536 bytes allocated in the heap 
    220,009,088 bytes copied during GC 
     40,846,168 bytes maximum residency (8 sample(s)) 
       85 MB total memory in use (0 MB lost due to fragmentation) 

    Total time 0.24s ( 0.28s elapsed) 

    %GC  time  69.1% (71.4% elapsed) 

そして、このポスト:

 
    290,725,952 bytes allocated in the heap 
     109,592 bytes copied during GC 
      78,704 bytes maximum residency (10 sample(s)) 
       2 MB total memory in use (0 MB lost due to fragmentation) 

    Total time 0.06s ( 0.07s elapsed) 

    %GC  time  5.0% (6.0% elapsed) 
+1

ありがとう素敵な改善! –

関連する問題