Quali algoritmi possono essere espressi usando un linguaggio funzionale totale con operatori di dati paralleli?


11

Immagina un linguaggio di programmazione funzionale i cui soli tipi di dati sono scalari numerici e annidamenti arbitrari di array. La lingua non ha alcun mezzo di iterazione illimitata, quindi non sono consentiti:

  • loop espliciti (comunque non molto usati senza effetti collaterali)
  • ricorsione
  • funzioni arbitrarie di prima classe (senza combinatore y)

La lingua tuttavia ha:

  • funzioni di alto livello
  • ambito less lessico
  • flusso di controllo ramificato
  • funzioni matematiche e logiche scalari comuni
  • qualche semplice costruttore di array come fill (n, x) che crea un array n-element con valori identici x
  • soprattutto: un insieme limitato di operatori di ordine superiore che eseguono iterazioni strutturate parallele (come mappa, riduzione, scansione, tutte le coppie).

Per essere più specifici sugli operatori di dati paralleli:

  • y = mappa (f, x) => y [i] = f [i]
  • y = riduci (f, a, x) => y = f (a, f (y [p [0]], f (y [p [1]], ...))) per alcune permutazioni p
  • y = scan (f, a, x) => y [i] = riduci (f, a, y [0 ... i-1])
  • y = allpairs (f, x, y) => y [i, j] = f (x [i], y [j])

Potremmo avere anche altri operatori, ma per qualificarli dovrebbero avere un tempo di esecuzione polinomiale, essere implementabili in base a un modello ragionevole di calcolo parallelo dei dati e utilizzare nella maggior parte dello spazio polinomiale.

Esistono ovviamente alcuni costrutti che non possono essere espressi in questa lingua, come:

while f(x) > tol:
    x <- update(x)   

Cosa possiamo esprimere in questo sistema? Siamo limitati solo ai problemi di ricerca in FP? Possiamo catturare tutti gli algoritmi temporali polinomiali? Inoltre, esiste una serie minima di operatori per questa classe?

Risposte:


7

Potresti voler esaminare il vecchio linguaggio di programmazione NESL che ha preso sul serio queste idee. Il linguaggio si basa su operazioni su raccolte, essenzialmente elenchi ed elenchi di elenchi e così via, ma penso che anche alberi e grafici siano stati considerati, attraverso codifiche complicate. Alcune delle ricerche condotte in quel progetto (a metà degli anni '90) potrebbero aiutare a rispondere alla tua domanda. La tesi di dottorato (disponibile come libro) Guy E. Blelloch. Modelli vettoriali per il calcolo parallelo dei dati. MIT Press, 1990 potrebbe fornire alcune risposte. Era da un po 'di tempo che non lo guardavo.

Anche il lavoro svolto sul formalismo di Bird-Meertens (BMF) rientra in questa categoria, così come il linguaggio Charity . Dalla pagina di Wikipedia di Charity dice che la lingua non è Turing completa, ma può esprimere la funzione di Ackermann, il che significa che è più che ricorsivo primitivo. Sia BMF che Charity coinvolgono operatori come pieghe e scansioni (catamorfismi, anamorfismi, ecc.) E hanno le loro radici nella teoria delle categorie.

Insomma, la risposta imprecisa è che puoi esprimere parecchio.


1
NESL non è una lingua totale.
Per Vognsen,

Sono stato superficialmente a conoscenza di NESL per un po ', ma ho appena letto uno dei documenti di Blelloch in dettaglio per la prima volta. Grazie per il consiglio. NESL è abbastanza simile alla lingua che ho descritto sopra, tranne che, come notato da Per Vognsen, consente la ricorsione.
Alex Rubinsteyn,

Sono anche interessato alla scelta di operatori primitivi di Blelloch: map, dist (credo uguale a quello che ho chiamato 'fill'), lunghezza, array-read, array-write, scan, partition, flatten. Le primitive di NESL sono "complete" o c'è qualche altra operazione con un'implementazione parallela dei dati che non può essere espressa in modo efficiente usando queste?
Alex Rubinsteyn,

2
Rimuovi la ricorsione, quindi hai un linguaggio piuttosto espressivo, specialmente se consideri le pieghe e così via. Quindi, guardare BMF e il lavoro che segue potrebbe essere più interessante. Mi dispiace, ma non sono aggiornato in questo settore.
Dave Clarke,

7

La mia altra risposta ha evidenziato difetti nella lingua così com'è. In questa risposta fornirò maggiori dettagli sulle questioni relative alla coesistenza di pieghe e spiegazioni in una lingua totale. Quindi proporrò una risoluzione e mostrerò che tutti i problemi in P (e in effetti molti altri) possono essere risolti in questo linguaggio esteso.

Piegare in una lingua totale consuma elenchi:

fold :: (a -> b -> b) -> b -> List a -> b

Il dispiegamento in una lingua totale genera flussi, che sono potenzialmente illimitati:

unfold :: (b -> Maybe (a, b)) -> b -> Stream a

Sfortunatamente, liste e flussi vivono in mondi diversi, quindi questi operatori non possono essere composti. Abbiamo bisogno di una corrispondenza parziale:

stream :: List a -> Stream a
list :: Int -> Stream a -> List a

L'operatore stream incorpora un elenco in un flusso limitato. La funzione list estrae i primi n elementi (o meno se lo stream termina in precedenza) in un elenco. Abbiamo quindi la seguente equazione:

for all xs :: List a, xs == list (length xs) (stream xs)

Come ottimizzazione dell'efficienza possiamo tagliare completamente i flussi come una struttura di dati intermedia:

unfoldList :: Int -> (b -> Maybe (a, b)) -> b -> List a

Ora traccerò una prova che questo (con gli altri operatori già implicati nella domanda originale) ci consente di simulare qualsiasi algoritmo del tempo polinomiale.

Per definizione, una lingua L è in P quando c'è una macchina di Turing M e un polinomio p tale che l'appartenenza di x in L può essere decisa eseguendo M al massimo p (n) iterazioni dove n = | x |. Secondo un argomento standard, lo stato del nastro della macchina nell'iterazione k può essere codificato con un elenco di lunghezza al massimo 2k + 1, anche se il nastro della macchina è infinito.

L'idea è ora di rappresentare la transizione di M come funzione dagli elenchi agli elenchi. L'esecuzione della macchina verrà eseguita spiegando lo stato iniziale con la funzione di transizione. Questo genera un flusso di elenchi. L'assunto che L sia in P significa che non abbiamo bisogno di guardare oltre gli elementi p (n) nel flusso. Quindi possiamo comporre lo svolgersi con list p(n)per ottenere un elenco finito. Infine, lo pieghiamo per verificare se la risposta al problema decisionale era sì o no.

Più in generale, questo dimostra che ogni volta che abbiamo un algoritmo il cui tempo di terminazione può essere limitato da una funzione calcolabile nella lingua, possiamo simularlo. Questo suggerisce anche perché qualcosa come la funzione di Ackermann non può essere simulato: è legato da solo, quindi c'è un problema con pollo e uova.


4

È un linguaggio molto confuso. Prova a programmare la funzione fattoriale:

fact 0 = 1
fact n = n * fact (n-1)

Il problema è che la tua lingua ha solo pieghe ma senza spiegazioni. Il modo naturale di esprimere fattoriale è comporre una spiegazione di n nella lista [1, 2, ..., n] con la piegatura che la strappa mentre si moltiplica.

Sei davvero interessato a questo linguaggio specifico o alla programmazione funzionale totale in generale? È ovvio che la tua lingua può al massimo esprimere algoritmi a tempo polinomiale. Il sistema F (calcolo lambda polimorfico) può esprimere facilmente mostri come la funzione di Ackermann. Anche se il tuo interesse è per gli algoritmi del tempo polinomiale, hai spesso bisogno della stanza del gomito superpolinomiale per esprimerli in modo naturale.

Modifica: Come sottolinea Dave, NESL è uno dei linguaggi di programmazione fondamentali paralleli ai dati paralleli, ma è molto lontano dal totale (non ci prova nemmeno). La famiglia APL aveva una traccia parallela di evoluzione e ha un sottoinsieme algebrico totale (evitare gli operatori a virgola fissa). Se il focus della tua domanda è la totalità, David Turner ha scritto alcuni buoni articoli in questo settore, sebbene non specificamente sulla programmazione parallela ai dati.


Scusate, la mancanza di operatori che si svolgono è una svista da parte mia ... Volevo aggiungerne uno ma ho dimenticato di metterlo nel post. Non sono necessariamente interessato a questo linguaggio specifico, ma piuttosto all'espressività e ai limiti del calcolo parallelo dei dati.
Alex Rubinsteyn,

Il dispiegamento è naturalmente valutato in base al flusso, non al valore in serie, il che costituisce un problema di base con la correzione in linguaggi rigorosamente rigidi.
Per Vognsen,
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.