Alessandro Del Sole's Blog

{ A programming space about Microsoft® .NET® }
posts - 1908, comments - 2047, trackbacks - 352

My Links

News

Your host

This is me! Questo spazio è dedicato a Microsoft® .NET®, di cui sono molto appassionato :-)

Cookie e Privacy

Disabilita cookie ShinyStat

Microsoft MVP

My MVP Profile

Microsoft Certified Professional

Microsoft Specialist

Xamarin Certified Mobile Developer

Il mio libro su VB 2015!

Pre-ordina il mio libro su VB 2015 Pre-ordina il mio libro "Visual Basic 2015 Unleashed". Clicca sulla copertina per informazioni!

Il mio libro su WPF 4.5.1!

Clicca sulla copertina per informazioni! E' uscito il mio libro "Programmare con WPF 4.5.1". Clicca sulla copertina per informazioni!

These postings are provided 'AS IS' for entertainment purposes only with absolutely no warranty expressed or implied and confer no rights.
If you're not an Italian user, please visit my English blog

Le vostre visite

I'm a VB!

Guarda la mia intervista a Seattle

Follow me on Twitter!

Altri spazi

GitHub
I miei progetti open-source su GitHub

Article Categories

Archives

Post Categories

Image Galleries

Privacy Policy

Introduzione a Parallel LINQ con Visual Basic

Premessa

 

Parallel LINQ (detto anche PLINQ) è una nuova implementazione di LINQ che si inquadra nel più ampio discorso del Parallel Computing, ossia quelle speciali forme tecnologiche volte a sfruttare le architetture multi-processore o i processori multi-core che oggi hanno ampia diffusione nel mondo dei personal computer. Per farvi un banale esempio, tutti i notebook che vengono venduti oggi ad uso domestico montano architetture almeno dual-core. Tipicamente, però, quasi mai sfruttiamo queste risorse hardware appieno e, conseguentemente, non sempre riusciamo a sfruttarle ai fini del miglioramento delle performance.

 

Pensiamo, ad esempio, ai cicli For..Each o alle operazioni asincrone eseguite tramite multi-threading che non sempre sono ottimizzati per lavorare sfruttando tutte le risorse a disposizione.

 

Mi riservo di introdurre in modo più argomentato il Parallel Computing in articoli/post successivi e, sebbene PLINQ sia solitamente illustrato come ultima parte delle trattazioni di questo genere, preferisco partire da qui perché offre un modo “visivo” di capire alcuni concetti.

 

Parallel Computing è un qualcosa che attualmente può essere utilizzato sia su VS 2008, installando delle apposite estensioni che dovrete scaricare prima di proseguire, oppure sulla Beta 1 di Visual Studio 2010. Esiste infatti un nuovo framework chiamato Parallel Task Library che farà parte integrante di .NET Framework 4.0 e che si occupa in modo moderno di gestire, in linea più generale, il discorso del multi-threading e delle opeazioni asincrone sfruttando proprio le architetture multi-core al fine di garantire migliori performance e notevole velocità in più.

 

Un esempio “empirico”

 

Pensate a quando andate a fare la spesa con un vostro familiare. Queste due persone rappresentano un’architettura dual-core. Tornate dalla spesa con quattro sacchetti della spesa e dovete fare diversi piani di scale per portarle in casa. Se solo uno dei due familiari si occuperà di questa operazione, dovrà portare in casa due sacchetti alla volta, quindi fare due viaggi, magari al secondo viaggio sarà più stanco per via delle scale e avrà impiegato il doppio del tempo.

Se invece anche l’altro familiare porta due sacchetti della spesa, si farà un solo viaggio, non si sovraccaricherà una singola persona, ci si impiegherà la metà del tempo, le risorse vengono sfruttate al 100%.

Il Parallel Computing fa questo: “scala” l’esecuzione di compiti su tutti i processori disponibili mentre Parallel LINQ “scala” l’esecuzione di query sfruttando le architetture multi-core.

 

IMPORTANTE: tipicamente potremo apprezzare PLINQ in operazioni di calcolo piuttosto “intensive”. Ad esempio, rendering grafico molto complesso, query con Join su centinaia di record, cicli lunghi e complessi. In operazioni di elaborazione molto ridotte, non è detto che il parallel computing sia sempre migliore rispetto alle consuete pratiche. Per tale motivo nella demo seguente simuleremo un’attività intensiva ricorrendo al threading.

 

Cosa c’è alla base

 

Alla base di PLINQ c’è una specifica estensione di Enumerable, che espone un metodo extension chiamato AsParallel e che consente l’esecuzione di query che sfruttano il parallelismo. In realtà esiste anche una nuova classe chiamata ParallelEnumerable, che però non mostrerò in questo post. Si tratta di una classe utile indubbiamente, ma che elabora i blocchi di dati in modo diverso e che, quindi, in un post introduttivo come questo, è meno indicata tenuto conto della familiarità che potete già avere con LINQ classico.

 

Prerequisiti

 

Come detto all’inizio, il codice che illustrerò si può utilizzare di base con Visual Basic 2010 Beta 1 senza aggiungere nulla. Se volete provarlo con Visual Studio 2008, dovete scaricare ed installare le apposite estensioni (in CTP) ed aggiungere un riferimento all’assembly System.Threading.dll che troverete nella cartella di installazione (C:\Programmi\Microsoft Parallel Extensions).

 

Il codice

 

Per la demo seguente ho utilizzato la Beta 1 di Visual Studio 2010, ma siete liberi di provare con VS 2008. Creiamo un semplice progetto per la Console in Visual Basic. Il nostro obiettivo è quello di ottenere l’elenco dei numeri dispari dato un insieme di numeri compresi tra 0 e 1000. Abbiamo innanzitutto bisogno di un metodo che verifichi se un numero è dispari o meno:

 

Imports System.Threading

 

    'Determina se è un numero dispari

    Private Function IsOdd(ByVal number As Integer) As Boolean

        'Simulo un lavoro intensivo

        Thread.SpinWait(1000000)

        Return (number Mod 2) <> 0

    End Function

 

La cosa da notare nel metodo è la simulazione del lavoro “intensivo” che ci permetterà di apprezzare PLINQ. Utilizziamo il metodo SpinWait che forzerà il thread attendere il numero di iterazioni specificato.

 

Ora considerate la seguente query:

 

    Sub Main()

 

        Dim range = Enumerable.Range(0, 1000)

 

        Dim query = From num In range

                    Where IsOdd(num)

                    Select num

 

Definiamo un insieme di numeri compresi tra 0 e 1000 tramite il metodo Enumerable.Range. La query, semplicissima, fa una selezione di tutti i numeri dispari. Ora immaginiamo di voler calcolare il tempo impiegato per eseguire tale operazione:

 

        'Un contatore

        Dim sw As Stopwatch = Stopwatch.StartNew

 

        'La query LINQ viene effettivamente eseguita qui (metodo Count)

        Console.WriteLine("Elementi rilevati: " + query.Count.ToString)

 

        sw.Stop()

 

        Console.WriteLine(sw.ElapsedMilliseconds.ToString)

 

        Console.ReadLine()

    End Sub

 

Possiamo utilizzare un oggetto StopWatch che funge da contatore, coi suoi metodi StartNew e Stop per avviare e terminare il contatore stesso. Il posizionamento del contatore non è casuale, nel senso che, come sappiamo, la query LINQ viene eseguita effettivamente solo quando invochiamo esplicitamente qualcosa su di essa, nel nostro caso il metodo Count.

 

Prima di avviare l’applicazione, lanciamo il Task Manager di Windows e impostiamolo come Always on Top (menu Opzioni) e attiviamo la scheda Prestazioni (Performance). Così potremo monitorare l’utilizzo di CPU.

 

Esecuzione del test con LINQ classico

 

Ok, ora avviamo l’applicazione premendo F5. Durante l’esecuzione, noterete che l’utilizzo della CPU è abbastanza limitato:

Questo chiaramente dipende da quante applicazioni avete in esecuzione, nel mio caso con 3 applicazioni aperte (Visual Studio, Word, Internet Explorer) l’utilizzo di CPU varia tra il 50% e il 60% durante l’elaborazione.

 

Al termine, otterremo il seguente risultato:

Abbiamo impiegato quindi circa 18 secondi per eseguire una query “intensiva”. Vediamo come Parallel LINQ può migliorare il tutto.

 

Esecuzione del test con Parallel LINQ

 

Il bello di Parallel LINQ è che modifica praticamente di nulla il nostro modo di pensare le query. Infatti, per utilizzare PLINQ, è sufficiente invocare il metodo AsParallel nella query come segue:

 

        Dim query = From num In range.AsParallel

                    Where IsOdd(num)

                    Select num

 

AsParallel restituisce una ParallelQuery(Of T). Se ora avviamo l’applicazione, potremo osservare come entrambe le CPU della mia macchina vengano utilizzate, testimoniato anche dal 100% di utilizzo:

Al termine, l’applicazione mostra il seguente risultato:

Circa 10 secondi, ossia 8 secondi in meno rispetto alla query LINQ classica. Non male vero? :-) In sostanza l’utilizzo di AsParallel ha fatto sì che l’elaborazione dei dati venisse scalata su tutti i processori disponibili, migliorando le performance.

 

Come detto, PLINQ non andrebbe usato ciecamente. Quantomeno, è errato pensare che ogni cosa “parallelizzata” sia migliore di una che non lo è. Dipende sempre dal contesto in cui si opera. Ulteriori informazioni possono essere reperite sul blog del Team di Parallel FX e nella libreria MSDN, ma come detto torneremo sull’argomento Parallel Computing e PLINQ.

 

Alessandro

Print | posted on martedì 7 luglio 2009 02:01 | Filed Under [ Visual Basic LINQ Visual Studio 2010 Parallel Programming ]

Powered by:
Powered By Subtext Powered By ASP.NET