2014-11-05 13 views
7

Próbuję użyć tabel mieszania w Haskell z hashtables package i stwierdzenie, że nie mogę uzyskać w pobliżu wydajności Pythona. Jak mogę osiągnąć podobną wydajność? Czy jest to możliwe, biorąc pod uwagę obecne biblioteki i kompilatory Haskella? Jeśli nie, jaki jest podstawowy problem?Haskell Hashtable Performance

Oto mój kod Python:

y = {} 
for x in xrange(10000000): 
    y[x] = x 
print y[100] 

Oto mój odpowiedni kod Haskell:

import qualified Data.HashTable.IO as H 
import Control.Monad 

main = do 
    y <- H.new :: IO (H.CuckooHashTable Int Int) 
    forM_ [1..10000000] $ \x -> H.insert y x x 
    H.lookup y 100 >>= print 

Oto kolejna wersja użyciu Data.Map, które jest wolniejsze niż zarówno dla mnie:

import qualified Data.Map as Map 
import Data.List 
import Control.Monad 
main = do 
    let m = foldl' (\m x -> Map.insert x x m) Map.empty [1..10000000] 
    print $ Map.lookup 100 m 

Co ciekawe, Data.HashMap działa bardzo źle:

import qualified Data.HashMap.Strict as Map 
import Data.List 
main = do 
    let m = foldl' (\m x -> Map.insert x x m) Map.empty [1..10000000] 
    print $ Map.lookup 100 m 

Podejrzewam, że Data.HashMap wykonuje źle, ponieważ w odróżnieniu Data.Map, nie jest Mrożąca ścisłe (chyba), więc foldl' tylko foldl, związanych z tym problemów thunk wodnej.

Zauważ, że użyłem -prof i sprawdzeniu, że większość czasu jest spędzić w kodzie hashtables lub Data.Map, a nie na forM lub coś podobnego. Cały kod jest kompilowany z -O2 i nie ma innych parametrów.

+1

Którą godzinę otrzymujesz? – jamshidh

+0

Używając 'nowego', czasy to 2 i 10-15 sekund; używając 'newSized', jak zasugerowano w odpowiedzi poniżej, są to 2 i 4. –

+0

FYI- Właśnie wypróbowałem to na moim komputerze .... około 10 sekund dla kodu' Data.HashTable.IO', około 1 sekundy dla python i 3 sekundy dla 'Data.Map'. – jamshidh

Odpowiedz

8

Dokumentacja dla hashtables stwierdza, że ​​"Cuckoo hashing, podobnie jak podstawowa implementacja tablic asocjacyjnych za pomocą sondowania liniowego, może cierpieć z powodu dużych opóźnień przy zmianie rozmiaru tabeli." Używasz new, która tworzy nową tabelę domyślnego rozmiaru. From looking at the source, wydaje się, że domyślny rozmiar to 2. Wstawienie 10000000 elementów prawdopodobnie pociąga za sobą liczne zmiany rozmiaru.

Spróbuj użyć newSized.

+0

Dzięki za sugestię. Pomaga to dramatycznie. Jeśli użyję 'newSized' z dokładnie odpowiednią liczbą elementów, kod Pythona i kod Haskella różnią się tylko 2x szybkością. Czy są jakieś inne sztuczki, które mogą pomóc? –

2

Biorąc pod uwagę powyższe czasy, pomyślałem, że wrzucę rozwiązanie Data.Map, które wydaje się być porównywalne do używania newSized.

import qualified Data.Map as M 

main = do 
     print $ M.lookup 100 $ M.fromList $ map (\x -> (x,x)) [1..10000000] 
+0

To również rozwiązanie, ale uważam, że nie odzwierciedla dokładnie oryginalnego kodu, ponieważ chodzi o to, że robimy wielokrotne wstawienia i nie mamy wszystkich danych z góry. Ale nadal jest to przydatny punkt danych. –

+0

@AndrewGibiansky, myślę, że odnosisz się do 'fromList'. Ale jeśli spojrzysz na [źródło] (http://hackage.haskell.org/package/containers-0.4.0.0/docs/src/Data-Map.html#fromList), zobaczysz, że 'fromList' jest po prostu zaimplementowane jako zagięcie wstawek. – luqui

+1

Na mojej platformie wymuszenie listy na ':: [Int]' powoduje przyspieszenie o 13% (od 8,5 s do 7,4 s) z 'Data.Map.Lazy' przewyższające' Data.Map.Strict', ale przełączenie na IntMap powoduje to szybciej przez współczynnik 2, a zwycięzcą jest 'Data.IntMap.Strict' w 3.5s. W rzeczywistości można uzyskać jeszcze szybszy z 'fromDistinctAscList' (2,2s), ale jak Andrew wskazuje, to prawdopodobnie oszukuje, ponieważ benchmark przypomina mniej wykorzystanie w realnym świecie. –

10

Jak reddit.com/u/cheecheeo zasugerował here korzystając Data.Judy, dostaniesz podobną wydajność dla danego microbenchmark:

module Main where 

import qualified Data.Judy as J 
import Control.Monad (forM_) 

main = do 
    h <- J.new :: IO (J.JudyL Int) 
    forM_ [0..10000000] $ \i -> J.insert (fromIntegral i) i h 
    v <- J.lookup 100 h 
    putStrLn $ show v 

Timeing powyższego:

$ time ./Main 
Just 100 

real 0m0.958s 
user 0m0.924s 
sys  0m0.032s 

Czas synchronizacji kodu Pythona:

$ time ./main.py 
100 

real 0m1.067s 
user 0m0.886s 
sys  0m0.180s