λ -calculus: qual è il più efficiente nella rappresentazione in memoria delle funzioni?


9

Vorrei confrontare le prestazioni delle strutture di dati codificate in funzione (Church's / Scott's) e codificate in modo classico (assemblatore / C).

Ma prima di farlo devo sapere quanto è efficiente / può essere la rappresentazione della funzione in memoria. Naturalmente la funzione può essere parzialmente applicata (ovvero chiusura).

Sono interessato sia all'algoritmo di codifica corrente che i linguaggi funzionali popolari (Haskell, ML) utilizzano sia a quello più efficiente che può essere raggiunto.


Punto di bonus: Esiste ad esempio la codifica che Maps funzione interi a interi nativi codificati ( short, intecc in C). È anche possibile?


Apprezzo l'efficienza in base alle prestazioni. In altre parole, più efficiente è la codifica, meno influenza le prestazioni di calcolo con strutture di dati funzionali.


Tutti i miei tentativi di Google sono falliti, forse non conosco le parole chiave giuste.
Ford O.

Puoi modificare la domanda per chiarire cosa intendi con "efficiente"? Efficiente per cosa? Quando si richiede una struttura dati efficiente, è necessario specificare quali operazioni si desidera poter eseguire sulla struttura dati, poiché ciò influisce sulla scelta della struttura dati. O vuoi dire che la codifica è il più efficiente possibile in termini di spazio?
DW

1
Questo è abbastanza ampio. Ci sono molte macchine astratte per il calcolo lambda che mirano a eseguirlo in modo efficiente (vedi ad esempio SECD, CAM, Krivine's, STG). Inoltre, è necessario considerare i dati codificati Church / Scott, il che pone più problemi. Ad esempio negli elenchi codificati dalla Chiesa, l'operazione di coda deve essere O (n) invece di O (1). Penso di aver letto da qualche parte che l'esistenza di una codifica per elenchi nel Sistema F con operazioni di testa e coda O (1) era ancora un problema aperto.
chi,

@DW Sto parlando di prestazioni / spese generali. Ad esempio, con un'efficace mappatura della codifica sull'elenco della chiesa e l'elenco di Haskell dovrebbe richiedere lo stesso tempo.
Ford O.

Prestazioni per quali operazioni? Cosa vuoi fare con le funzioni? Vuoi valutare queste funzioni su un valore? Una volta o valutare la stessa funzione su molti valori? Fare qualcos'altro con loro? Stai solo chiedendo come compilare una funzione (scritta in un linguaggio funzionale) in modo che possa essere eseguita nel modo più efficiente possibile?
DW

Risposte:


11

Il fatto è che non c'è davvero molto margine di manovra in termini di codifica delle funzioni. Ecco le principali opzioni:

  • Riscrittura di termini: memorizzi le funzioni come i loro alberi di sintassi astratti (o una loro codifica. Quando chiami una funzione, attraversi manualmente l'albero di sintassi per sostituire i suoi parametri con l'argomento. Questo è facile, ma terribilmente inefficiente in termini di tempo e spazio .

  • Chiusure: hai un modo di rappresentare una funzione, forse un albero di sintassi, un codice macchina più probabile. E in queste funzioni, fai riferimento ai tuoi argomenti per riferimento in qualche modo. Potrebbe essere un offset del puntatore, potrebbe essere un numero intero o un indice De Bruijn, potrebbe essere un nome. Quindi si rappresenta una funzione come chiusura : la funzione "istruzioni" (albero, codice, ecc.) Accoppiata con una struttura di dati contenente tutte le variabili libere della funzione. Quando una funzione viene effettivamente applicata, in qualche modo sa come cercare le variabili libere nella sua struttura di dati, usando ambienti, aritmetica del puntatore, ecc.

Sono sicuro che ci sono altre opzioni, ma queste sono quelle di base, e sospetto che quasi ogni altra opzione sarà una variante o ottimizzazione della struttura di chiusura di base.

Quindi, in termini di prestazioni, le chiusure funzionano quasi universalmente meglio della riscrittura a termine. Delle variazioni, quale è meglio? Dipende molto dal tuo linguaggio e dalla tua architettura, ma sospetto che il "codice macchina con una struttura contenente var gratuiti" sia il più efficiente. Ha tutto ciò di cui ha bisogno la funzione (istruzioni e valori) e nient'altro, e la chiamata non finisce per attraversare a lungo termine.

Sono interessato sia all'uso dell'algoritmo di codifica corrente che dei linguaggi funzionali popolari (Haskell, ML)

Non sono un esperto, ma sono il 99% della maggior parte dei gusti ML che usano alcune varianti delle chiusure che descrivo, anche se con alcune ottimizzazioni probabilmente. Vedi questo per una prospettiva (possibilmente obsoleta).

Haskell fa qualcosa di un po 'più complicato a causa della valutazione pigra: usa la riscrittura dei grafici senza tag senza spin .

e anche in quello più efficiente che può essere raggiunto.

Qual è il più efficiente? Non esiste un'implementazione che sarà la più efficiente su tutti gli input, quindi otterrai implementazioni che sono efficienti in media, ma ognuna eccellerà in diversi scenari. Quindi non esiste una classifica definita della più o meno efficiente.

Non c'è magia qui. Per memorizzare una funzione, è necessario memorizzare i suoi valori liberi in qualche modo, altrimenti si codificano meno informazioni rispetto alla funzione stessa. Forse puoi ottimizzare alcuni dei valori gratuiti con una valutazione parziale ma è rischioso per le prestazioni e devi fare attenzione per assicurarti che questo si fermi sempre.

E, forse, puoi utilizzare una sorta di compressione o un algoritmo intelligente per ottenere efficienza nello spazio. Ma poi stai scambiando tempo per lo spazio o sei nella situazione in cui hai ottimizzato per alcuni casi e rallentato per altri.

È possibile ottimizzare per il caso comune, ma ciò che il caso comune è in grado di cambiare il linguaggio, campo di applicazione, ecc Il tipo di codice che è veloce per un videogioco (numero macinare, cicli stretti con ampio ingresso) è probabilmente diverso da quello ciò che è veloce per un compilatore (traverse di alberi, liste di lavoro, ecc.).

Punto bonus: esiste una codifica tale che le mappe funzionano con numeri interi codificati in numeri interi nativi (short, int etc in C). È anche possibile?

No, non è possibile. Il problema è che il calcolo lambda non consente termini introspetti. Quando una funzione accetta un argomento con lo stesso tipo di un numero di Chiesa, deve essere in grado di chiamarlo, senza esaminare la definizione esatta di quel numero. Questa è la cosa con le codifiche della Chiesa: l' unica cosa che puoi fare con loro è chiamarle, e puoi simulare tutto ciò che è utile con questo, ma non senza costi.

Ancora più importante, gli interi occupano ogni possibile codifica binaria. Quindi se i lambda fossero rappresentati come numeri interi, non avresti modo di rappresentare i lambda non numerali della chiesa! Oppure, dovresti introdurre una bandiera per indicare se un lambda è un numero o meno, ma probabilmente qualsiasi efficienza che desideri è probabilmente uscita dalla finestra.

EDIT: Da quando ho scritto questo, sono venuto a conoscenza di una terza opzione per l'implementazione di funzioni di ordine superiore: defunzionalizzazione . Qui, ogni chiamata di funzione si trasforma in una grande switchaffermazione, a seconda dell'astrazione lambda data come funzione. Il compromesso qui è che si tratta di un'intera trasformazione del programma: non è possibile compilare parti separatamente e quindi collegarle in questo modo, poiché è necessario disporre in anticipo del set completo di astrazioni lambda.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.