2013-02-26 11 views
10

La mia applicazione moltiplica i vettori dopo una (costosa) conversione utilizzando una FFT. Di conseguenza, quando scrivoMoltiplicazione memorizzabile

f :: (Num a) => a -> [a] -> [a] 
f c xs = map (c*) xs 

solo voglio calcolare la FFT di c una volta, piuttosto che per ogni elemento di xs. Non c'è davvero alcuna necessità di memorizzare la FFT di c per l'intero programma, solo nell'ambito locale.

ho tentato di definire la mia Num istanza come:

data Foo = Scalar c 
     | Vec Bool v -- the bool indicates which domain v is in 

instance Num Foo where 
    (*) (Scalar c) = \x -> case x of 
         Scalar d -> Scalar (c*d) 
         Vec b v-> Vec b $ map (c*) v 
    (*) v1 = let Vec True v = fft v1 
      in \x -> case x of 
        Scalar d -> Vec True $ map (c*) v 
        v2 -> Vec True $ zipWith (*) v (fft v2) 

Poi, in una domanda, che io chiamo una funzione simile a f (che funziona su arbitrarie Num s) dove c=Vec False v, e mi aspettavo che questo sarebbe essere altrettanto veloce come se io hackero f a:

g :: Foo -> [Foo] -> [Foo] 
g c xs = let c' = fft c 
     in map (c'*) xs 

la funzione g rende il Memoizzazione di fft c verifica, ed è mu ch più veloce di chiamare f (non importa come definisco (*)). Non capisco cosa c'è che non va con f. È la mia definizione di nell'istanza Num? Ha qualcosa a che fare con f che funziona su tutti i Num, e GHC quindi non è in grado di capire come calcolare parzialmente (*)?

Nota: Ho controllato l'output principale per la mia istanza Num e (*) è effettivamente rappresentato come lambda nidificato con la conversione FFT nel lambda di livello superiore. Quindi sembra che questo sia almeno in grado di essere memoized. Ho anche provato sia l'uso giudizioso e sconsiderato di modelli di scoppio per tentare di forzare la valutazione senza alcun effetto.

Come nota a margine, anche se io riesco a capire come fare (*) Memoize suo primo argomento, c'è ancora un altro problema di come si è definito: Un programmatore voler uso il tipo di dati Foo deve sapere su questa capacità di memorizzazione. Se ha scritto

map (*c) xs 

non si verificherebbe alcuna memorizzazione. (Deve essere scritto come (map (c*) xs)) Ora che ci penso, non sono del tutto sicuro di come sarebbe GHC riscrivere la versione (*c) da quando sono al curry (*) Ma ho fatto un test rapido per verificare che sia (*c) e (c*) funzionare come previsto.: il primo argomento a *, mentre (*c) fa c il secondo argomento a *. Quindi il problema è che non è ovvio come si dovrebbe scrivere la moltiplicazione per garantire la memoizzazione.È questo solo un lato negativo intrinseco alla notazione infisso (e implicita che gli argomenti * sono simmetrici)?

il secondo problema meno pressante è che il caso whe mappiamo (v *) su un elenco di scalari. In questo caso, (si spera) il fft di v sarebbe calcolato e memorizzato, anche se non è necessario poiché l'altro multiplo è uno scalare. C'è un modo per aggirare questo?

Grazie

+0

compilazione con '-O2' e benchmarking con codice compilato? – jberryman

+0

Sì, ma ho controllato il core, e non sembra che stia compilando nulla. – crockeea

+0

BTW, 'let c '= fft c nella mappa (c *) xs' fa * not * calcola un' fft', perché Haskell è pigro. "Non c'è mai usato, quindi non verrà mai calcolato. – luqui

risposta

2

Credo che il pacchetto stable-memo potrebbe risolvere il tuo problema. Si memoizes valori che non utilizzano l'uguaglianza, ma per identità di riferimento:

Mentre la maggior parte combinatori memo Memoize basato sulla parità, stabile-memo vuol basa sul fatto che lo stesso identico argomento è stato passato alla funzione precedente (cioè, è lo stesso argomento in memoria).

E diminuisce automaticamente i valori memoized quando le chiavi sono garbage collection:

stabile-memo non mantiene le chiavi è visto finora, che consente loro di essere garbage collection se vogliono non più usato. I finalizzatori vengono posizionati per rimuovere le voci corrispondenti dalla tabella dei memo se ciò accade.

Quindi, se si definisce qualcosa come

fft = memo fft' 
    where fft' = ... -- your old definition 

si otterrà più o meno quello che ti serve: Calling map (c *) xs sarà Memoize il calcolo della FFT all'interno della prima chiamata a (*) e viene riutilizzata per le chiamate successive a (c *). E se c è spazzatura, lo è anche fft' c.

Vedi anche this answer per How to add fields that only cache something to ADT?

+0

Farò sicuramente un tentativo, ma non hai idea del perché "l'uguaglianza" non funzioni per la memoizzazione? – crockeea

+0

@Eric Non c'è niente di sbagliato nell'uguaglianza. È solo che questa libreria ha intrapreso un percorso diverso e lavora su un livello basso, confrontando i riferimenti. Sebbene ciò richieda probabilmente di basarsi sull'API di basso livello di GHC e di ridurre la portabilità del pacchetto, presenta alcuni vantaggi. In particolare, la sua stretta integrazione con la garbage collection (che è importante per il tuo compito). E non hai alcun vincolo sui tipi di memoizable - puoi memoizzare i tipi per i quali non hai un'istanza di 'Eq' o un'altra classe di tipo simile. –

+0

Sono d'accordo, tutte queste cose sono molto belle. La mia domanda era: perché dovrei credere che funzionerà, quando il mio tentativo di memoizzazione di "uguaglianza" fallì? O stai suggerendo che di fatto * non * ha fallito, ma che stava prendendo tempo lineare da usare? Credo che le mie scarse prestazioni si tradurranno in questo caso. – crockeea

1

posso vedere due problemi che possono impedire Memoizzazione:

In primo luogo, f ha un tipo di sovraccarico e funziona per tutti i Num le istanze. Pertanto, f non può utilizzare la memoizzazione a meno che non sia specializzato (che di solito richiede un pragma) o inline (che può avvenire automaticamente, ma è più affidabile con un pragma INLINE).

In secondo luogo, la definizione di (*) per Foo esegue il pattern matching sul primo argomento, ma f moltiplica con una sconosciuta c. Quindi, all'interno di f, anche se specializzato, non può verificarsi alcuna memoizzazione. Ancora una volta, dipende molto dal fatto che f sia in linea e che sia fornito un argomento concreto per c, in modo che possa apparire effettivamente la funzione di inlining.

Quindi penso che sarebbe di aiuto vedere esattamente come stai chiamando f. Si noti che se f viene definito utilizzando due argomenti, è necessario fornire due argomenti, altrimenti non può essere sottolineato. Aiuterebbe inoltre a vedere la definizione attuale di Foo, come quella che stai citando c e v che non sono nel campo di applicazione.

+0

Non riesco a vedere come tutto possa dipendere dal fatto che f sia in linea o meno. Non sto provando a memoize f stesso, solo la funzione che sto usando per mappare sulla lista. L'attuale def è: f :: (Num a) => [a] -> [[a]] -> [a] f xs = fold1 '(zipCon (+)). zipWith (\ a b -> mappa (a *) b) xs Vedo che ho perso i parametri fuori portata su Foo, ma i dati reali hanno 5 parametri e altrettanti costruttori. Penso che Foo trasmetta le informazioni rilevanti. Per essere più corretto, tuttavia, puoi immaginare di avere – crockeea

+0

'Foo = Vec Bool [Int] | Scalare Int'. – crockeea

+0

Allora, che ne pensi di essere memoized? Il '(a *)'? Ma come ho detto, non è possibile ridurlo, poiché (1) il tipo è troppo generico e (2) non sappiamo nulla di 'a', mentre la definizione di' (*) 'esegue prima una corrispondenza di modello. Quindi tutto dipende dal fatto che 'f' è specializzato o in linea. – kosmikus