9 Stimmen

Haskell-Leistung bei Verwendung von Klassen und Instanzen

Das Problem

Ich möchte in Haskell Funktionen simulieren, die mehrere Werte ausgeben. Der Haskell-Code wird generiert (nicht von Hand geschrieben) - dies sind wichtige Informationen, siehe unten:

Dies kann natürlich einfach durch Rückgabe eines Tupels aus der Funktion erfolgen, wie zum Beispiel

f x y = (x+y, x-y)

Aber beim Verwenden einer solchen Funktion muss ich wissen, was für ein Tupel es zurückgibt:

...
(out_f_1, out_f_2)          = f a b
(out_g_1, out_g_2, out_g_3) = g out_f_1
...

Und so weiter ... Aber während des Code-Generierens weiß ich nicht, was für ein Typ der Ausgang von z.B. f ist, daher verwende ich derzeit das Paket Data.List.Select und simuliere das Obige mit:

import Data.List.Select
...
out_f = f a b
out_g = g (sel1 outf)
...

Das Problem ist die Leistung - in meinem Testprogramm ist die Version, die Data.List.Select verwendet, doppelt so langsam wie die von Hand geschriebene Version.

Dies ist eine sehr offensichtliche Situation, da Data.List.Select unter Verwendung von Klassen und Instanzen geschrieben ist, so dass es so etwas wie ein Laufzeitwörterbuch verwendet (wenn ich mich nicht irre). (http://hackage.haskell.org/packages/archive/tuple/0.2.0.1/doc/html/src/Data-Tuple-Select.html#sel1)

Die Frage

Ich möchte fragen, ob es möglich ist, die Version, die Data.List.Select verwendet, irgendwie so zu kompilieren, dass sie so schnell ist wie die manuell erstellte?

Ich denke, es sollte einen Schalter für den Compiler geben, der ihm sagt, die Klassen und Schnittstellen für jede Verwendung zu "instanziieren" (etwas Ähnliches wie Templates aus C++).

Benchmarks

Test1.hs:

import qualified Data.Vector as V
import System.Environment
b :: Int -> Int
b x = x + 5
c x = b x + 1
d x = b x - 1
a x = c x + d x
main = do
   putStrLn "Starting..."
   args <- getArgs
   let iternum = read (head args) :: Int in do
      putStrLn $ show $ V.foldl' (+) 0 $ V.map (\i -> a (iternum-i))
         $ V.enumFromTo 1 iternum
      putStrLn "Done."

Kompilieren mit ghc -O3 Test1.hs

Test2.hs:

import qualified Data.Vector as V
import Data.Tuple.Select
import Data.Tuple.OneTuple

import System.Environment
b x = OneTuple $ x + 5
c x = OneTuple $ (sel1 $ b x) + 1
d x = OneTuple $ (sel1 $ b x) - 1
a x = OneTuple $ (sel1 $ c x) + (sel1 $ d x)
main = do
   putStrLn "Starting..."
   args <- getArgs
   let iternum = read (head args) :: Int in do
      putStrLn $ show $ V.foldl' (+) 0 $ V.map (\i -> sel1 $ a (iternum-i))
         $ V.enumFromTo 1 iternum
      putStrLn "Done."

Kompilieren mit ghc -O3 Test2.hs

Ergebnisse

time ./Test1 10000000 = 5.54 s
time ./Test2 10000000 = 10.06 s

3voto

tohava Punkte 5314

Ich bin mir nicht sicher, aber es könnte sich lohnen zu versuchen http://www.haskell.org/ghc/docs/7.0.3/html/users_guide/pragmas.html#specialize-pragma

0voto

Wojciech Danilo Punkte 11135

Ok, die Ergebnisse, die ich veröffentlicht habe, sind nicht genau - wie @sabauma gesagt hat - die beiden Codes führen zur gleichen Zeit aus, wenn Sie sie mit aktivierten Optimierungen kompilieren.

Die Antwort von @tohava ist sehr gut, wenn Sie explizit zeigen möchten, welche Funktionen zu spezialisieren sind (siehe den Kommentar von @sabauma oben).

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X