2013-02-01 6 views
10

Chcę policzyć unikalne bloki przechowywane w pliku przy użyciu Haskell. Blok to po prostu kolejne bajty o długości 512, a plik docelowy ma co najmniej 1 GB.Efektywny pojemnik mapy haszującej w Haskell?

To jest moja pierwsza próba.

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 

Działa, ale jestem sfrustrowany jego czasem wykonania i zużyciem pamięci. Especilly, gdy porównałem go z wersją C++, a nawet implementacją Pythona wymienioną poniżej, był wolniejszy 3 ~ 5x i zużywał 2 ~ 3x więcej miejsca w pamięci.

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) 

myślałem, że to głównie ze względu na wdrożenie HashMap i spróbował inne implementacje takich jak hashmap, hashtables i unordered-containers. Ale nie było żadnej zauważalnej różnicy.

Proszę, pomóż mi ulepszyć ten program.

Odpowiedz

6

Nie sądzę, że będziesz w stanie pokonać wydajność słowników Pythona. W rzeczywistości są one wdrażane za pomocą wielu lat optymalizacji, z drugiej strony hashmap jest nowy i niezbyt zoptymalizowany. Wydaje mi się, że uzyskanie 3-krotnego wyniku jest wystarczająco dobre. Możesz zoptymalizować kod haskell w niektórych miejscach, ale nadal nie będzie to miało większego znaczenia. Jeśli nadal jesteś nieugięty w kwestii zwiększenia wydajności, myślę, że powinieneś użyć wysoce zoptymalizowanej biblioteki c z ffi w swoim kodzie.

Oto niektóre z podobnych dyskusji

haskell beginners

+0

Właściwie to, co mnie najbardziej interesuje, to wykorzystanie pamięci, nie mogę zrozumieć nadmiernego użycia pamięci przez Haskell. Na przykład. Gdy plik wejściowy zawierał tylko 600 MB niepowtarzalnych danych, zużywał około 1 GB pamięci lub więcej. W każdym razie, dziękuję za odpowiedź i linki do artykułów. Powinienem rozważyć użycie FFI. – comatose

+4

@Comatose, to tylko GHC. Strategia odśmiecania GHC korzysta z kolektora kopiowania, który jest naprawdę szybki, ale ma 2 x obciążenie pamięci. – luqui

3

To może być całkowicie bez znaczenia w zależności od użytkowania, ale jestem lekko zaniepokojony insertWith (+) block 1. Jeśli twoje hrabiny osiągną wysokie liczby, będziesz gromadzić strzępy w komórkach mapy hash. Nie ma znaczenia, że ​​użyłeś ($!), który tylko wymusza kręgosłup - wartości są prawdopodobnie nadal leniwy.

Data.HashMap nie zawiera ścisłej wersji insertWith', takiej jak Data.Map. Ale można wdrożyć go:

insertWith' :: (Hashable k, Ord k) => (a -> a -> a) -> k -> a 
            -> HashMap k a -> HashMap k a 
insertWith' f k v m = maybe id seq maybeval m' 
    where 
    (maybeval, m') = insertLookupWithKey (const f) k v m 

Również może chcesz wyjścia (ale nie wejście) listę ścisłych ByteStrings z toBlocks, co uczyni mieszania szybciej.

To wszystko, co mam - nie jestem jednak guru wydajności.

+1

Udało mi się wycisnąć trochę, tworząc 'dane Blk = Blk {- # UNPACK # -} Word64 ...' aby pomieścić 512 bajtów. Znaczny wzrost wydajności występuje, jeśli przełączysz się na ścisłe ByteStrings, ale nie jestem pewien, ile z tego wynika z efektów takich jak pamięć podręczna i ile wynika z mojej starej nemezis leniwych kawałków ByteString, które nie mają rozsądnego wyrównania (co niepokoi mnie, ponieważ powoduje braches, kopiowanie itp.). Ostatecznie najlepiej zrobiły 'nieuporządkowane-pojemniki '(4,8 sec py vs 6,5 sec hs, ale było to ścisłe bytestring), podczas gdy' hashtable' było po prostu frustrujące z powodu braku operacji 'insertWith'. –

+0

@luqui Dzięki za odpowiedź, dowiedziałem się czegoś od ciebie. Właściwie istnieje 'Data.HashMap.Strict' w' unormered-containers' i próbowałem tego, ale nie mogło to poprawić sytuacji, ani ścisłe 'ByteString' ani. 'toStrict' jest dość kosztowne. – comatose

+0

@ ThomasM.DuBuisson dzięki, powinienem spróbować. – comatose