Dans mon billet précèdent nous avions évalué rapidement la librairie TPL en C#. Cette fois nous regarderons le côté natif de Visual Studio 2010. Celui-ci arrive avec une nouvelle librairie « Parallel Pattern Library » (PPL) permettant de paralléliser des applications natives en adhérence avec le style de la Standard Template Librairie (STL) tout en tirant parti des nouvelles caractéristiques du standard C++0x comme par exemple les expressions lambda. Pour illustrer cette librairie je vous propose l’étude de cas d’une simple méthode : l’exemple naïf d’une multiplication de matrices, si facile à comprendre (certains penseront que je manque d'imagination :-))

void MatrixMult(int size, double** m1, double** m2, double** result)

{

    for (int i = 0; i < size; i++) {

         for (int j = 0; j < size; j++) {

             double t = 0;

            for (int k = 0; k < size; k++) {

                 t += m1[i][k] * m2[k][j];

            }

             result[i][j] = t;

        }

    }

}

Figure 1: code à adapter à l'aide de la librairie PPL

Pour évaluer les performances de cette première version, nous allons solliciter la méthode MatrixMult, avec une valeur de size de 1000 sur une machine quadri-cœurs.

    printf("Starting execution ...\n");

    start = GetTickCount();

    MatrixMultSeq(SIZE, m1, m2, result);

    end = GetTickCount();

    printf("Elapsed time: %ds\n\n", (end-start)/1000);

Figure 2: Code pour mesurer le temps de traitement

On lance le programme en mode Release tout en observant le Task Manager pour constater l'occupation des cœurs.

image001 

Figure 3: Exécution du traitement en mode séquentiel

Nous constatons que le traitement occupe peu les cœurs disponibles avec un temps d'exécution de 9 secondes.

Pour profiter de tous les cœurs disponibles, modifions le code et ajoutons l'inclusion du fichier de la librairie Parallel Pattern Library (PPL) :

#include <ppl.h>

Puis nous altérons légèrement le code de la méthode MatrixMult afin de paralléliser le code. La méthode parallel_for prend en paramètres: un pas d'incrémentation, une taille de collection à itérer, une expression lambda contenant le corps du traitement. 

void MatrixMult(int size, double* m1[], double* m2[], double* result[])

{

    parallel_for(0, size, [&](int i) {

        for (int j = 0; j < size; j++)

         {       

             double t = 0;

            for (int k = 0; k < size; k ++)

             {

                t += m1[i][k] * m2[k][j];               

             }

            result[i][j] = t;

         }

    });

}

Figure 4: code adapté à l'aide de la libraire PPL

On remarque que le code reste lisible et sémantiquement compréhensible en comparaison au code de la figure 1 J

image002

Figure 5: Exécution du traitement en mode parallèle

On relance le programme et on observe une nouvelle fois le Task Manager. Cette fois les cœurs sont pleinement exploités avec un temps d’exécution de 2 secondes.

Finalement avec très peu d'efforts nous avons divisé par 4,5 le temps d’exécution de notre petit programme :-)

A bientôt,

Bruno

Bruno Boucard (boucard.bruno@free.fr)