Sforzi enormi per sviluppare le applicazioni
Scrivere applicazioni efficienti (cioè veloci) per un supercomputer può essere molto difficile. La ragione principale sta nel fatto che una singola applicazione deve essere segmentata, in modo da poter essere distribuita su diversi processori. Fortunatamente, l'architettura dei processori offre due diverse soluzioni a questo ostacolo: il multithreading e la vettorizzazione. Due o quattro processori non rappresentano un gran problema, ma decine di migliaia sì. Il processo di segmentazione delle applicazioni può, quindi, avere un livello di difficoltà che va dal “gioco da ragazzi” al “quasi impossibile”.
Con le dovute scuse all'industria della grafica, la produzione di frame video rappresenta un esempio imbarazzante, già che ogni frame può essere prodotto in un processore diverso. Sarebbe anche possibile dividere ogni frame in diversi segmenti di codice, e una cosa davvero difficile sarebbe dividere codici privi di loops (ripetizioni); per fortuna quest'ultimo caso non è così comune, e non ci sono applicazioni adatte, almeno non in gran numero. Credo però che sia possibile segmentare anche applicazioni molto difficili. Bisogna pensarle nuovamente, gli algoritmi devono essere modificati, inserendo processi che soddisfino i nuovi bisogni. Il rovescio della medaglia, però, ci dice che il nuovo algoritmo è molto più complesso. La crescita della complessità segue la legge del contrappasso, e prima o poi si arriva ad un punto in cui non ne vale la pena.
E poi ci sono i compilatori, che trasformano la soluzione in un linguaggio che la macchina può capire, anche in un sistema a più processori. I compilatori moderni sono strumenti dalla molteplice utilità, usati sia per lo sviluppo delle applicazioni che per quello dei sistemi, ma c'è una salutare tendenza a dividere le due sintassi. Resta, tuttavia, molta strada da fare.
Per capire fino in fondo la complessità del linguaggio di un compilatore, ci vorrà uno sforzo cognitivo enorme. Molti compilatori sono realizzati per generare soluzioni eleganti, piuttosto che essere focalizzati sulla soluzione del problema in sé, il che mostra quanto lavoro creativo contengano. Quello di cui c'è realmente bisogno è una sintassi di alto livello che descriva l'algoritmo in generale, non i dettagli della sua soluzione. Questo offre a chi scrive il compilatore la libertà che serve per coniugare applicazioni e hardware.
Gli autori dei compilatori hanno fatto sforzi enormi per adattare le applicazioni alle forme dei supercomputers. Oggigiorno l'approccio tende a essere molto forzoso; gli sviluppatori cercano procedure che li portino direttamente ai gruppi di istruzioni (array), il che non sembra un mossa molto intelligente, visto che ogni volta che il “loop magico” non si trova, l'ostacolo sembra insuperabile. Sembra assurdo, ma gli sviluppatori che lavorano così sono moltissimi. Se, invece, si riesce a trovare una maniera generale per vettorizzare o costruire semplici array da loops che soddisfino le impostazioni di fabbrica dei processori, allora avrete trovate il biglietto per il club degli entrepreneur ricchi e famosi.
Gli algoritmi di una applicazione formano una sequenza logica che, normalmente, contiene numerosi segmenti, molti dei quali possono essere ulteriormente suddivisi in singoli threads. Questi ultimi possono essere eseguiti e sincronizzati tramite normali estensioni del sistema operativo; la velocità aumenta naturalmente, perché molti threads possono essere eseguiti parallelamente.
Con i codici iterativi, l'applicazione può essere distribuita su una struttura simile a quella delle pipeline dei processori. Il risultato è un aumento dell'interazione in parallelo, utile per evitare molti errori di sincronizzazione. Il problema, in questo caso, emerge quando l'applicazione non funziona bene nel multithreading. La soluzione sta nella segmentizzazione orizzontale dell'algoritmo, invece che in una serie verticale di istruzioni. Ovviamente un passo del genere presuppone che l'algoritmo sia ripensato da capo, ma il risultato può quasi sempre essere un multithreading efficiente.
Negli anni '70 lavoravo al CDC Advanced Design Laboratory, ed ero incaricato di scrivere un compilatore COBOL per il computer a vettori Star 100. Il set di istruzioni per quel computer era stato ricavato sul modello del libro di Ken Iverson “A Programming Language”. Uno degli architetti, Neil Lincoln, scrisse un compilatore FORTRAN privo di test o loop; consisteva di 147 operazioni su vettori isolati. Ogni operazione era una singola istruzione. Ovviamente, il compilatore correva come un treno, e Neil ricevette numerosi applausi. Lo racconto per spezzare una lancia in favore della creatività, sempre messa a rischio dai preconcetti, o meglio dalle idee preconfezionate
Quindi, se volete tenere un supercomputer lontano dall'ufficio, convincete gli amministratori che le applicazioni non devono essere toccate. Così non riusciranno mai a trovare un supercomputer che farà funzionare tutte le loro applicazioni, almeno non abbastanza da giustificare la spesa. Ma sarà facile convincere gli amministratori che ci vorrà un sacco di lavoro per rifare le applicazioni, e anche un sacco di denaro. Senza dimenticare il fatto che le applicazioni vanno aggiornate ogni volta che c'è un passo avanti nell'architettura. E magari il primo sviluppatore si è licenziato, e il codice sorgente non si trova. Di certo si tireranno indietro, di fronte ai costi aggiuntivi, all'aumento del personale e degli spazi necessari. Insomma, almeno in tempi brevi, possiamo garantire che il supercomputer non offre vantaggi economici, e quindi nessuno lo comprerà.
E però, se volete arricchirvi, trovate qualche buona soluzione per riscrive gli algoritmi per le applicazioni, sia attraverso compilatori migliori o attraverso un metodo efficace per ripensarli. O, meglio ancora, sviluppate un sistema all'interno del quale il problema possa essere posto in termini che possano essere tradotti direttamente in linguaggio macchina. Questo eviterebbe le difficoltà per comprendere soluzioni che possono essere più complesse della stessa architettura del sistema.