Voglio contare i blocchi unici memorizzati in un file usando Haskell. Il blocco è solo byte consecutivi con una lunghezza di 512 e il file di destinazione ha una dimensione di almeno 1 GB.Contenitore di mappe hash efficiente in Haskell?
Questo è il mio tentativo iniziale.
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
Funziona, ma sono stato frustrato dal tempo di esecuzione e dall'utilizzo della memoria. Specialmente quando ho confrontato con quelli di C++ e persino con l'implementazione di Python elencati di seguito, era 3 ~ 5 volte più lento e consumava 2 ~ 3 volte più spazio di memoria.
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)
ho pensato che fosse dovuto principalmente alla realizzazione hashmap, e provato altre implementazioni, come hashmap
, hashtables
e unordered-containers
. Ma non c'era alcuna differenza evidente.
Per favore aiutatemi a migliorare questo programma.
In realtà, quello che mi interessa di più è l'utilizzo della memoria, non riesco a capire l'uso eccessivo della memoria delle hashmap di Haskell. Per esempio. Quando il file di input conteneva solo 600 MB di dati univoci, consumava circa 1 GB di memoria o più. Ad ogni modo, grazie per la tua risposta e i collegamenti degli articoli. Dovrei considerare l'utilizzo di FFI. – comatose
@comatose, questo è solo GHC. La strategia di garbage collection di GHC utilizza un raccoglitore di copie, che è molto veloce, ma ha un overhead di memoria 2x. – luqui