Fonctionnalités multithread des drivers et fonctions de LabVIEW

Aperçu

Ce document fait partie de la
Série de tutoriaux sur les principes fondamentaux de la programmation multicœur

 Série de tutoriaux sur les principes fondamentaux de la programmation multicœur

Série de tutoriaux sur les principes fondamentaux de la programmation multicœur

La programmation multithread est une approche clé pour tirer parti des processeurs multicœurs. En divisant votre application en plusieurs threads, le système d'exploitation peut équilibrer (ou planifier) ces threads sur plusieurs cœurs de traitement disponibles sur le PC. Cet article étudie les avantages liés à l'utilisation de fonctions et de drivers "multithread-safe" et réentrants sur un PC à processeur multicœur.

Contenu

Comportement en parallèle vs. multithread-safe

Dans les langages traditionnels, vous devez diviser votre programme en différents threads pour une exécution en parallèle. Chaque thread peut s'exécuter en même temps. Toutefois, il y a une différence entre écrire du code qui peut s'exécuter sans risque dans une application multithread et écrire du code qui s'exécute en parallèle pour optimiser les performances que vous obtenez sur un système multicœur. Cette différence est souvent illustrée dans les drivers ou les fonctions que vous utilisez peut-être quand vous écrivez des programmes. Des fonctions multithread-safe peuvent être appelées par plusieurs threads et n'écrasent pas leurs données, ce qui évite des conflits en bloquant l'exécution. Si un thread appelle la fonction, tout autre thread qui essaie d'appeler la même fonction doit attendre que le premier thread ait terminé. Des fonctions réentrantes vont plus loin en permettant à plusieurs threads d'appeler et d'exécuter la même fonction en même temps et en parallèle. Ces deux exemples s'exécutent correctement dans un programme multithread mais l'exécution est plus rapide avec des fonctions réentrantes car elles s'exécutent simultanément.

 

Compromis entre mémoire et performances dans LabVIEW

Dans LabVIEW, les données transmises sur les fils de liaison sont généralement indépendantes des fonctions qui opèrent sur les données. Par définition, il n'est pas facile pour des VIs ou des fonctions qui ne sont pas connectés à un fil de liaison d'accéder aux données de ce fil. Si nécessaire, LabVIEW fait une copie des données quand vous divisez un fil de liaison de façon à ce qu'il existe des versions indépendantes des données pour les VIs qui vont effectuer des opérations dessus par la suite. En outre, la plupart des VIs LabVIEW et des drivers sont multithread-safe et réentrants. Toutefois, certains VIs de la bibliothèque intégrée dans LabVIEW peuvent être configurés comme non réentrants par défaut. Dans la mesure où les VIs réentrants utilisent davantage de mémoire, vous devrez peut-être faire un compromis entre l'utilisation de la mémoire et le parallélisme. Dans la plupart des cas, LabVIEW choisit par défaut d'économiser de la mémoire, c'est-à-dire d'utiliser la configuration non réentrante, et c'est à vous de décider si le but est d'optimiser le parallélisme. Si tel est le cas, les VIs de bibliothèque peuvent facilement être configurés pour être réentrants.

 

Fonctions réentrantes

Il existe certains cas, ou certains environnements, dans lesquels vous devrez utiliser une fonction ou un programme non réentrant pour éviter des problèmes d'accès aux fonctions. Pour assurer un fonctionnement en toute sécurité, la plupart des bibliothèques multithread-safe verrouillent des ressources, c'est-à-dire que lorsqu'un thread appelle une fonction, cette fonction ou toute la bibliothèque, est verrouillée de façon à ce qu'aucun autre thread ne puisse l'appeler. Dans une situation de programmation en parallèle, si deux chemins différents de votre code, ou threads, essaient d'appeler la même bibliothèque ou fonction, le verrouillage bloque un des threads ou le force à attendre jusqu'à ce que l'autre ait terminé. En outre, autoriser l'accès à une fonction un thread à la fois économise de la mémoire car aucune autre instance n'est nécessaire.

Toutefois, comme cela est mentionné auparavant, associer des techniques de programmation en parallèle et configurer vos fonctions pour qu'elles soient réentrantes peut améliorer les performances de votre code.

Étant donné que des drivers de périphériques dans LabVIEW (comme NI-DAQmx) sont à la fois multithread-safe et réentrants, votre fonction peut être appelée par plusieurs threads en même temps et toujours fonctionner correctement sans blocage. Ceci est une fonctionnalité importante pour écrire du code en parallèle et optimiser les performances sur des systèmes multicœurs. Si vos performances ne s'améliorent pas, c'est probablement parce que vous utilisez du code qui n'a pas d'exécution réentrante : votre code doit attendre que les autres threads aient terminé d'utiliser chaque fonction avant de pouvoir accéder à ces fonctions. Pour mieux comprendre ce principe, observez la fonctionnalité Hiérarchie du VI dans LabVIEW. Pour voir la hiérarchie d'un VI, sélectionnez Affichage»Hiérarchie du VI. Dans la Hiérarchie du VI montrée dans la figure 1, F1 et F2 dépendent du même VI (dans ce cas, un algorithme de transformée de Fourier rapide (FFT) gourmand en ressources). Il est important que ce VI soit réentrant si F1 et F2 s'exécutent en parallèle.

Figure 1 - Affichage de la hiérarchie d'un VI dans LabVIEW

La configuration réentrante est un facteur important à prendre en considération pour éliminer toute dépendance superflue dans votre code. Certains VIs d'analyse dans LabVIEW sont non réentrants ou réentrants par défaut. Il est donc important d'étudier les propriétés de ces VIs pour vous assurer qu'ils s'exécutent en parallèle.

 

Configuration de LabVIEW

Pour définir votre VI LabVIEW comme étant réentrant, sélectionnez Fichier»Propriétés du VI, puis sélectionnez Exécution dans le menu déroulant. Vous pouvez à présent cocher la case située à côté du paramètre Exécution réentrante et décider quelle option de clonage vous voulez choisir.

Figure 2 - Option Exécution réentrante dans la boîte de dialogue Propriétés du VI

LabVIEW supporte deux types de VIs réentrants. Sélectionnez l'option Préallouer une copie pour chaque instance si vous voulez créer une copie de VI pour chaque appel au VI réentrant avant que LabVIEW n'appelle le VI réentrant ou si une copie de VI doit préserver des informations d'état entre les appels. Par exemple, si un VI réentrant contient un registre à décalage non initialisé ou une variable locale, une propriété ou une méthode qui contient des valeurs qui doivent rester pour de futurs appels à la copie du VI, sélectionnez l'option Préallouer une copie pour chaque instance. Sélectionnez aussi cette option si le VI réentrant contient la fonction Premier appel ? Ceci est aussi la configuration recommandée pour des VIs qui s'exécutent sur des systèmes LabVIEW Real-Time pour assurer un jitter minimum.

Sélectionnez l'option Partager les copies entre instances pour réduire l'utilisation de la mémoire associée à la préallocation d'un grand nombre de copies de VIs. Quand vous sélectionnez l'option Partager les copies entre instances, LabVIEW attend qu'un VI fasse un appel au VI réentrant avant de créer une copie du VI. Avec cette option, LabVIEW crée les copies de VIs sur demande, en introduisant potentiellement du jitter dans l'exécution du VI. LabVIEW ne conserve pas les informations d'état entre des appels au VI réentrant.

 

Capacités de driver de LabVIEW

Il est important d'utiliser des drivers qui soient réentrants et thread-safe si vous interagissez avec n'importe quel type de matériel. Avec ces attributs, vous pouvez tirer parti de la technologie multicœur pour améliorer les performances.

Avec des versions précédentes de LabVIEW, la configuration réentrante n'était pas toujours disponible avec les drivers de périphériques. NI-DAQ traditionnel, par exemple, était multithread-safe dans le sens où il ne donnait pas lieu à une erreur s'il était appelé par deux threads différents en même temps. NI-DAQ traditionnel gérait ceci avec un verrouillage global ; c'est-à-dire qu'une fois qu'un thread appelait une fonction NI-DAQ, tous les autres threads devaient attendre que cette fonction ait terminé avant de pouvoir exécuter une autre fonction NI-DAQ.

NI-DAQmx fonctionne de façon optimale dans un environnement multithread et parallèle. NI-DAQmx est réentrant ; plusieurs threads peuvent appeler le driver simultanément. Vous pouvez exécuter deux entrées analogiques différentes provenant de deux cartes différentes à partir de threads différents dans un même programme et ils s'exécutent simultanément sans causer de blocage. En outre, vous pouvez exécuter une entrée analogique et une entrée numérique à partir de deux threads complètement différents en même temps, sur la même carte. Ceci traite de manière efficace une même ressource matérielle comme deux ressources séparées grâce aux capacités du driver NI-DAQmx.

Les drivers d'instruments modulaires de NI fonctionnent aussi comme NI-DAQmx. Tous les drivers listés dans le tableau 1 sont réentrants et thread-safe. Ils offrent tous la possibilité d'appeler la même fonction deux fois sur deux périphériques différents en même temps. Ceci est particulièrement avantageux pour des systèmes volumineux dotés de code utilisant plusieurs instruments.

Tableau 1 - Drivers d'instruments modulaires réentrants et thread-safe.

 

Conclusion

Quand vous utilisez la programmation en parallèle pour tirer parti des architectures multicœurs, il est important de prendre en considération le langage de programmation et tout ce qui a trait à l'écriture de code parallèle, mais il est aussi important de s'assurer que vos fonctions et drivers sont adaptés à un environnement parallèle.