I chip e le loro performance sono aspetti importanti per determinare le prestazioni di un sistema, ma il software deve essere scritto in modo da consentire le ottimizzazioni necessarie per un’esecuzione efficiente su queste nuove architetture. Come è possibile sfruttare al meglio i P-Core e gli E-Core di Alder lake senza scrivere programmi che dipendano interamente dall’architettura del processore?
Anche in questo caso, in aiuto potrebbe accorrere l’intelligenza artificiale. Ma partiamo dalle basi e, quindi, dall’evoluzione costante – non senza problemi – dei chip.
Carenza di chip: ecco perché il peggio (per i consumatori) deve ancora venire
L’evoluzione dei chip non si ferma, nonostante la crisi
Negli ultimi mesi la difficoltà di approvvigionamento di chip da parte dell’industria automobilistica nonché dei produttori di elettronica ha riacceso l’attenzione sul processo di produzione di chip e sull’importante ruolo che questi giocano sull’industria globale.
Nonostante questo rallentamento produttivo, il design di questi microscopici universi fatti di decine di miliardi di transistor prosegue in un continuo susseguirsi di design innovativi in discontinuità con la tradizione. È il caso di Intel che introduce Alder lake per i desktop, primo design della casa americana che introduce core misti su una CPU, ovverosia alcuni core che sono capaci di potenza di calcolo (P-Core) e altri più semplici ma più efficienti dal punto di vista energetico (E-Core). Un altro esempio è l’annuncio della nuova generazione M1 di Apple con le versioni Pro e Max basate sul set di istruzioni ARM invece che x86.
Più in generale la pluralità di architetture che si affacciano al panorama odierno ci riporta improvvisamente nel futuro riproponendo allo stesso tempo problemi a cui abbiamo già assistito in passato: al variare delle architetture e dei set di istruzioni delle CPU diviene più difficile assicurare la disponibilità del software su tutte le piattaforme. Certo, rispetto agli anni ’90, beneficiamo tutti dell’eredità di Netscape e Sun Microsystems, la prima che ci ha consegnato il Web browser piattaforma sempre più utilizzata per realizzare applicazioni portabili capaci di eseguire su CPU e architetture differenti, la seconda che aveva introdotto Java come piattaforma per scrivere applicazioni capaci di eseguire su qualsiasi sistema e il cui arrivo ha spinto Microsoft a sviluppare la piattaforma .NET tornata a vivere una seconda vita dopo il passaggio ad una nuova vita multipiattaforma e open source.
Eppure, sempre più complesse, fatte di processori con sempre più core, di acceleratori grafici programmabili (GPU) ora alla base del Deep Learning, aspetto chiave dell’intelligenza artificiale (AI) odierna, e di altri dispositivi programmabili che supportano la CPU che diviene sempre più spesso un direttore di orchestra piuttosto che, come si diceva un tempo, l’”unità centrale di elaborazione”.
Allo stesso tempo concetti chiave come il disco rigido, messo per decenni in contrapposizione con la memoria RAM in quanto lentissimo ma capace di preservare l’informazione in assenza di alimentazione, divengono fluidi con dischi basati su tecnologia allo stato solido che cominciano a sembrare memorie lente piuttosto che dischi veloci, con la tecnologia Intel Optane che addirittura consente di avere delle memorie persistenti installate insieme alla tradizionale RAM e che, sebbene più lente, consentono di raggiungere taglie di memoria altrimenti difficili da raggiungere su desktop e server.
In questo quadro sempre più articolato troviamo i linguaggi di programmazione in cui vengono sviluppate librerie e applicazioni la cui esecuzione dipende spesso da un software chiamato compilatore capace di convertire un programma espresso in un certo linguaggio di programmazione in un altro programma equivalente ma in un linguaggio diverso, come ad esempio il linguaggio macchina oppure il bytecode di sistemi di esecuzione come la Java Virtual Machine o il Common Language Runtime di .NET.
Trasformazioni di programmi sempre più intricate e complesse
Chi ha studiato come me informatica negli anni ’90 ha appreso concetti molto rassicuranti come quello di sistemi a livelli, e i compilatori erano quello C/C++, quello Pascal e quello Fortran. In quegli anni il compilatore era “semplicemente” un software capace di tradurre il tuo programma nel tanto agognato file eseguibile. Oggi il panorama è ben più complesso e concetti originariamente introdotti da Java come bytecode e Just-In-Time (JIT) hanno iniziato una trasformazione dei linguaggi di programmazione e delle loro architetture capaci di sfruttare la crescente potenza di calcolo consentendo una maggior espressività e dinamismo al programmatore senza rinunciare (troppo) alla performance.
Negli stessi anni il Web browser ci ha consegnato il linguaggio di programmazione JavaScript che, insieme al linguaggio Perl, ha inaugurato una nuova stagione di linguaggi di programmazione (allora riferiti come linguaggi di scripting) che sono cresciuti fino a consentire la realizzazione di sistemi complessi e non di semplici script come in origine. Python è figlio di questa linea di sviluppo ed è divenuto il linguaggio dei programmatori AI.
Più recentemente l’introduzione di piattaforme di compilazioni modulari come il progetto open source LLVM, promosso in origine anche da Apple, ha consentito di realizzare compilatori decisamente curiosi come il sistema emscripten per compilare programmi con grafica 3D scritti in C++ in Javascript (avete letto bene: compilare C++ in Javascript!) con l’obiettivo di eseguire giochi 3D all’interno dei web browser (provare ad esempio il gioco demo Bananabread promosso dalla Mozilla Foundation).
Tra le catene di trasformazione di programmi più curiose trovo sicuramente interessante quella usata da Unity 3D, popolare piattaforma per lo sviluppo di videogiochi, che per compilare un gioco per il Web parte da C# che viene compilato in linguaggio intermedio MSIL (l’equivalente .NET del bytecode di Java), poi convertito in C++ per poi usare emscripten e convertirlo per il Web.
Questa catena sovverte tutti i principi che si insegnavano negli anni ’90 (e prima), il linguaggio target della compilazione è infatti Javascript e non linguaggio macchina per la generazione di un eseguibile.
Infine, il linguaggio TypeScript ha reso popolare la trasformazione di programmi tra due linguaggi molto simili: TypeScript non è altro che una versione con un sistema di tipi di JavaScript e il compilatore non fa altro che convertire TypeScript in Javascript. Chiamare il programma di conversione compilatore sembrava troppo impegnativo, per questo si è cominciato a riferire come transpiler questa forma di traduzione di un programma scritto in un linguaggio in un linguaggio di pari livello di astrazione.
Ma quanto mi costa?
L’efficienza delle CPU e delle GPU è tale da non far notare la diversità di prestazioni tra programmi scritti in linguaggi differenti. Si presta quindi meno attenzione al fatto che un’applicazione non sia compilata nativamente ma magari semplicemente interpretata. Va però detto che il consumo di energetico di un programma efficiente è inferiore a quello di un programma meno efficiente, e nell’era del Cop26 è necessario cominciare a favorire lo sviluppo di software meno avaro di corrente, capace anche di estendere la durata della batteria di un dispositivo mobile.
Un benchmark popolare di linguaggi di programmazione è lo Shootout di Debian che confronta le prestazioni di numerosi linguaggi di programmazione usando vari algoritmi implementati nei differenti linguaggi.
Come si può osservare dal grafico tra l’esecuzione del codice C/C++ e quella di Ruby c’è un fattore 300x nelle performance. Python 3 è tra le 30 e le 50 volte più lento del C, sicuramente molto rispetto anche rispetto a Javascript (NodeJS) che è “solo” tra le 3 e le 5 volte più lento del C.
La consapevolezza delle prestazioni del linguaggio scelto in relazione ai consumi energetici sarà un aspetto sempre più importante nei prossimi anni.
E… a scuola?
L’insegnamento della programmazione a scuola è sempre stato un argomento molto complesso. Come è possibile dare una formazione sulla programmazione che duri più del linguaggio di moda al momento? Come diceva Douglas Hofstadter in “Gödel, Escher, Bach: un’eterna ghirlanda brillante” un linguaggio di programmazione è come uno spartito musicale: consente di rappresentare idee, e linguaggi di programmazione differenti riescono a rappresentare più o meno bene specifici pattern esattamente come alcune figure musicali si esprimono meglio in una chiave musicale piuttosto che in un’altra.
Troppo spesso però la scuola italiana si è fermata agli anni ’90, dando una visione dei sistemi e della loro programmazione molto lontana dalla realtà. L’insegnamento di linguaggi come C/C++ è oggi meno “professionalizzante” di quanto non fosse allora, e la complessità del linguaggio rischia di distogliere i ragazzi dal quadro complessivo. È sicuramente difficile però individuare una linea “giusta” da seguire in un panorama in continua evoluzione dove per comprendere un linguaggio è sempre più necessario comprendere anche le sue interazioni con il sistema operativo e l’hardware.
Un aiutino da casa?
L’evoluzione dei compilatori, delle CPU e dei sistemi operativi ha fatto sì che sia sempre più difficile ottenere la generazione di codici ottimizzati per particolari architetture. Ecco che i produttori di Sistemi Operativi e quelli di Chip hanno cominciato a lavorare per rendere efficace l’esecuzione di programmi sulle nuove architetture, scaricando almeno in parte, il compilatore dall’onere di generare il codice sempre più ottimizzato per queste nuove piattaforme.
Conclusioni
L’efficienza dei programmi sarà nel futuro sempre più rilevante per ridurre i consumi energetici dei nostri computer, e l’interazione tra programma, sistema operativo, e hardware determinerà molto di questa efficienza.
Sicuramente l’intelligenza artificiale ci aiuterà a scrivere programmi migliori, è una delle nuove funzionalità di Microsoft Visual Studio 2022 e dalle prime prove che ho fatto la capacità del sistema di proporre frammenti di codice da inserire nel programma, influenzando lo stile di programmazione, sembra funzionare in modo ottimale. Tutto questo ci avvicina alle “macchine che generano tabelle per altre macchine” previste da Alan Turing a cavallo tra gli anni ‘40 e ’50 dove le tabelle erano i programmi.