MapReduce: Volume III

Dove eravamo rimasti? Ah, si...all'implementazione concreta della classe astratta che permette l'esecuzione dell'algoritmo di MapReduce...

Come dicevamo nel precente puntata, per portare a termine il nostro scopo, ci faremo dare una mano dalla libreria "Task Parallel Library", o TPL. Tale libreria, in precedenza rilasciata come estensione del .NET Framework, a partire dalla versione 4.0 è stata incorporata nel framework stesso, ponendosi come approccio più efficace per la gestione di processi multi-thread e asincroni in generale.

Ma non perdiamo ulteriore tempo e vediamo il codice, per poi illustrare come sia nata la sua implementazione.

Naturalmente la classe astratta deve implementare i requisiti dell'interfaccia "IMapReduceProcessor"; esporre le proprietà che servono per "osservare" lo stato, e il metodo di "Execute" che rappresenta il cuore dell'algoritmo.

Come detto in precedenza, affichè MapReduce possa essere applicato, è necessario specificare il numero di "assistenti" (thread paralleli) e i "piani del palazzo" che andiamo ad elaborare (il dominio dati). Poichè sono informazioni vitali per la nostra esecuzione, definiamo che siano anche gli "input" del nostro costruttore: dopo le opportune validazione degli argomenti (mi raccomando, il corretto uso delle eccezioni e sempre fondamentale), andiamo a valorizzare due delle nostre tre proprietà definite dall'interfaccia.

Definiamo come "astratti" i metodi di "Process" (come detto il lavoro svolto da ogni assistente) e "Reduce" (il lavoro svolto del supervisore, una volta ottenuti i dati di ciascun assistente). Li abbiamo visti del precedente post, quindi non perdiamoci altro tempo...

Passiamo al "nocciolo" dell'argomento: il metodo "Execute". Prima cosa dobbiamo "sezionare" il nostro dominio dati, letteramente "tagliandolo" in parti più o meno uguali, in modo da assegnarne una porzione a ciascun assistente per l'esecuzione successiva. Far questo è banale: prendiamo il numero totale dei piani, e dividiamolo per il numero di assistenti definiti, ottenendo quindi la dimensione del singolo "pacchetto di dati" in carico ad ogni thread.

Per implementare il paradigma definito da TPL, è importante sapere che ogni operazione atomica asincrona è definita come "Task". Ciascuno dei nostri assistenti svolge due distinte operazioni atomiche: "conteggio" delle persone su ogni piano, e "consegna" dei risultati in mano al supervisore. Quindi predispongo due distinti array che rappresentano il task di "conteggio" e quello di "consegna". Per finire, predispongo un'area di memoria dove gli assistenti andranno a depositare i conteggi eseguiti.

Il seguito viene via facile: basta iterare su ciascun assistente, "tagliare la sua fetta di torta", definire i due task di conteggio e consegna, quindi "schierare" l'assistente in un array di Task, pronto per essere mandato in esecuzione.

Con una iterazione su tutte le task degli assistenti, provvedo ad avviarli, utilizzando poi l'istruzione "Task.WaitAll" per fare in modo che il thread principale di MapReduce resti in attesa finchè tutti gli assistenti non hanno "consegnato" i risultati parziali nelle aree di memoria prestabilite.

In ultimo faccio intervenire il supervisore che non fa altro che elaborare i "partial results" calcolati dagli assistenti e avviare la "riduzione" delle informazioni nel conteggio finale voluto.

Bene...tre post per arrivare alla conclusione...ma penso ne sia valsa la pena per descrivere un algoritmo che, come detto, pur nella sua semplicità mi ha salvato "capre e cavoli" non più di un paio di mesetti fa. Vi basti pensare che la sua semplice applicazione ha permesso di portare la famosa applicazione "legacy" di cui ho parlato nel primo post della serie, da un'esecuzione di circa un'ora e venti minuti, a soli 12 minuti. Un bel miglioramento, eh? :)

Un ultimo pensiero. MapReduce è una tecnica molto potente e molto efficace in certe situazioni, ma non in tutte. Come ogni altra deve essere dosata accuratamente, cercando di capire qual'è il giusto grado di parallelismo che si vuole dare (il numero di processori paralleli, per intenderci). Se la cosa viene usata a sproposito, è possibile che invece che migliorare le prestazioni della vostra applicazione, riusciate persino a peggiorarle per via dell'override che introdurrete.

Quindi, usate la testa e fate molto "tuning"...

Commenti

Post popolari in questo blog

Cancellazione fisica vs cancellazione logica dei dati

Restore di un database SQL Server in un container Docker

Costruire una DataSession custom con Chakra.Core