私はHaskellを使ってファイルに保存されたユニークなブロックを数えたいと思っています。 ブロックは長さが512の単なる連続バイトで、対象ファイルのサイズは少なくとも1GBです。ハスケルの効率的なハッシュマップコンテナ?
これは私の最初の試みです。
import Control.Monad
import qualified Data.ByteString.Lazy as LB
import Data.Foldable
import Data.HashMap
import Data.Int
import qualified Data.List as DL
import System.Environment
type DummyDedupe = Map LB.ByteString Int64
toBlocks :: Int64 -> LB.ByteString -> [LB.ByteString]
toBlocks n bs | LB.null bs = []
| otherwise = let (block, rest) = LB.splitAt n bs
in block : toBlocks n rest
dedupeBlocks :: [LB.ByteString] -> DummyDedupe -> DummyDedupe
dedupeBlocks = flip $ DL.foldl' (\acc block -> insertWith (+) block 1 $! acc)
dedupeFile :: FilePath -> DummyDedupe -> IO DummyDedupe
dedupeFile fp dd = LB.readFile fp >>= return . (`dedupeBlocks` dd) . toBlocks 512
main :: IO()
main = do
dd <- getArgs >>= (`dedupeFile` empty) . head
putStrLn . show . (*512) . size $ dd
putStrLn . show . (*512) . foldl' (+) 0 $ dd
これは動作しますが、実行時間とメモリ使用量に不満があります。特に、以下に挙げるC++やPythonの実装と比較すると、3〜5倍遅くなり、2〜3倍多くメモリを消費します。
import os
import os.path
import sys
def dedupeFile(dd, fp):
fd = os.open(fp, os.O_RDONLY)
for block in iter(lambda : os.read(fd, 512), ''):
dd.setdefault(block, 0)
dd[block] = dd[block] + 1
os.close(fd)
return dd
dd = {}
dedupeFile(dd, sys.argv[1])
print(len(dd) * 512)
print(sum(dd.values()) * 512)
私はそれが主にハッシュマップの実装によるものであったと考え、このようなhashmap
、hashtables
及びunordered-containers
などの他の実装を試みました。 しかし、顕著な違いはありませんでした。
このプログラムを改善するのに手伝ってください。
実際に、私が気にするのはメモリ使用量です。ハスケルハッシュマップの過度のメモリ使用量を理解することはできません。例えば。入力ファイルに600MBのユニークなデータが含まれていた場合、約1GB以上のメモリが使用されていました。とにかく、あなたの答えと記事のリンクをありがとう。私はFFIの使用を検討すべきです。 – comatose
@comatose、それはちょうどGHCです。 GHCガベージコレクション戦略では、コピーコレクタが使用されていますが、これは非常に高速ですが、メモリオーバーヘッドは2倍です。 – luqui