2013-06-05 9 views
8

Lets sagen, dass ich zwei Funktionen gegeben habe:Verschachtelung Listenfunktionen

f :: [a] -> b 
g :: [a] -> c 

ich eine Funktion schreiben wollen, die das Äquivalent davon ist:

h x = (f x, g x) 

Aber wenn ich das tue, für große Listen zwangsläufig ich nicht mehr genügend Speicher.

Ein einfaches Beispiel ist die folgende:

x = [1..100000000::Int] 
main = print $ (sum x, product x) 

Ich verstehe dies der Fall ist, weil die Liste x wird, ohne Müll gesammelt im Speicher abgelegt werden. Es wäre besser, statt f und g auf x in, naja, "parallel" gearbeitet.

Angenommen, ich nicht f ändern und g, noch will eine separate Kopie von x machen (unter der Annahme x ist teuer in der Herstellung), wie kann ich h schreiben, ohne Probleme aus dem Speicher läuft in?

+2

Ich habe das nicht wirklich vorher studiert, aber http://squing.blogspot.com/2008/11/beautiful-folding.html ist direkt am Punkt. Conal Elliot hat auch ein paar Follow-ups zu diesem Thema gemacht. – Carl

Antwort

2

Sie können mehrere Threads verwenden, um f x und g x parallel auszuwerten.

z.

x :: [Int] 
x = [1..10^8] 

main = print $ let a = sum x 
        b = product x 
       in a `par` b `pseq` (a,b) 

Es ist eine gute Möglichkeit, die parallele Laufzeit von GHC auszunutzen, um ein Platzleck zu verhindern, indem zwei Dinge gleichzeitig ausgeführt werden.

Alternativ müssen Sie f und g in a single pass fusionieren.

+2

Don: Wenn 'sum' 10 mal schneller ist als' product', würde 'product' nicht zurückbleiben, die Garbage Collection verhindern und trotzdem ein Platzleck verursachen? Es könnte in diesem Fall funktionieren, aber im allgemeinen Fall kann ich sehen, dass es scheitert. – Clinton

+1

YEs, sie müssen ungefähr in Verriegelungsschritt sein. Wenn sie sind, haben Sie, wie es aussieht, Loop-Fusion kostenlos (dynamisch). –

+2

Don: Ich bin mir nicht sicher, ob das funktionieren wird. Ich habe nach einer allgemeinen Lösung gesucht, die nicht zu unterschiedlichen CPU-Timings geführt hat, die möglicherweise Platzlecks verursacht haben. – Clinton

12

Eine kurze Antwort ist, können Sie nicht. Da Sie keine Kontrolle über f und g haben, können Sie nicht garantieren, dass die Funktionen ihre Eingaben sequentiell verarbeiten. Eine solche Funktion kann auch die gesamte Liste im Speicher behalten, bevor das Endergebnis erzeugt wird.

Wenn jedoch Ihre Funktionen als Falten ausgedrückt werden, ist die Situation anders. Dies bedeutet, dass wir jeden Schritt schrittweise anwenden können, sodass wir diese Schritte in einem Durchgang parallelisieren können.

Das sind viele Ressourcen zu diesem Bereich. Zum Beispiel:


das Muster eine Folge von Werten mit richtig definierten Raum Grenzen des Verzehrs wird allgemein mit rohrartigen Bibliotheken gelöst so Leitung, iterates o r Rohre.Zum Beispiel in Leitung, könnten Sie die Kombination von Rechen Summen und Produkten wie

import Control.Monad.Identity 
import Data.Conduit 
import Data.Conduit.List (fold, sourceList) 
import Data.Conduit.Internal (zipSinks) 

product', sum' :: (Monad m, Num a) => Sink a m a 
sum'  = fold (+) 0 
product' = fold (*) 1 

main = print . runIdentity $ sourceList (replicate (10^6) 1) $$ 
           zipSinks sum' product' 
2

Express Wenn Sie Ihre Funktionen in Falten drehen können, kann man sich dann nur mit einem Scan verwenden:

x = [1..100000000::Int] 
main = mapM_ print . tail . scanl foo (a0,b0) . takeWhile (not.null) 
     . unfoldr (Just . splitAt 1000) -- adjust the chunk length as needed 
     $ x 

foo (a,b) x = let a2 = f' a $ f x ; b2 = g' b $ g x 
       in a2 `seq` b2 `seq` (a2, b2) 

f :: [t] -> a   -- e.g. sum 
g :: [t] -> b   --  (`rem` 10007) . product 
f' :: a -> a -> a  -- e.g. (+) 
g' :: b -> b -> b  --  ((`rem` 10007) .) . (*) 

wir verbrauchen die Eingabe in Chunks für eine bessere Leistung. Zusammen mit -O2 sollte dies in einem konstanten Raum ausgeführt werden. Die Zwischenergebnisse werden als Fortschrittsanzeige gedruckt.

Wenn Sie Ihre Funktion nicht in eine Falte umwandeln können, bedeutet dies, dass hat, um die gesamte Liste zu konsumieren, um irgendeine Ausgabe zu produzieren, und dieser Trick trifft nicht zu.