Lost in the matrix

C/C++/C#/Java, Multithreading

Ceci est une version très basique de la fonction Ping.
Elle ne gère pas tous les cas, mais je vais poster une version améliorée bientôt.

#include <winsock2.h>
#include <Ipexport.h>
#include <icmpapi.h>

#pragma comment(lib, "iphlpapi.lib")
#pragma comment(lib, "ws2_32.lib")

int IsPingable(const char *host, int timeout)
{
 IP_OPTION_INFORMATION ioi;
 ICMP_ECHO_REPLY       ier;
 LPHOSTENT             Host;
 WSADATA               wsaData;
 IN_ADDR               iaAddr;
 HANDLE                hIcmp;

 if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
 {
  WSACleanup();
  return (0);
 }
 iaAddr.s_addr = inet_addr(host);
 if (iaAddr.s_addr == INADDR_NONE)
  Host = gethostbyname(host);
 else
  Host = gethostbyaddr((char *)&iaAddr, sizeof(IN_ADDR), AF_INET);
 if (Host == NULL)
 {
  WSACleanup();
  return (0);
 }
 hIcmp = IcmpCreateFile();
 ioi.Ttl = 255;
 ioi.Tos = 0;
 ioi.Flags = 0;
 ioi.OptionsSize = 0;
 ioi.OptionsData = NULL;
 IcmpSendEcho(hIcmp, *(DWORD *)(*Host->h_addr_list), NULL, 0, &ioi, &ier, sizeof(ICMP_ECHO_REPLY), timeout);
 IcmpCloseHandle(hIcmp);
 WSACleanup();
 return (ier.Status == 0);
}
Exemple:
int main(int argc, char **argv)
{
 printf("IsPingable : %d\n", IsPingable("clustrmaps.com", 5000));
 getchar();
 return (EXIT_SUCCESS);
}

Aujourd'hui, je suis tombé sur un problème avec le contrôle PropertyGrid, car je voulais éditer les propriétés d'un objet ainsi que ses sous-objets. Toutefois, j'ai eu quelques soucis, car la PropertyGrid ne permet pas (de base) d'éditer les sous-objets et ceux-ci apparaissent grisés.

Voici mon code:

using System.Drawing;
using System.Windows.Forms;

namespace WindowsFormsApplication
{
    public partial class PropertyGridTest : Form
    {
        public PropertyGridTest()
        {
            InitializeComponent();
            this.propertyGrid1.SelectedObject = new Car();
        }
    }

    public class Driver
    {
        public string Name { get; set; }
        public bool HasDriverLicence { get; set; }
    }

    public class Car
    {
        public Car()
        {
            this.Driver = new Driver()
            {
                Name = "Johannes Fetz",
                HasDriverLicence = false
            };
            this.Color = Color.Red;
            this.Kind = "Sedan";
        }

        public string Kind { get; set; }
        public Color Color { get; set; }
        public Driver Driver { get; set; }
    }
}
qui donne le résultat suivant:
Après quelques recherches, j'ai trouvé comment résoudre ce problème. En effet, il suffit d'indiquer à la PropertyGrid que "Driver" est un objet "Expandable". Pour cela, il faut ajouter un using :
using System.ComponentModel;
et un attribut au dessus de la classe "Driver" :
[TypeConverterAttribute(typeof(ExpandableObjectConverter))]
public class Driver
{
Ce qui donne le résultat suivant:

Voici une petite démonstration d'une implémentation des delegates que j'ai fait en langage C :)
Dans ce code, une fonction est déclarée en locale avec un nom unique, composé du mot
"__delegate__" et du numéro de la ligne courrante (__LINE__). Puis, l'adresse est stockée
dans la variable de l'utilisateur sous forme d'un pointeur sur fonction de type "Delegate".

#include <stdio.h>

#define CONCAT2(s1, s2) s1##s2
#define CONCAT(s1, s2) CONCAT2(s1, s2)
#define NewDelegate(Ptr, Expr) void \
        CONCAT(__delegate__, __LINE__)() Expr \
        Ptr = (Delegate)&CONCAT(__delegate__, __LINE__)

typedef void (*Delegate)(void);

void    PseudoDelegateTest()
{       
        int  i;
        Delegate my_delegate;

        NewDelegate(my_delegate,
        {
           printf("i = %d\n", i);
        });

        for (i = 0; i < 5; ++i)
            my_delegate();
}

int main(int argc, char **argv)
{
  PseudoDelegateTest();
  getchar();
  return (0);
}


Cela fait pas mal de temps que je m'intéresse à cet éditeur dont les possibilités sont infinies. En effet, j'ai eu l'occasion grâce à un ami, de le voir à l'oeuvre. Toutefois, il n'est pas toujours évident d'utiliser un autre éditeur quand on est habitué à un autre (personnellement j'utilise Emacs). Cet éditeur offre réellement une plus-value pour celui qui le maîtrise. Notamment grâce à son système de macros très bien pensé. Ainsi, j'ai appris les commandes de base et j'ai rédigé un document avec la liste des commandes élémentaires pour commencer avec Vim.


Voici le lien vers le document complet (pdf ou txt) : Commandes élémentaires de Vim


Lien vers le site officiel de Vim : www.vim.org

A la place de :

ConnectionInfo crConnectionInfo = new ConnectionInfo();
crConnectionInfo.Type = ConnectionInfoType.SQL;
crConnectionInfo.ServerName = "ODBCsource";
crConnectionInfo.DatabaseName = "DataBase";
crConnectionInfo.UserID = "Login";
crConnectionInfo.Password = "Password";
foreach (CrystalDecisions.CrystalReports.Engine.Table tmpTable in doc.Database.Tables)
{
     tmpTable.LogOnInfo.ConnectionInfo = crConnectionInfo;
     tmpTable.ApplyLogOnInfo(tmpTable.LogOnInfo);
     tmpTable.Location = tmpTable.Name;
}
Il suffit de faire :
doc.DataSourceConnections[0].SetConnection("ODBCsource", "DataBase", "Login", "Password");
doc.Refresh();
Où la variable "doc" est du type "ReportDocument".
Attention j'ai pris l'index 0 dans "DataSourceConnections" à titre d'exemple.
La valeur peut changer en fonction du modèle.

Voici une petite astuce dont je ne vois pas l'utilité immédiate, mais qui peut intéresser certain.

public int this[string val1, string val2]
{
    set
    {
       Console.WriteLine("{0} - {1}", val1, val2);
    }
}

Aujourd'hui, j'ai eu un problème lors de l'impression d'un rapport avec le composant Crystal Reports.
Lors de l'appel de la méthode "ReportDocument.PrintToPrinter" une étrange erreur
"Win32Exception : The operation completed successfully" est apparue. Après une longue recherche,
je me suis aperçu, que le problème était en réalité lié au pilote (Driver) de mon imprimante sous
Windows 7 64 bits. En effet, le programme fonctionne très bien sous Windows Vista 64 bits
avec la même imprimante. De plus, un de mes collègue à strictement le même problème
avec la même configuration. Cependant, le problème est particulièrement vicieux, car j'arrive à imprimer
certains documents à partir de mon poste, avec le Bloc Note de Windows par exemple.
Quelle péripétie!

Je viens de trouver dans mon code pourquoi ma fenêtre de pré-visualisation Crystal Report
était très lente. J'ai supprimé la ligne ci-dessous dans la méthode "InitializeComponent" de
mon formulaire (.Designer.cs) :

this.crystalReportViewer1.SelectionFormula = "";
Cela m'a permis de gagner neuf secondes à l'affichage.

using System.Reflection;

public sealed class AssemblyInfo
{
    #region Ctors

    static AssemblyInfo()
    {
    }

    private AssemblyInfo()
    {
        PortableExecutableKinds peKind;
        ImageFileMachine machine;

        Assembly.GetCallingAssembly().ManifestModule.GetPEKind(out peKind, out machine);
        this.Kind = peKind;
        this.Machine = machine;
    }

    #endregion

    #region Public Methods

    public bool Is(PortableExecutableKinds kind)
    {
        return ((this.Kind & kind) != 0);
    }

    public bool Is(ImageFileMachine machine)
    {
        return ((this.Machine & machine) != 0);
    }

    #endregion

    #region Properties

    public static AssemblyInfo Instance
    {
        get
        {
            return (AssemblyInfo._instance);
        }
    }

    public PortableExecutableKinds Kind { get; private set; }

    public ImageFileMachine Machine { get; private set; }

    #endregion

    #region Fields

    private static readonly AssemblyInfo _instance = new AssemblyInfo();

    #endregion
}
Exemple d'utilisation:
if (AssemblyInfo.Instance.Is(PortableExecutableKinds.Required32Bit))
{
}






/// <summary>
/// Différence entre deux dates en minute.
/// </summary>
/// <param name="start">Date de début</param>
/// <param name="end">Date de fin</param>
/// <returns>La différence en minute</returns>
public static int GetMinuteDiff(DateTime start, DateTime end)
{
   if (start > end)
      return (-((int)start.Subtract(end).TotalMinutes));
   return ((int)end.Subtract(start).TotalMinutes);
}


Introduction

Depuis l’avènement en 2005 des processeurs multi-cœurs dans nos appareils domestiques, nous, développeur, avons dû changer notre façon de concevoir les applications afin d’exploiter cette technologie. Tout le monde est concerné, directement ou indirectement. Les domaines d’utilisation sont vastes. Ils ne se limitent plus aux super calculateurs comme c’est le cas depuis plus de 20 ans, mais notamment aux jeux vidéos, et aux multimédias en générale. Ainsi nous avons vu apparaître un grand nombre de solution qui permette la parallélisation des traitements au sein d’un programme.

Mais en premier lieu, Il a fallu reconcevoir (ou concevoir) les kernels(noyaux) et les drivers(pilotes) des systèmes d’exploitation modernes pour supporter les nouvelles architectures multi-cœurs. Puis ce fut le tour des applications. Ce qui a par conséquent poussé Intel à développer TBB.

TBB en résumé

TBB est une bibliothèque de programmation portable écrite en C++ qui permet la parallélisation des données au sein d’un code C++ (ou compatible C++). TBB est arrivé en 2006 et fournit un haut niveau d’abstraction des threads grâce à sa conception qui combine le « Task Programming » et le « Data Parallelism ». De plus, TBB est compatible avec les autres bibliothèques de programmation telle qu’OpenMP, les POSIX Threads, les fibres, les (co)routines et les Protothreads.

Au niveau des compilateurs, il n’y a pas de restrictions particulières. N’importe quel compilateur compatible ISO C++ peut être utilisé (Intel Compiler, VC++, G++, DM, …) contrairement à Open MP qui est directement intégré au compilateur.

Au niveau des processeurs, seuls les processeurs IA 32/64, AMD 32/64 et Sparc sont supportés pour l’instant.

Au niveau de la licence, elle est « open source » sous licence GPL version 2.

Remarque : TBB n’est pas compatible avec Mono.

Quelle est la raison d’être de TBB ?

Intel s’est fixé plusieurs objectifs tels que :

Ø Donner aux développeurs la possibilité d’utiliser le Multithreading sans en être un expert.

Ø Obtenir des gains de performance sur les architectures multi-cœur.

Ø Privilégier le « Data Parallelism » plutôt que le « Functional Parallelism ».

Quelle est la différence entre le « Data Parallelism » et le « Functional Parallelism » ?

Supposons le code ci-dessous :

Func1() ;
Func2() ;


Le « Functional Parallelism » consiste à séparer chaque fonction dans un contexte différent.

Thread 1

Thread 2

Func1() ;

Func2() ;


Par opposition, si nous prenons le code ci-dessous :

For (i = 0 ; i < 42 ; ++i)
Func(i);


Le « Data Parallelism » consiste à scinder un block de donnée en N block et de les traités dans un contexte différent.

Thread 1

Thread 2

for (i = 0 ; i < 21 ; ++i)
Func(i);

for (i = 21 ; i < 42 ; ++i)
Func(i);


Le grand avantage du « Data Parallelism » c’est qu’il est beaucoup plus évolutif dans ma mesure que l’on peut changer la taille des blocks à traité, sans devoir réécrire le code. De plus on peut facilement imaginer un scheduler (ordonnanceur de tâches) qui va définir la taille des blocks et leurs contextes en fonction de l’architecture matérielle et logicielle (ce que fait TBB).

Architecture de TBB

L’architecture de TBB est composée de plusieurs couches dont la première est « les allocateurs de mémoire ». Chaque couche repose sur l’implémentation de la précédente à l’exception de la première. Voici le schéma de l’architecture de TBB (à lire de bas en haut) :

TBB par la pratique

La meilleure façon d’apprendre une technologie c’est par la pratique. Par conséquent, nous allons commencer par un cas simple. Pour notre exemple nous allons partir du code suivant :

void Func1(float *tab, int size)
{
for (int i = 0; i < size; ++i)
tab[i] = Func2(i);
}

Dans ce code, nous devons traités un tableau de nombre flottant et effectuer un traitement sur chacun des membres de ce tableau. Comme nous avons vu précédemment, TBB fonctionne autour du « Data Parallelism » et du « Task Programming ». Ainsi, nous allons considérer trois choses :

Ø Notre tableau de nombre flottant constitue un block de donnée.

Ø Notre bloque de donnée peut être découpé en sous block.

Ø Le traitement sur un block constitue une tâche.

La première étape est de créer la « class » qui va représenter le traitement sur un block de donnée. Cette « class » prendra dans un constructeur public un pointeur sur notre tableau de nombre flottant (notre block) et le stockera.

class CalcFloat
{
public:
CalcFloat(float *tab) : _tab(tab) { }

private:
float *_tab ;
} ;


Remarque : Le pointeur passé en paramètre dans le constructeur de « CalcFloat » sera l’adresse du tableau (du block) au complet.

Maintenant, il faut rajouter une méthode qui effectuera le traitement sur le block qu’elle contient. En réalité TBB n’utilise pas une méthode mais la surcharge de l’opérateur (). De plus, cet opérateur n’effectuera pas le traitement sur l’intégralité du block mais sur une portion (range). Sous TBB, une portion est définie par un « blocked_range ». Ainsi à chaque appel de l’opérateur on reçoit en paramètre la portion sur laquelle il faut effectuer un traitement.

Les « blocked_range »

Il existe 3 types de blocked_range :

Ø Unidimensionnel: blocked_range

Ø Bidimensionnel: blocked_range

Ø Tridimensionnel: blocked_range

La particularité des blocked_range c’est qu’ils implémentent un « Split Constructor » que nous détaillerons un peu plus loin.

Dans notre cas, nous avons un conteneur unidimensionnel représenté par notre tableau de nombre flottant. Par conséquent nous allons utiliser un blocked_range

#include <tbb/blocked_range.h>

class CalcFloat
{
public:
CalcFloat(float *tab) : _tab(tab) { }
void operator () (const tbb::blocked_range &range) const
{
}

private:
float *_tab;
} ;


Il ne nous reste plus qu’à ajouter notre traitement :

#include <tbb/blocked_range.h>

class CalcFloat
{
public:
CalcFloat(float *tab) : _tab(tab) { }
void operator () (const tbb::blocked_range &range) const
{
for (int i = range.begin(); i != range.end(); ++i)
this->_tab[i] = Func2(i);

}
private:
float *_tab;
} ;

Enfin, la dernière étape consiste à utiliser l’algorithme « parallel_for » à la place du « for » dans « Func1 ».

void Func1(float *tab, int size)
{
tbb::parallel_for(tbb::blocked_range(0, size),
CalcFloat(tab))
}


On remarque que le constructeur du blocked_range prend la taille du block à traiter (0 -> size).

Grâce à ce code, TBB va découper le blocked_range en plusieurs morceaux puis appeler la surcharge de l’opérateur () pour chacun de ces morceaux (qui sont aussi des blocked_range).
Bien entendu ce découpage n’est pas aléatoire, il sera détaillé dans la section « granularité ».

Voici le code final :

#include <tbb/blocked_range.h>
#include <tbb/parallel_for.h>

class CalcFloat
{
public:
CalcFloat(float *tab) : _tab(tab) { }
void operator () (const tbb::blocked_range &range) const
{
for (int i = range.begin(); i != range.end(); ++i)
this->_tab[i] = Func2(i);
}
private:
float *_tab;
} ;

void Func1(float *tab, int size)
{
tbb::parallel_for(tbb::blocked_range(0, size),
CalcFloat(tab))
}


Split Constructor

Comme nous avons vu à la section précédente, chaque block de donnée est découpé et traité séparément. Ce découpage est réalisé par l’intermédiaire d’un « Split Constructor » que le blocked_range implémente. Ainsi les blocks sont récursivement découpés dans les algorithmes de parallélisation de TBB tels que le « parallel_for ».

Range::~Range() // Destructeur.
Range::Range(Range &r, split) // Coupe r en deux.
bool Range::empty() const // vrai si il est vide.
bool Range::is_divisible() const // vrai si il peut être découpé.


Remarque : de préférence, on essai toujours que lorsque l’on coupe un « range » en deux, les deux parties soient égales pour ne pas tromper le scheduleur (ordonnanceur de tâches).

Le découpage des blocks est réalisé par l’intermédiaire du « Partitioner » qui s’appui entre-autre sur la valeur de la granularité du block.

Granularité

Pour que le « Partitioner » découpe de façon optimisé les blocks, il à besoin de connaître la granularité de chaque block. La granularité constitue l’unité d’itération. Elle peut être explicite ou implicite. En effet, nous n’avons pas précisé de granularité dans notre exemple avec le parallel_for. Sous TBB la granularité est représenté par un entier non-signé.

On peut expliciter la granularité grâce au troisième paramètre du blocked_range :

void Func1(float *tab, int size)
{
tbb::parallel_for(tbb::blocked_range(0, size, 42),
CalcFloat(tab))
}


La valeur de la granularité a une très grande influence sur les performances du programme. Mais si elle est mal calculée, elle peut avoir des conséquences désastreuses.

Voici un graphique fournit pas Intel qui illustre l’importance de la granularité :

Calcul de A[i] = B[i] * C sur 1 million de float avec une machine à 4 sockets et 8 hard-threads.

On remarque que le graphique est en forme de baignoire. En effet, le temps d’exécution du programme diminue jusqu'à que la granularité atteint 100000 puis remonte assez brusquement. On peu considérer que les performances sont correctes quand la granularité est comprise entre 100 et 100000.

Mais quelle est la valeur optimale pour la granularité dans notre cas ?

En réalité c’est très simple, il suffit de prendre la plus grande valeur de l’intervalle ou les performances sont correctes.

Pourquoi ?

Plus la valeur de la granularité est petite, plus les blocks sont petits. Par conséquent il est inutile de surchargé le scheduler (ordonnanceur de tâches) avec des petits fragments. D’autant plus que cela n’apporte aucun grain de performance par rapport à une granularité fixé à 100000.

Pourquoi ne pas laisser TBB décider de la valeur de la granularité ?

Même si la valeur que l’on définit est identique à celle qu’il calcul, il est préférable d’expliciter la granularité car cela évite qu’il la calcule (donc gain de temps). De plus la granularité dépend aussi du traitement que l’on va effectuer sur le block. Par conséquent, TBB ne peut pas toujours calculer de façon fiable la granularité à tous les coups.

Partitioner

Le « Partitioner » est le dispositif de TBB qui est chargé de découper les « Range » en « SubRange ». Il intervient pour deux algorithmes :

Ø parallel_for

Ø parallel_reduce

Il existe 3 types de « partitioner » :

Ø simple_partitioner : taille des blocks = valeur de la granularité.

Ø auto_partitioner : taille des blocks automatiques (calculé en interne).

Ø affinity_partitioner : auto_partitioner + gestion du « cache affinity ».

Il est tentant d’utiliser le « affinity_partitioner » dans tous les cas, car celui-ci est le plus complet. Pourtant il faut bien faire attention à son emploi.

Voici un graphique fournit par Intel qui illustre la différence entre le «auto_partitioner » et le « affinity_partitioner » :

On remarque que le « affinity_partitioner » est utile lorsque l’on a un nombre important d’élément. Attention, l’abus du « affinity_partitioner » peut avoir des effets très négatifs sur la performance du code. Seule l’expérience peut déterminer si son emploi est justifié.

Les conteneurs

Le gros défaut des conteneurs de la STL (Standard Template Library) c’est qu’ils ne sont pas « Thread-Safe » et que par conséquent, (dans la plupart des cas) on encapsule chaque accès du conteneur dans une section critique par l’intermédiaire de mutex, sémaphores, barrières, etc. Quant à lui, TBB fournit 4 conteneurs qui répondent à la plupart des besoins :

Ø concurrent_hash_map : Quasi similaire à la std::map de la STL.

Ø concurrent_vector : Similaire au std::vector de la STL.

Ø concurrent_queue : Une FIFO non bloquante.

Ø concurrent_bounded_queue : Une FIFO bloquante avec possibilité de définir une capacité.

Remarque : Les conteneurs de type « queue » supporte les itérateurs de la STL.


Mutex

Malgré que cela ne soit pas conseillé, TBB fournit néanmoins plusieurs implémentations des mutex dans le cas ou une opération nécessite un accès exclusif.

Voici un tableau des différents mutex que l’on retrouve dans TBB :

Mutex

Scalable

Fair

Recursive

Long Wait

Size

mutex

OS dep

OS dep

Non

Bloque

≥ 3 words

recursive_mutex

OS dep

OS dep

Oui

Bloque

≥ 3 words

spin_mutex

Non

Non

Non

« yields »

1 byte

queuing_mutex

Oui

Oui

Non

« yields »

1 word

spin_rw_mutex

Non

Non

Non

« yields »

1 word

queuing_rw_mutex

Oui

Oui

Non

« yields »

1 word

null_mutex

Non

Oui

Oui

Jamais

Zero

null_rw_mutex

Non

Oui

Oui

Jamais

Zero


Opérations atomiques

Parallèlement aux mutex, TBB fournit une implémentation des types atomiques qui a l’avantage d’être beaucoup plus rapide que les mutex. De plus il n’y a pas de risque de « Deadlock ». D’une manière générale les opérations atomiques sont préférables aux mutex.

Syntaxe : atomic<T>

Remarque : T doit être un type de base : int, float, long, enum depuis la version 2.2.

Exemples :

= x // lit la valeur de x.

x = // écrit la valeur de x et la retourne.

x.fetch_and_store(y) // y = x et renvoi l’ancienne valeur de x.

x.fetch_and_add(y) // x += y et renvoi l’ancienne valeur de x.

x.compare_and_swap(y,z) // si x == z, alors x = y. sinon il renvoi l’ancienne valeur de x.

Liens utiles :

Ø http://www.threadingbuildingblocks.org

Ø http://koalab.epitech.net/

Øhttp://software.intel.com/en-us/blogs/2008/12/16/compare-windows-threads-openmp-intel-threading-building-blocks-for-parallel-programming