2014-11-05 11 views
7

Ich versuche, Hash-Tabellen in Haskell mit der hashtables package zu verwenden, und zu finden, dass ich nicht in der Nähe von Pythons Leistung kommen kann. Wie kann ich ähnliche Leistungen erzielen? Ist es möglich, aktuelle Haskell-Bibliotheken und Compiler zu geben? Wenn nicht, was ist das zugrunde liegende Problem?Haskell Hashtable Leistung

Hier ist meine Python-Code:

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

Hier ist mein entsprechenden Code 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 

Hier ist eine andere Version ist Data.Map verwenden, die für mich langsamer als beide ist:

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 

Interessanterweise funktioniert Data.HashMap sehr schlecht:

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 

Mein Verdacht ist, dass Data.HashMap schlecht ab, weil im Gegensatz zu Data.Map, ist es nicht wirbelsäulen streng (glaube ich), so foldl' ist nur ein foldl, mit den damit verbundenen thunk Aufbauproblemen.

Bitte beachte, dass ich verwendet habe -prof und bestätigt, dass die Mehrheit der Zeit verbringt, ist in dem hashtables oder Data.Map Code, nicht auf den forM oder so etwas. Der gesamte Code ist mit -O2 und anderen Parametern kompiliert.

+1

Welche Zeiten bekommen Sie? – jamshidh

+0

Mit "neu" sind die Zeiten 2 und 10-15 Sekunden; mit 'newSize', wie in der Antwort unten vorgeschlagen, sind sie 2 und 4. –

+0

FYI- Ich habe es gerade auf meinem Rechner versucht .... etwa 10 Sekunden für den' Data.HashTable.IO' Code, etwa 1 Sekunde für die Python und 3 Sekunden für 'Data.Map'. – jamshidh

Antwort

8

Die Dokumentation für hashtables stellt fest, dass "Cuckoo Hashing, wie die grundlegende Hash-Tabellenimplementierung mit linearen Probing, lange Verzögerungen leiden kann, wenn die Tabelle in der Größe geändert wird." Sie verwenden new, die eine neue Tabelle der Standardgröße erstellt. From looking at the source, scheint die Standardgröße 2 zu sein. Das Einfügen von 10000000 Elementen führt wahrscheinlich zu zahlreichen Änderungen.

Versuchen Sie mit newSized.

+0

Danke für den Vorschlag. Dies hilft dramatisch. Wenn ich 'newSized' mit genau der richtigen Anzahl von Elementen verwende, unterscheiden sich der Python-Code und der Haskell-Code nur um zwei in der Geschwindigkeit. Gibt es noch andere Tricks, die helfen können? –

2

Angesichts der oben genannten Zeiten dachte ich, ich würde die Data.Map Lösung werfen, die mit der Verwendung von newSize vergleichbar zu sein scheint.

import qualified Data.Map as M 

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

Dies ist auch eine Lösung, aber ich denke, dass es den Originalcode nicht genau wiedergibt, da der Punkt darin besteht, dass wir wiederholte Einfügungen vornehmen und nicht alle Daten im Vordergrund haben. Aber das ist immer noch ein nützlicher Datenpunkt. –

+0

@AndrewGibiansky, ich denke du beziehst dich auf 'fromList'. Aber wenn Sie sich die [Quelle] (http://hackage.haskell.org/package/containers-0.4.0.0/docs/src/Data-Map.html#fromList) ansehen, können Sie sehen, dass 'fromList' einfach ist als Faltung von Insertionen implementiert. – luqui

+1

Auf meiner Plattform führt das Erzwingen der Liste zu ':: [Int]' zu einer Beschleunigung um 13% (8,5 s bis 7,4 s), wobei "Data.Map.Lazy" Data.Map.Strict übertrifft, aber zu einem IntMap wechselt Dies ist um einen Faktor 2 schneller und der Gewinner ist "Data.IntMap.Strict" bei 3,5s. Sie können sogar noch schneller mit 'fromDistinctAscList' (2.2s) kommen, aber wie Andrew darauf hinweist, ist das wahrscheinlich Betrug, da der Benchmark weniger dem realen Gebrauch ähnelt. –

10

Als reddit.com/u/cheecheeo here vorgeschlagen, mit Data.Judy, werden Sie eine ähnliche Leistung für Ihre speziellen-Micro erhalten:

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 die oben:

$ time ./Main 
Just 100 

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

Timing des Python-Codes von OP:

$ time ./main.py 
100 

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