3.2 “Back to the Future”
3.3. La scelta di un supporto al parallelismo
Come accennato al paragrafo 1.5.2, rivolgeremo particolare attenzione ai processori con architettu- ra multi-core. Questa sarà la nostra architettura target (di riferimento). In questo paragrafo verrà argomentata la scelta di adottare la libreria Intel Threading Building Blocks3 come supporto al pa-
rallelismo, nell’ipotesi di poggiare, appunto, su processori multi-core, ed escluderemo a priori solu- zioni che non siano conformi allo standard ISO C++.
La complessità indotta dal parallelismo richiede un supporto che permetta di esprime e di realizzare il controllo sulla concorrenza ad un qualche livello d’astrazione.
In ambito C++, diverse sono le scelte possibili. Programmare utilizzando un’interfaccia ai thread crudi, come l’interfaccia POSIX (lo standard Pthread4), o l’interfaccia ai thread offerta da una par-
ticolare piattaforma, è stata una scelta che molti programmatori di architetture a memoria condivisa hanno adottato. Ci sono tuttavia librerie wrapper che aumentano la portabilità, come Boost Thread, libreria che è stata inclusa nel Library Technical Report [C++TR104] dal comitato di standardizza- zione del C++.
Su architetture a memoria distribuita si adotta invece di frequente un modello ad ambiente locale, in cui l’interazione tra i processi avviene attraverso lo scambio di messaggi. In questo caso, una scelta diffusa è l’utilizzo di un’implementazione dello standard Message Passing Interface5 (MPI),
come MPICH6 e Open-MPI7.
Thread crudi ed MPI espongono il controllo del parallelismo al livello più basso. Essi rappresentano il linguaggio assembler per il parallelismo. Come tali, essi offrono la massima flessibilità, ma ad un alto costo in termini di sforzo di programmazione, tempo di debugging, e costi di manutenzione. Per programmare una macchina con architettura parallela, come i processori multi-core, occorre- rebbe l’abilità di esprimere il parallelismo senza dover gestire ogni dettaglio: problemi come la ge- stione ottimale di un pool di thread, e l’opportuna distribuzione del lavoro rispetto al bilanciamento del carico e l’utilizzo ottimale della cache, non dovrebbero essere le questioni al centro dell’atten- zione del programmatore mentre progetta ed esprime il parallelismo in un programma.
4 Dall’IEEE Portable Operating System Interface, POSIX, sezione 1003.1. 5 http://www.mpi-forum.org/
6 http://www.mcs.anl.gov/research/projects/mpich2/ 7 http://www.open-mpi.org/
Intel Threading Building Blocks (TBB) nasce proprio con l’intento di venire in aiuto nella creazione di applicazioni che vogliano prendere vantaggio dei nuovi processori multi-core, in modo efficiente e scalabile8, man mano che il numero dei core inseriti nel processore aumentano.
TBB è una libreria che supporta la programmazione parallela scalabile utilizzando codice C++ standard, senza richiedere linguaggi o compilatori speciali. L’abilità di poter utilizzare TBB vir- tualmente su un qualunque processore e sistema operativo dotato di un compilatore C++, rende tale libreria molto attraente.
Nel seguente sottoparagrafo sono esposti i vantaggi offerti da TBB, giustificando la scelta di adot- tarla come supporto al parallelismo per abilitare la futura versione di pCM all’elaborazione concor- rente da parte degli element.
3.3.1. Perché adottare Intel Threading Building Blocks?
L’obiettivo principale di un programmatore in un ambiente di calcolo moderno è la scalabilità [Vanneschi07b]: in altre parole, se n è il numero di core presenti in un processore multi-core, que- sto significa massimizzare l’utilizzo degli n core, all’aumentare di n. TBB facilita lo sviluppo di ap- plicazioni scalabili rispetto ai tradizionali pacchetti di supporto al multi-threading.
Come accennato sopra, ci sono una varietà di approcci alla programmazione parallela, che spaziano dall’uso di primitive di supporto al multi-threading dipendenti dalla piattaforma, a nuovi linguaggi o estensioni di linguaggi sequenziali con parole e sintassi riservate (per esempio, questo è il caso dell’API definita dalla specifica OpenMP9). Il vantaggio di Threading Building Blocks è che lavora
ad un livello più alto rispetto a quello dei thread crudi, senza richiedere linguaggi o compilatori eso-
8 Senza voler dare una definizione formale, ricordiamo che l’efficienza relativa esprime il grado (in percentuale) di uti- lizzo delle risorse di calcolo che compongono un sistema parallelo, mentre la scalabilità esprime quanto è accellerata la computazione (ovvero quanto aumenta la banda) rispetto al caso sequenziale.
tici. È infatti possibile utilizzare TBB con un qualunque compilatore conforme allo standard ISO C++.
Threading Building Blocks differisce dai tradizionali supporti al multi-threading nei seguenti punti: • TBB abilita la specifica dei task invece dei thread: molti supporti al parallelismo richiedono di
creare, relazionare e gestire i thread di un’applicazione. Programmare direttamente in termini di thread può essere tedioso e può portare facilmente a programmi inefficienti, di difficile lettura, debugging e manutenzione. Questo perché i thread sono costrutti di basso livello, troppo vicini al livello del sistema operativo. Programmare utilizzando direttamente i thread forza il programma- tore ad avere la responsabilità di mappare efficientemente i task10 logici, riconoscibili a livello
applicativo, nei thread forniti dal livello del sistema operativo. Diversamente, il runtime della li- breria TBB schedula automaticamente i task sui thread in un modo tale da utilizzare in modo effi- ciente le risorse di calcolo del processore.
Evitando di programmare sulla base del modello dei thread nativi, con TBB si ha un aumento immediato in termini di portabilità, semplicità di programmazione, codice sorgente più intuitivo, e miglior performance e scalabilità.
• TBB è compatibile con altri pacchetti di supporto al multi-threading: TBB può coesistere senza complicazioni con altri supporti al parallelismo. Questo evita di forzare il programmatore a sce- gliere un particolare supporto, escludendo tutti gli altri: è possibile quindi adottare TBB in un programma già in fase di sviluppo che faccia uso di un qualche supporto al parallelismo. Questo è proprio il nostro caso, dal momento che PortAudio si serve del supporto nativo ai thread per im- plementare la concorrenza tra il task di generazione audio (callback) e il task di controllo (main thread). Nessuna interferenza sarà provocata dall’uso di TBB nel nostro progetto.
10 Il concetto di task rientra in quello di modulo di elaborazione nella strutturazione a moduli [Vanneschi07b], dove per modulo di elaborazione si intende una qualunque entità di elaborazione attiva (dotata di autocontrollo), indipendente- mente dal livello a cui appartiene. Nel caso dei task, siamo al di sopra dell’astrazione dei thread del livello del sistema operativo.
• TBB incentiva la programmazione di soluzioni concorrenti data-parallel scalabili, e su stream con bilanciamento del carico: scomporre un programma in blocchi funzionali distinti ed assegnarne un thread separato per ciascun blocco è una soluzione che nella maggior parte dei casi non scala, perché tipicamente il numero dei blocchi è fisso. Questo è ciò che avviene quando si adottano forme di parallelismo su stream, come la forma pipeline. Diversamente, TBB incentiva la pro- grammazione data-parallel, abilitando più thread a lavorare concorrentemente su partizioni di una stessa collezione di dati. L’utente può controllare le modalità di partizionamento e la grana della partizione da applicare sui dati, o lasciare che TBB valuti dinamicamente la dimensione del parti- zionamento sulla base di euristiche di bilanciamento del carico.
Per quanto riguarda il parallelismo su stream, TBB offre un supporto alla forma pipeline con re- plicazione automatica degli stadi, in modo tale da bilanciare dinamicamente il carico, là dove le risorse disponibili lo consentano.
Inoltre, TBB evita il verificarsi di colli di bottiglia, come l’utilizzo di una coda di task globale a cui ciascun thread debba accedere in modo mutuamente esclusivo per poter ottenere un nuovo task.
• TBB fa affidamento sulla programmazione generica [Järvi05]: librerie tradizionali specificano interfacce in termini di tipi di dato specifici o classi base. TBB utilizza invece la programmazione generica. L’essenza della programmazione generica è di massimizzare le prestazioni limitando i vincoli imposti sui tipi di cui un algoritmo o una struttura dati è parametrica. La libreria Standard Template Library (STL) del C++ è un classico esempio di applicazione della programmazione ge- nerica, in cui le interfacce sono specificate da requisiti sui tipi, espressi sotto forma di espressioni valide (come nel caso dello standard ISO C++), o di pseudo-signatures (come nel caso del docu- mento di specifica della TBB [Intel07]).
za: le interfacce generiche permettono al programmatore di specializzare i componenti rispetto alle sue particolari necessità, con zero conseguenze sulle prestazioni.