Windows 7 et le Concurrency Runtime

 

Microsoft à récemment mis à disposition une version beta de Windows 7 disponible ici : (https://www.microsoft.com/windows/windows-7/default.aspx) dans laquelle vous trouverez un nombre impressionnant de nouveautés. Dans ce billet, nous nous focaliserons essentiellement sur celles qui permettront à vos applications parallèles de tirer profit au maximum des performances.

1. Le support de plus de 64 processeurs

2. User Mode Scheduled Threads

Ces deux fonctionnalités que je vais aborder seront supportées dans le Concurrency Runtime, qui sera disponible avec Visual Studio 10. Il est à retenir qu’elles ne le seront uniquement sur les versions 64-bits de Windows 7

Plus de 64 processeurs

Jusqu’à la version de Windows 7  64 Bits, les systèmes d’exploitation Windows ne pouvaient gérer qu’un maximum de 64 processeurs. Cette limite est désormais dépassée avec Windows 7 qui peut gérer jusqu'à 4 groupes de processeurs, chaque groupe ayant un maximum de 64 processeurs. Soit un maximum aujourd’hui de 256 processeurs. Ces mécanismes sont détaillés dans le livre blanc (en Anglais) ici : https://www.microsoft.com/whdc/system/Sysinternals/MoreThan64proc.mspx.

Néanmoins, par défaut, Windows 7 assignera initialement un processus à un seul groupe (seul les processus système au démarrage, se verront assigner à des multi-groupes) soit un maximum de 64 processeurs. La raison initiale à été dictée par le faite que 64 processeurs sont aujourd’hui largement appropriés pour la majorité des applications. Si vous voulez utiliser plus de threads, il vous faudra alors, vous-même dynamiquement assigner vos threads supplémentaires à un autre groupe avec tous les avantages et inconvénients que cela peut causer (voir la section Group, Process, and Thread Affinity, du livre blanc : Supporting Systems That Have More than 64 processors).

La bonne nouvelle, c’est que si vous utilisez le Concurrency Runtime sur Windows 7, vous n’aurez pas besoin de vous préoccuper de tous les détails d’implémentation. Il prendra en charge pour vous, tous les détails en déterminant automatiquement les ressources disponibles (ex. le nombre de cœurs total disponibles), et en utilisera autant que nécessaire lors de l’exécution de votre application. Compilez votre application avec le Concurrency Runtime, et elle profitera automatiquement de la mise à l’échelle d’un dual-core, jusqu’à un serveur Windows 7 256 Cœurs.

User Mode Scheduling

Le User Mode Scheduled Threads (UMS Threads) est une autre fonctionnalité qui met en évidence l’attrait du Concurrency Runtime.

Comme son nom l’indique, UMS Threads sont des threads qui sont ordonnancés par un ordonnanceur en mode utilisateur, par opposition au mode noyau de Windows. Ordonnancer les threads au niveau du mode utilisateur, apporte de nombreux avantages :

1. On évite la transition du mode utilisateur vers le mode noyau, ce qui peut fournir des améliorations de performances.

2. Garantie de l’utilisation totale du quantum de temps assigné par le système si un UMS Thread bloque pour n’importe quelle raison.

Pour illustrer le second point, imaginons un ordonnanceur très simple et naïf, qui ne possède qu’une seule file de travaux. Dans cet exemple, on imagine également que nous avons 100 tâches, qui tournent en parallèle sur 2 CPUs.

clip_image001

Ici nous avons démarré avec 100 éléments dans notre file (queue), et deux tâches (threads) sont en cours d’exécution et tournent en parallèle. Malheureusement, la Tâche 2 est bloquée sur une section critique. De toute évidence, nous souhaiterions ici que l’ordonnanceur (i.e le Concurrency Runtime) prenne une tâche dans la file et fasse en sorte qu’elle soit exécutée quand même sur la CPU 2, tant que la Tâche 2 est encore bloquée. Avec les Threads Win32 traditionnels, l’ordonnanceur ne sait pas faire la différence entre une tâche qui met du temps pour effectuer son travail, et une tâche qui est bloquée dans le noyau. Le résultat est que tant que la tâche 2 n’est pas débloquée, le Concurrency Runtime, n’ordonnancera aucune tâche sur le CPU 2. Notre 2-coeurs devient alors un 1-coeur, et dans le pire des scénarios, les 99 autres tâches seront exécutées séquentiellement sur le CPU 1.

En tant que développeur, il est possible d’améliorer cette situation en utilisant les primitives de synchronisation du Concurrency Runtime dites coopérative (critical_section, reader_writer_lock, event), à la place des primitives Win32 du noyau. Ces primitives vont bloquer un thread de manière plus astucieuse, en informant le Concurrency Runtime, qu’il peut utiliser le CPU pour d’autres tâches.

L’UMS Thread de Windows 7 va encore plus loin pour améliorer cette situation. Lorsque la tâche 2 bloque, le système d’exploitation redonne le contrôle au Concurrency Runtime. Qui peut alors prendre la décision d’ordonnancer un nouveau thread pour exécuter la tâche 3. Comme le nouveau thread est ordonnancé en mode utilisateur par le concurrency runtime et non plus par le système d’exploitation, le basculement est très rapide. Avec ce système, on garde les deux CPUs occupés par les 99 autres tâches. Lorsque la tâche 2 se débloque, Windows 7 en informe le Concurrency Runtime afin qu’il l’ordonnance en mode utilisateur une nouvelle fois sur un des processeurs disponible.

Vous pourriez dire, “Mais si ma tâche, ne fait rien de bloquant en mode noyau, est-ce que cela va toujours m’aider ? ” La réponse est oui. Il est très difficile de savoir si votre tâche bloquera ou pas. Si vous appelez “new” ou “malloc” vous pouvez bloquer sur un verrou du tas. Même si votre code ne bloque pas, une opération peut causer un défaut de page. Une opération d’entrée/sortie peut très bien causer une transition dans le noyau. Toutes ces évènements peuvent prendre du temps CPU sur lequel ils apparaissent, donc peuvent très bien arrêter la progression de votre tâche. Le CPU étant en mode idle, ça donne l’opportunité à l’ordonnanceur, d’exécuter des tâches additionnelles, évitant ainsi le gaspillage de cycle-CPU. Le résultat est un meilleur débit et donc une utilisation plus efficace du CPU.

Nous avons simplifié à l’extrême notre exemple, car l’ordonnanceur est beaucoup plus complexe que cela. Inside Windows 7 - User Mode Scheduler (UMS). Mais encore une fois ceux qui utiliseront le Concurrency Runtime, PPL et les Agents, ne seront pas concernés par tous ces détails, et votre code utilisera soit les threads Win32, soit les UMS Threads.

Meilleur Ensemble

Il est clair que Windows 7 sera un système d’exploitation, qui permettra le développement et l’exécution efficace des applications parallèles. Il à fait l’objet lui-même d’une attention particulière au niveau noyau (Microsoft à retravaillé sur un certain nombre de verrous, Dispatcher lock, Physical Frame Number Lock, et autres verrous de cache d’adresses virtuelles) qui améliore les performances.

Le Concurrency Runtime permettra aux développeurs de tirer profit de tout le potentiel de Windows 7, d’une manière simple et puissante.