Utilisation de mémoire par les VIs

Aide LabVIEW 2014

Date d'édition : June 2014

Numéro de référence : 371361L-0114

»Afficher les infos sur le produit
Télécharger l'aide (Windows uniquement)

LabVIEW gère beaucoup de détails que vous devriez gérer dans un langage de programmation textuel. Une des contraintes principales des langages textuels est l'utilisation de la mémoire. Dans les langages textuels, c'est à vous, le programmeur, d'allouer de la mémoire avant de l'utiliser et de la libérer quand vous avez terminé. Vous devez aussi veiller à ne pas écrire au-delà de la mémoire que vous avez allouée initialement. L'une des plus grandes erreurs des programmeurs qui utilisent des langages textuels est de ne pas allouer de mémoire, ou d'en allouer une taille insuffisante. De plus, l'allocation insuffisante de mémoire constitue un problème difficile à résoudre.

Le concept de flux de données de LabVIEW simplifie considérablement la gestion de la mémoire. Dans LabVIEW, vous n'allouez pas de variables et vous ne leur affectez pas de valeur. À la place, vous créez un diagramme avec des connexions qui représentent la transition des données.

Les fonctions qui génèrent des données allouent le stockage pour ces données. Lorsque les données ne sont plus utilisées, la mémoire associée est libérée. Lorsque vous ajoutez de nouvelles informations à un tableau ou à une chaîne, une mémoire suffisante est allouée automatiquement pour gérer les nouvelles informations.

Cette gestion automatique de la mémoire est l'un des avantages principaux de LabVIEW. Cependant, comme la gestion se fait automatiquement, il est plus difficile de contrôler quand elle se produit. Si votre programme utilise de grandes quantités de données, vous devriez avoir une idée de quand la mémoire est allouée. Si vous comprenez les principes en jeu, vous pourrez écrire des programmes ayant des besoins en mémoire largement inférieurs. En outre, si vous comprenez comment minimiser l'utilisation de la mémoire, vous pouvez aussi accélérer l'exécution des VIs car l'allocation de la mémoire et la copie des données peuvent prendre une quantité de temps considérable.

Mémoire virtuelle

Les systèmes d'exploitation utilisent de la mémoire virtuelle pour permettre aux applications d'accéder à davantage de mémoire que celle disponible dans la RAM physique. Les OS divisent la RAM physique en blocs appelés pages. Quand une application ou un processus assigne une adresse à un bloc de mémoire, l'adresse ne se réfère pas à un emplacement direct dans la RAM physique mais à la mémoire dans une page. L'OS peut permuter ces pages entre la RAM physique et le disque dur.

Si une application ou un processus doit accéder à un certain bloc ou une page de la mémoire qui ne se trouve pas dans la RAM physique, l'OS peut déplacer une page de la RAM physique qu'une application ou un processus n'utilise pas vers le disque dur et la remplacer par la page requise. L'OS garde une trace des pages en mémoire et traduit les adresses virtuelles en pages en adresses réelles dans la RAM physique quand une application ou un processus doit accéder à la mémoire.

La figure suivante illustre comment deux processus peuvent remettre et obtenir des pages de la RAM physique. Pour cet exemple, Processus A et Processus B s'exécutent en même temps.

1  Processus A   3  Processus B   5  Page de mémoire du processus B  
2  RAM physique   4  Page de mémoire virtuelle   6  Page de mémoire du processus A  

Étant donné que le nombre de pages qu'une application ou un processus utilise dépend de l'espace disque disponible et non de la RAM physique disponible, la mémoire qu'une application utilise peut être supérieure à celle de la seule RAM physique. La taille des adresses de mémoire qu'une application utilise limite la mémoire virtuelle à laquelle l'application peut accéder. Étant donné que LabVIEW est une application 32 bits, il utilise des adresses 32 bits et peut accéder à 4 Go de mémoire virtuelle au maximum.

Une application 32 bits reconnaît les grandes adresses si elle peut assigner des adresses à plus de 2 Go de mémoire virtuelle. Si un système possède moins de 4 Go de RAM physique, des applications 32 bits peuvent seulement assigner des adresses à 3 Go de mémoire virtuelle. Le système d'exploitation réserve toujours une certaine quantité de mémoire virtuelle à son noyau, ou composant principal. (Windows) Chaque application ou processus d'un OS 32 bits peut accéder à un maximum de 4 Go de mémoire virtuelle. Par défaut, le système d'exploitation réserve 2 Go au noyau et alloue les 2 Go restants à l'utilisateur.

(Windows Vista) Le stockage BCD (Boot Configuration Data) contrôle la quantité de mémoire que l'OS réserve pour le noyau. Pour permettre à LabVIEW et à d'autres applications qui reconnaissent les grandes adresses d'accéder à plus de mémoire virtuelle, vous pouvez ouvrir la fenêtre de ligne de commande en tant qu'administrateur et utiliser les commandes bcdedit pour modifier le stockage BCD. Pour ouvrir la fenêtre de ligne de commande en tant qu'administrateur, naviguez vers la fenêtre dans le menu Démarrer de Windows, cliquez avec le bouton droit sur le nom du programme et sélectionnez Exécuter en tant qu'administrateur.

(Windows XP) Le fichier boot.ini de Windows contrôle combien de mémoire l'OS réserve pour le noyau. Vous pouvez modifier ce fichier pour permettre à LabVIEW et à d'autres applications qui reconnaissent les grandes adresses d'accéder à davantage de mémoire virtuelle.

Utilisation de la mémoire virtuelle par LabVIEW (Windows uniquement)

La quantité de mémoire virtuelle à laquelle LabVIEW peut accéder dépend de la version de Windows que vous utilisez.

  • (Windows Vista 64 bits) Par défaut, LabVIEW peut accéder à un maximum de 4 Go de mémoire virtuelle. Dans la mesure où LabVIEW est une application 32 bits, vous ne pouvez pas l'autoriser à accéder à plus de 4 Go de mémoire virtuelle.
  • (Windows Vista) Pour permettre à LabVIEW d'accéder à 3 Go de mémoire virtuelle au maximum, vous pouvez utiliser la commande bcdedit /set increaseuserva 3072 dans la fenêtre de ligne de commande pour ajouter une entrée au stockage BCD.
  • (Windows XP) Pour permettre à LabVIEW d'accéder jusqu'à 3 Go de mémoire virtuelle sur un OS 32 bits, vous pouvez ajouter le tag /3GB au fichier boot.ini de Windows sur la ligne qui spécifie la version de Windows à lancer.

Gestion de la mémoire des composants de VIs

Un VI a quatre composants principaux :

  • Face-avant
  • Diagramme
  • Du code (diagramme compilé en code machine)
  • Des données (valeurs des commandes et des indicateurs, valeurs par défaut, valeurs des constantes du diagramme, etc.)

Lorsqu'un VI se charge, la face-avant, le code (s'il correspond à la plate-forme) et les données du VI sont chargés en mémoire. Si le VI doit être recompilé suite à un changement de plate-forme ou d'interface d'un sous-VI, le diagramme est lui-aussi chargé en mémoire.

Le VI charge également le code et l'espace de données de ses sous-VIs en mémoire. Dans certaines circonstances, la face-avant de certains sous-VIs est aussi chargée en mémoire. Par exemple, ceci peut se produire si le sous-VI utilise un nœud de propriété, car ces derniers manipulent les informations d'état des commandes de la face-avant.

Une considération importante concernant l'organisation des composants des sous-VIs : en général, une section de VI convertie en sous-VI n'utilise pas beaucoup plus de mémoire. Si vous créez un très grand VI unique sans sous-VIs, la face-avant, le code et les données de ce VI de niveau principal sont enregistrés en mémoire. Toutefois, si vous divisez le VI en sous-VIs, le code du VI de niveau principal est plus petit, et le code et les données des sous-VIs résident en mémoire. Dans certains cas, vous pouvez constater une diminution de l'utilisation de la mémoire à l'exécution.

Les VIs complexes prennent aussi plus de temps à éditer. Vous pouvez éviter ce problème de l'une des manières suivantes :

Remarque  Si la face-avant ou le diagramme d'un VI donné est beaucoup plus grand qu'un écran, vous pouvez le décomposer en sous-VIs pour qu'il soit plus accessible.

Programmation par flux de données et buffers de données

En général, dans la programmation par flux de données, vous n'utilisez pas de variables. Dans les modèles de flux de données, les nœuds sont décrits comme des consommateurs de données en entrée et producteurs de données en sortie. Une implémentation littérale d'un tel modèle produit des applications susceptibles d'utiliser de très grandes quantités de mémoire et d'avoir des performances fort ralenties. Chaque fonction produit une copie des données pour chaque destination à laquelle une sortie est envoyée. LabVIEW améliore cette implémentation par le biais de la réutilisation des buffers, ou "inplaceness".

LabVIEW utilise cette capacité de réutilisation des buffers pour déterminer quand réutiliser la mémoire et s'il faut faire des copies de chaque terminal de sortie. Si LabVIEW ne copie pas les données d'une entrée ou d'une sortie, les données de l'entrée et de la sortie sont "en place".

Vous pouvez utiliser la fenêtre Afficher les allocations de buffer pour identifier où LabVIEW peut créer des copies des données.

Par exemple, dans une approche plus traditionnelle du compilateur, le diagramme suivant utilise deux blocs de mémoire de données : un pour les entrées et l'autre pour les sorties.

Le tableau en entrée et le tableau en sortie contiennent le même nombre d'éléments et le type de données des deux tableaux est identique. Considérez le tableau en entrée comme un buffer de données. Au lieu de créer un nouveau buffer pour la sortie, le compilateur réutilise la mémoire du buffer d'entrée pour le buffer de sortie. Cet exemple de réutilisation des buffers économise de la mémoire et produit une exécution plus rapide car il n'y a pas besoin d'allouer de la mémoire pendant l'exécution.

Cependant, dans certains cas, comme dans le diagramme suivant, le compilateur ne peut pas réutiliser les buffers de mémoire.

Un signal transmet une source de données unique à plusieurs destinations. La fonction Remplacer une portion d'un tableau modifie le tableau en entrée pour produire le tableau en sortie. Dans ce cas, les données d'une des fonctions est en place et la sortie réutilise les données pour le tableau en entrée alors que les autres sorties ne le font pas. Par conséquent, le compilateur crée de nouveaux buffers de données pour deux des fonctions et copie les données du tableau dans les buffers. Ce diagramme utilise 12 Ko environ (4 Ko pour le tableau d'origine et 4 Ko pour chacun des deux buffers de données supplémentaires).

Maintenant, étudiez le diagramme suivant.

Comme avant, l'entrée est branchée à trois fonctions. Cependant, dans ce cas-ci, la fonction Indexer un tableau ne modifie pas le tableau en entrée. Si vous transférez des données vers plusieurs emplacements qui lisent tous les données sans les modifier, LabVIEW ne fait pas de copie des données. Il en résulte que toutes les données sont en place. Ce diagramme utilise 4 Ko de mémoire environ.

Finalement, étudiez le diagramme suivant.

Dans ce cas-ci, l'entrée est branchée à deux fonctions, et l'une d'elles modifie les données. Il n'y a pas de dépendance entre les deux fonctions. Vous pouvez donc prédire qu'il faut faire au moins une copie pour que la fonction Remplacer une portion d'un tableau puisse modifier les données en toute sécurité. Dans ce cas, toutefois, le compilateur planifie l'exécution des fonctions de sorte que la fonction qui lit les données s'exécute en premier et que la fonction qui modifie les données s'exécute en dernier. De cette façon, la fonction Remplacer une portion d'un tableau réutilise le buffer du tableau en entrée sans générer de tableau dupliqué ; les données de cette fonction sont donc en place. Si l'ordre des nœuds est important, rendez-le explicite avec une séquence ou en utilisant la sortie d'un nœud pour l'entrée d'un autre.

En pratique, l'analyse des diagrammes par le compilateur n'est pas parfaite. Dans certains cas, le compilateur n'est pas capable de déterminer la méthode optimale pour réutiliser la mémoire du diagramme.

Indicateurs conditionnels et buffers de données

La façon dont un diagramme est construit peut empêcher LabVIEW de réutiliser les buffers de données. Si un sous-VI comporte un indicateur conditionnel, LabVIEW ne peut pas optimiser l'utilisation des buffers de données. Un indicateur conditionnel est un indicateur à l'intérieur d'une structure Condition ou d'une boucle For. Si vous placez un indicateur sur un chemin de code exécuté de manière conditionnelle, le flux de données dans le système sera interrompu et LabVIEW forcera une copie des données dans l'indicateur plutôt que d'utiliser le buffer de données pour l'entrée. Si vous placez les indicateurs en dehors de structures Condition et de boucles For, LabVIEW modifie directement les données dans la boucle ou la structure et les passe à l'indicateur plutôt que de créer une copie des données. Vous pouvez créer des constantes pour d'autres conditions au lieu de placer des indicateurs dans la structure Condition.

Surveillance de l'utilisation de la mémoire

Vous pouvez utiliser différentes méthodes pour déterminer l'utilisation de la mémoire.

Pour visualiser l'utilisation de la mémoire pour le VI actuel, sélectionnez Fichier»Propriétés du VI, puis Utilisation de la mémoire dans le menu déroulant supérieur. Remarquez que les informations ne tiennent pas compte de l'utilisation de la mémoire par les sous-VIs. Vous pouvez utiliser la fenêtre Profil - Performances et mémoire pour surveiller la mémoire utilisée par tous les VIs en mémoire et identifier un sous-VI qui a des problèmes de performances. La fenêtre Profil - Performances et mémoire du VI maintient les statistiques sur le nombre minimal, maximal et moyen d'octets et de blocs utilisés par chaque VI à chaque exécution.

Remarque   Pour surveiller précisément les performances d'exécution d'un VI, enregistrez tous les VIs sans code compilé séparé dans la version actuelle de LabVIEW pour les raisons suivantes :
  • L'utilisation de la fonctionnalité Annuler crée des copies temporaires des objets et des données, ce qui augmente les besoins de mémoire rapportés pour le VI. En enregistrant le VI, vous supprimez les copies générées par la fonction Annuler, de sorte que les informations rapportées sur la mémoire sont justes.
  • L'exécution de VIs non enregistrés provenant de versions antérieures de LabVIEW risque d'augmenter les besoins de mémoire rapportés et de diminuer les performances d'exécution du VI.

Utilisez la fenêtre Afficher les allocations de buffer pour identifier spécifiquement les zones du diagramme où LabVIEW alloue de l'espace mémoire sous forme d'un buffer.

Remarque  La fenêtre Afficher les allocations de buffer n'est disponible que dans les systèmes de développement complet et professionnel de LabVIEW.

Sélectionnez Outils»Profil»Afficher les allocations de buffer pour afficher la fenêtre Afficher les allocations de buffer. Cochez la case à côté des types de données pour lesquels vous voulez afficher les buffers et cliquez sur le bouton Rafraîchir. Les carrés noirs qui apparaissent sur le diagramme indiquent où LabVIEW crée des buffers dans le but d'allouer de l'espace pour les données.

La quantité de mémoire que LabVIEW alloue à chaque buffer a la même taille que le plus grand type de données. Lorsque vous exécutez un VI, LabVIEW a la possibilité d'utiliser ces buffers pour stocker les données. Il n'y a pas moyen de savoir si LabVIEW fait une copie des données ou non car LabVIEW prend cette décision à l'exécution et le VI dépend peut-être de données dynamiques.

Si le VI a besoin de mémoire pour une allocation de buffer, LabVIEW fait une copie des données pour le buffer. Si LabVIEW n'est pas sûr que le buffer requiert une copie des données, il fait parfois une copie pour le buffer.

Remarque  La fenêtre Afficher les allocations de buffer n'identifie que les buffers d'exécution avec les carrés noirs sur le diagramme. Cette fenêtre ne permet pas d'identifier les buffers d'opération sur la face-avant.

Lorsque vous savez où LabVIEW crée des buffers, vous êtes en mesure d'éditer le VI pour réduire la quantité de mémoire dont LabVIEW a besoin pour exécuter ce VI. LabVIEW doit allouer de la mémoire pour exécuter le VI ; vous ne pouvez donc pas éliminer tous les buffers.

Si LabVIEW doit recompiler un VI que vous avez modifié, les carrés noirs disparaissent, car les informations du buffer risquent de ne plus être exactes. Cliquez sur le bouton Rafraîchir sur la fenêtre Afficher les allocations de buffer pour recompiler le VI et afficher les carrés noirs. Dès que vous fermez la fenêtre Afficher les allocations de buffer, les carrés noirs disparaissent.

Remarque  Vous pouvez aussi utiliser le toolkit LabVIEW Desktop Execution Trace pour surveiller l'utilisation des threads, les fuites de mémoire et d'autres aspects de la programmation LabVIEW.

Sélectionnez Aide»À propos de LabVIEW pour voir une statistique qui résume la quantité totale de mémoire utilisée par votre application. Ceci comprend la mémoire pour les VIs ainsi que la mémoire utilisée par l'application. Vous pouvez vérifier cette quantité avant et après l'exécution d'une série de VIs pour avoir une idée approximative de la quantité de mémoire utilisée par les VIs.

Règles pour une meilleure utilisation de la mémoire

Le point essentiel de la discussion précédente est que le compilateur essaie de réutiliser la mémoire intelligemment. Les règles qui déterminent quand le compilateur peut ou non réutiliser la mémoire sont complexes. En pratique, les règles suivantes peuvent vous aider à créer des VIs qui utilisent la mémoire efficacement :

  • En général, la division d'un VI en sous-VIs n'augmente pas l'utilisation de la mémoire. Dans bons nombres de cas, l'utilisation de la mémoire est améliorée car le système d'exécution pour réutiliser la mémoire des données d'un sous-VI lorsque ce sous-VI ne s'exécute pas.
  • Ne vous souciez pas trop des copies des valeurs scalaires ; il faut beaucoup de scalaires pour affecter négativement l'utilisation de la mémoire.
  • Évitez un usage excessif des variables globales et locales lorsque vous travaillez avec des tableaux ou des chaînes. La lecture d'une variable globale ou locale force LabVIEW à générer une copie des données.
  • N'affichez pas de grands tableaux ou de longues chaînes sur les faces-avant si ce n'est pas indispensable. Les commandes et indicateurs des faces-avant ouvertes conservent une copie des données qu'ils affichent.
Astuce  Si vous avez besoin d'utiliser un indicateur graphe déroulant, sachez que l'historique du graphe déroulant conservera une copie des données qu'il affiche. Ceci continue jusqu'à ce que l'historique du graphe déroulant soit plein ; à ce moment-là, LabVIEW arrête de prendre davantage de mémoire. LabVIEW n'efface pas automatiquement l'historique du graphe déroulant quand le VI redémarre ; il est donc utile d'effacer l'historique du graphe déroulant au fur et à mesure que le programme s'exécute. Pour cela, écrivez un tableau vide au nœud d'attribut Données d'historique du graphe déroulant.
  • Utilisez la propriété Retarder MàJ face-avant. Si vous définissez cette propriété sur VRAI, les valeurs des indicateurs de la face-avant ne changent pas, même si vous changez la valeur d'une commande. Votre système d'exploitation n'a pas besoin d'utiliser de la mémoire pour donner la ou les nouvelles valeurs au ou aux indicateurs.
Remarque  Normalement, LabVIEW n'ouvre pas la face-avant des sous-VIs qu'il appelle.
  • Si la face-avant d'un sous-VI ne sera pas affichée, ne laissez pas de nœuds de propriété inutilisés sur le sous-VI. S'il y a des nœuds de propriété, la face-avant d'un sous-VI reste en mémoire, ce qui produit une utilisation superflue de la mémoire.
  • Lorsque vous développez des diagrammes, recherchez les endroits où la taille d'une entrée diffère de celle de la sortie. Par exemple, si vous voyez des endroits où vous augmentez souvent la taille d'un tableau ou d'une chaîne à l'aide des fonctions Construire un tableau ou Concaténer des chaînes, vous générez des copies des données.
  • Utilisez des types de données cohérents pour les tableaux et recherchez les points de coercition lorsque vous transférez des données à des sous-VIs et à des fonctions. Lorsque vous changez les types de données, le système d'exécution fait une copie des données.
  • N'utilisez pas de types de données hiérarchiques et compliqués comme des clusters ou des tableaux de clusters contenant de grands tableaux ou de longues chaînes. Vous risqueriez d'utiliser davantage de mémoire. Si possible, utilisez des types de données plus efficaces.
  • N'utilisez pas d'objets de face-avant transparents ou qui se chevauchent s'ils ne sont pas indispensables. Ces objets risquent d'utiliser plus de mémoire.

Reportez-vous à la Liste de vérification de style LabVIEW pour obtenir des suggestions spécifiques pour optimiser les performances d'un VI.

Problèmes de mémoire dans les faces-avant

Lorsqu'une face-avant est ouverte, les commandes et les indicateurs conservent leur propre copie privée des données qu'ils affichent.

Le diagramme suivant montre la fonction Incrémenter avec les commandes et les indicateurs de la face-avant.

Lorsque vous exécutez le VI, les données de la commande de la face-avant sont transmises au diagramme. La fonction d'incrémentation réutilise le buffer d'entrée. L'indicateur fait alors une copie des données pour les afficher. Il y a donc trois copies du buffer.

Cette protection des données de la commande de la face-avant vous empêche de voir les données changer dans la commande quand vous entrez les données dans une commande, que vous exécutez le VI associé et que les données sont transmises sur place aux nœuds suivants. De même, les données sont protégées pour les indicateurs, de sorte qu'ils affichent le contenu précédent jusqu'à ce qu'ils reçoivent de nouvelles données.

Avec les sous-VIs, vous pouvez utiliser les commandes et les indicateurs comme entrées et sorties. Le système d'exécution fait une copie des données des commandes et des indicateurs du sous-VI dans les cas suivants :

  • La face-avant se trouve en mémoire. Ceci peut se produire pour l'une des raisons suivantes :
    • La face-avant est ouverte.
    • Le VI a été changé mais pas enregistré (tous les composants du VI restent en mémoire tant que le VI n'est pas enregistré).
  • La face-avant utilise l'impression de données.
  • Le diagramme utilise des nœuds de propriété.
  • Le VI utilise des variables locales.
  • La face-avant utilise l'enregistrement des données.

Pour qu'un nœud de propriété puisse lire l'historique d'un graphe déroulant dans des sous-VIs dont les faces-avant sont fermées, la commande ou l'indicateur doit afficher les données qu'elle ou il reçoit. Comme il y a beaucoup d'autres attributs similaires, le système d'exécution conserve la face-avant du sous-VI en mémoire si le sous-VI utilise des nœuds de propriété.

Si une face-avant utilise l'enregistrement ou l'impression des données de la face-avant, les commandes et les indicateurs conservent des copies de leurs données. De plus, les faces-avant sont conservées en mémoire pour l'impression des données afin de pouvoir imprimer la face-avant.

Si vous paramétrez un sous-VI en utilisant les boîtes de dialogue Propriétés du VI ou Configuration du nœud du sous-VI pour que la face-avant du VI s'affiche lorsqu'il est appelé, la face-avant se charge dans la mémoire lorsque vous appelez le sous-VI. Si vous sélectionnez l'élément Fermer après l'exécution si fermée à l'origine, LabVIEW supprime la face-avant de la mémoire lorsque le sous-VI termine son exécution.

Les sous-VIs peuvent réutiliser la mémoire de données

En général, un sous-VI peut utiliser les buffers de données du VI qui l'appelle aussi facilement que si les diagrammes du sous-VI étaient dupliqués au niveau supérieur. Dans la plupart des cas, vous n'utilisez pas davantage de mémoire si vous convertissez une section de votre diagramme en sous-VI. Pour les VIs qui ont des spécifications d'affichage particulières, comme le décrit la section précédente, il se peut que les faces-avant et les commandes utilisent plus de mémoire.

Comprendre quand la mémoire est libérée

Étudiez le diagramme suivant.

Une fois le VI Moyenne exécuté, le tableau de données n'est plus nécessaire. Comme il peut être très difficile de déterminer quand les données ne sont plus utilisées dans le cas de plus grands diagrammes, le système d'exécution ne libère pas les buffers de données d'un VI particulier pendant qu'il s'exécute.

Sur Macintosh, si le système d'exécution a presque épuisé sa mémoire, il libère les buffers de données utilisés par les VIs qui ne sont pas en cours d'exécution. Le système d'exécution ne libère pas la mémoire utilisée par les commandes et les indicateurs de la face-avant, les variables globales et les registres à décalage qui ne sont pas initialisés.

Considérez maintenant le même VI comme étant un sous-VI d'un plus grand VI. Le tableau de données est créé et utilisé uniquement dans le sous-VI. Sur Macintosh, si le sous-VI n'est pas en cours d'exécution et que le système est à cours de mémoire, il peut libérer les données dans le sous-VI. Dans ce cas, l'utilisation de sous-VIs peut diminuer l'utilisation de la mémoire.

Normalement, sur les plates-formes Windows et Linux, les buffers de données ne sont libérés que si un VI est fermé et supprimé de la mémoire. La mémoire est allouée à partir du système d'exploitation en fonction des besoins, et la mémoire virtuelle fonctionne bien sur ces plates-formes. À cause de la fragmentation, il se peut que l'application semble utiliser plus de mémoire qu'en réalité. Au fur et à mesure que de la mémoire est allouée et libérée, l'application essaie de regrouper la mémoire utilisée pour pouvoir rendre les blocs inutilisés au système d'exploitation.

Vous pouvez utiliser la fonction Demander une désallocation pour libérer la mémoire inutilisée une fois que le VI qui contient cette fonction a terminé son exécution. Cette fonction n'est utilisée que pour les optimisations de performances avancées. Dans certains cas, la libération de la mémoire inutilisée peut améliorer les performances. Cependant, si la mémoire est libérée agressivement, LabVIEW risque de réallouer de l'espace de manière répétée plutôt que de réutiliser une allocation. Utilisez cette fonction si votre VI alloue pour un grand espace de données mais ne réutilise jamais cette allocation. Lorsqu'un VI de niveau principal appelle un sous-VI, LabVIEW alloue un espace de données de mémoire dans lequel ce sous-VI s'exécute. Quand le sous-VI a terminé son exécution, LabVIEW ne libère pas l'espace de données tant que le VI de niveau principal n'a pas fini de s'exécuter ou que toute l'application n'est pas arrêtée, ce qui peut causer des conditions de mémoire insuffisante et une baisse des performances. Placez la fonction Demander une désallocation dans le sous-VI dont vous voulez libérer la mémoire. Lorsque vous définissez l'entrée booléenne de flag à VRAI, LabVIEW réduit l'utilisation de la mémoire en libérant l'espace de données pour ce sous-VI.

Comment déterminer si les sorties peuvent réutiliser les buffers d'entrée

Si la taille et le type de données d'une sortie sont les mêmes que ceux de l'entrée et que l'entrée n'est pas utilisée ailleurs, la sortie peut réutiliser le buffer d'entrée. Comme mentionné plus tôt, dans certains cas, le compilateur et le système d'exécution peuvent ordonner l'exécution du code de sorte qu'il est possible de réutiliser l'entrée pour un buffer de sortie, même si l'entrée est utilisée ailleurs. Cependant, les règles utilisées sont complexes. Ne vous basez pas sur ces règles.

Vous pouvez utiliser la fenêtre Afficher les allocations de buffer pour voir si un buffer de sortie réutilise le buffer d'entrée. Dans le diagramme suivant, le fait de placer un indicateur dans chaque cas d'une structure Condition interrompt le flux des données car LabVIEW crée une copie des données pour chaque indicateur. Au lieu d'utiliser le buffer qu'il a créé pour le tableau en entrée, LabVIEW crée une copie des données pour le tableau en sortie.

Si vous déplacez l'indicateur pour le placer à l'extérieur de la structure Condition, le buffer de sortie réutilise le buffer d'entrée car LabVIEW n'a pas besoin de faire de copie des données affichées par l'indicateur. Comme LabVIEW n'a pas besoin de la valeur du tableau en entrée plus tard dans le VI, la fonction d'incrémentation peut modifier directement le tableau en entrée et le passer au tableau en sortie. Dans ce cas, LabVIEW n'a pas besoin de copier les données et aucun buffer n'apparaît sur le tableau en sortie, comme l'illustre le diagramme suivant.

Optimisation de l'utilisation de la mémoire en utilisant les mêmes types de données

Si vous utilisez des types de données cohérents, LabVIEW peut réutiliser les buffers de mémoire alloués à une entrée quand il génère des données en sortie. Ceci permet d'optimiser l'utilisation de la mémoire et le temps d'exécution du VI. Cependant, si le type de données d'une entrée est différent de celui de la sortie, la sortie ne peut pas réutiliser la mémoire que LabVIEW alloue pour cette entrée.

Par exemple, si vous ajoutez un entier 32 bits à un entier 16 bits, LabVIEW contraint l'entier 16 bits à un entier 32 bits. Le compilateur crée un nouveau buffer pour les données contraintes et LabVIEW place un point de coercition au niveau de la fonction Additionner. Dans cet exemple, LabVIEW peut utiliser l'entrée de l'entier 32 bits pour le buffer de sortie si l'entrée répond à toutes les autres exigences. Cependant, comme LabVIEW contraint l'entier 16 bits, il ne peut pas réutiliser la mémoire allouée pour cette entrée.

Pour minimiser l'utilisation de la mémoire, utilisez des types de données cohérents dans la mesure du possible. L'utilisation de types de données cohérents diminue le nombre de copies des données produites lorsque LabVIEW contraint les données à une taille supérieure. Dans certaines applications, vous pourriez également envisager d'utiliser des types de données plus petits afin de minimiser l'utilisation de données. Par exemple, vous pourriez utiliser des nombres 4 octets à simple précision au lieu de nombres 8 octets à double précision. Cependant, afin d'éviter des coercitions superflues, faites correspondre les types de données des objets que vous câblez à un sous-VI à ceux que le sous-VI attend.

Optimisation de l'utilisation de la mémoire quand des données d'un type spécifique sont générées

Lorsque vous devez générer des données d'un type spécifique, il se peut que vous deviez convertir des types de données sur le diagramme. Cependant, vous pouvez optimiser l'utilisation de la mémoire en convertissant les types de données à l'aide des fonctions de conversion avant que LabVIEW ne crée de grands tableaux.

Dans l'exemple suivant, les données en sortie doivent être du type virgule flottante simple précision afin de correspondre au type de données d'une entrée d'un autre VI. LabVIEW crée un un tableau de 1000 valeurs aléatoires et l'ajoute à une valeur scalaire. LabVIEW place un point de coercition sur la fonction Additionner car il contraint le type de données scalaire simple précision à virgule flottante à correspondre aux nombres aléatoires à virgule flottante double précision.

Le diagramme suivant montre une tentative de résolution du problème en convertissant le tableau de nombres à virgule flottante double précision à l'aide de la fonction En flottant simple précision.

Puisque la fonction continue de convertir le type de données du grand tableau après que LabVIEW l'a créé, le VI utilise la même quantité de mémoire que dans l'exemple des points de coercition.

Le diagramme suivant illustre comme optimiser l'utilisation de la mémoire et la vitesse d'exécution en convertissant les nombres aléatoires avant que LabVIEW ne crée le tableau.

Si vous devez convertir des types de données, vous pouvez utiliser une fonction de conversion avant que LabVIEW ne crée un tableau afin d'éviter de convertir le grand buffer de données alloué à ce tableau d'un type de données à un autre. La conversion des données avant qu'un grand tableau soit créé par LabVIEW permet d'optimiser l'utilisation de la mémoire d'un VI.

Éviter de redimensionner constamment les données

Si la taille d'une sortie est différente de la taille d'une entrée, la sortie ne réutilise pas le buffer de données en entrée. C'est le cas pour les fonctions telles que Construire un tableau, Concaténer des chaînes et Sous-ensemble d'un tableau, qui augmentent ou diminuent la taille d'un tableau ou d'une chaîne. Lorsque vous utilisez des tableaux ou des chaînes, évitez d'utiliser constamment ces fonctions, car votre programme utilisera davantage de mémoire de données et sera plus lent du fait qu'il doit constamment copier des données.

Exemple 1 : construction de tableaux

Considérez le diagramme suivant, utilisé pour créer un tableau de données. Ce diagramme crée un tableau dans une boucle en appelant constamment la fonction Construire un tableau pour concaténer un nouvel élément. Le tableau en entrée est réutilisé par la fonction Construire un tableau. Le VI redimensionne continuellement le buffer à chaque itération pour laisser de la place pour le nouveau tableau et ajoute le nouvel élément à la fin. L'exécution est plus lente, surtout si la boucle est exécutée beaucoup de fois.

Remarque  Lorsque vous manipulez des tableaux, des clusters, des waveforms et des données variant, vous pouvez utilisez la structure Élément en place pour optimiser l'utilisation de la mémoire dans les VIs.

Si vous voulez ajouter une valeur au tableau à chaque itération de la boucle, vous obtiendrez les performances les meilleures en utilisant l'auto-indexation sur le bord de la boucle. Pour les boucles For, le VI peut déterminer à l'avance la taille du tableau (selon la valeur câblée à N) et redimensionner le buffer une seule fois.

Avec les boucles While, l'auto-indexation n'est pas aussi efficace car la taille finale du tableau est inconnue. Cependant, dans le cas des boucles While, l'auto-indexation évite de redimensionner le tableau en sortie à chaque itération en augmentant de grands incréments la taille du tableau en sortie. Lorsque la boucle est terminée, le tableau en sortie est redimensionné à la taille appropriée. Les performances de l'auto-indexation des boucles While sont presque identiques à celles de l'auto-indexation des boucles For.

L'auto-indexation suppose que vous allez ajouter une valeur au tableau résultant à chaque itération de la boucle. Si vous devez ajouter conditionnellement des valeurs à un tableau, mais que vous pouvez déterminer la limite supérieure de la taille de ce tableau, vous devriez envisager de pré-allouer le tableau et d'utiliser la fonction Remplacer une portion d'un tableau pour remplir le tableau.

Quand vous avez fini de remplir les valeurs du tableau, vous pouvez redimensionner le tableau à la taille appropriée. Le tableau n'est créé qu'une fois, et la fonction Remplacer une portion d'un tableau peut réutiliser le buffer d'entrée pour le buffer de sortie. Dans ce cas, les performances sont très proches de celles des boucles qui utilisent l'auto-indexation. Si vous utilisez cette technique, assurez-vous que le tableau dans lequel vous remplacez les valeurs est assez grand pour contenir les données résultantes car la fonction Remplacer une portion d'un tableau ne redimensionne pas les tableaux pour vous.

Le diagramme suivant représente un exemple de ce processus.

Exemple 2 : recherche dans les chaînes

Vous pouvez utiliser la fonction Rechercher une expression pour rechercher une expression dans une chaîne. Selon la façon dont vous l'utilisez, les performances risquent d'être affectées par la création superflue de buffers de données de chaîne.

Si vous voulez rechercher un entier dans une chaîne, vous pouvez utiliser [0–9]+ comme entrée d'expression régulière de cette fonction. Pour créer un tableau comportant tous les entiers d'une chaîne, utilisez une boucle et appelez la fonction Rechercher une expression régulière de manière répétée jusqu'à ce que la valeur renvoyée pour l'offset soit –1.

Le diagramme suivant représente une méthode de balayage de toutes les occurrences d'entiers dans une chaîne. Il crée un tableau vide puis recherche l'expression numérique dans le reste de la chaîne à chaque itération de la boucle. Si l'expression est trouvée (l'offset n'est pas –1), ce diagramme utilise la fonction Construire un tableau pour ajouter le nombre à un tableau de nombres résultant. S'il n'y a plus de valeurs dans la chaîne, la fonction Rechercher une expression régulière renvoie –1, et le diagramme termine son exécution.

Ce diagramme a un problème : il utilise la fonction Construire un tableau dans la boucle pour concaténer la nouvelle valeur à la valeur précédente. À la place, vous pouvez utiliser l'auto-indexation pour accumuler les valeurs au bord de la boucle. Remarquez qu'il y a une valeur supplémentaire indésirable dans le tableau, produite à la dernière itération de la boucle, quand la fonction Rechercher une expression régulière n'a pas trouvé d'expression. Vous pouvez résoudre ce problème en utilisant la fonction Sous-ensemble d'un tableau pour supprimer la valeur indésirable. Ceci est représenté dans le diagramme suivant.

L'autre problème de ce diagramme, c'est que vous créez une copie superflue du reste de la chaîne à chaque itération de la boucle. La fonction Rechercher une expression régulière comporte une entrée qui vous permet d'indiquer où la recherche doit commencer. Si vous connaissez l'offset de l'itération précédente, vous pouvez utiliser ce nombre pour indiquer où commencer la recherche à l'itération suivante. Cette technique est représentée dans le diagramme suivant.

Développement de structures de données efficaces

L'exemple précédent expliquait, entre autres, que les structures de données hiérarchiques, comme les clusters et les tableaux de clusters qui contiennent de grands tableaux ou de longues chaînes, ne peuvent pas être manipulées efficacement. Cette section explique pourquoi et décrit des stratégies pour sélectionner des types de données plus efficaces.

Le problème des structures de données compliquées, c'est qu'il est difficile d'accéder aux éléments d'une structure de données et de les changer sans générer de copie des éléments auxquels vous accédez. Si ces éléments sont de grande taille, comme c'est le cas si l'élément même est un tableau ou une chaîne, ces copies supplémentaires utilisent plus de mémoire et il faut plus de temps pour copier la mémoire.

En général, les types de données scalaires peuvent être manipulés très efficacement. De même, vous pouvez manipuler efficacement les chaînes courtes et les petits tableaux si l'élément est un scalaire. Dans le cas d'un tableau de scalaires, le code suivant montre comment incrémenter une valeur d'un tableau.

Remarque  Lorsque vous manipulez des tableaux, des clusters, des waveforms et des données variant, vous pouvez utiliser la structure Élément en place pour améliorer l'utilisation de mémoire dans les VIs. De nombreuses opérations de LabVIEW nécessite que celui-ci copie et conserve les valeurs de données en mémoire, ce qui ralentit la vitesse d'exécution et accroît l'utilisation de la mémoire . La structure Élément en place effectue les opérations courantes de LabVIEW sans que LabVIEW ne doive faire des copies multiples des valeurs de données en mémoire. À la place, la structure Élément en place agit sur les éléments de données dans le même espace de mémoire et renvoie ces éléments au même emplacement dans le tableau, le cluster, le variant ou la waveform. Comme LabVIEW renvoie les éléments de données au même emplacement de la mémoire, le compilateur LabVIEW ne doit pas faire de copies supplémentaires des données en mémoire.

Ceci est assez efficace car il n'est pas nécessaire de générer de copies supplémentaires du tableau global. De plus, l'élément produit par la fonction Indexer un tableau est un scalaire, et il peut être créé et manipulé efficacement.

C'est la même chose pour les tableaux de clusters si le cluster ne contient que des scalaires. Dans le diagramme suivant, la manipulation des éléments devient un peu plus compliquée car il faut utiliser les fonctions Désassembler et Assembler. Cependant, comme le cluster est probablement petit (les scalaires n'utilisent que très peu de mémoire), l'accès aux éléments du cluster et la remise en place des éléments dans le cluster d'origine ne demandent pas beaucoup de temps système.

Le diagramme suivant montre la méthode efficace pour effectuer le désassemblage, l'opération et le réassemblage. Le fil de liaison de la source de données ne devrait avoir que deux destinations : l'entrée de la fonction Désassembler et le terminal du milieu de la fonction Assembler. LabVIEW reconnaît ce modèle et peut générer du code plus performant.

Si vous avez un tableau de clusters et que chaque cluster contient de grands sous-tableaux ou de longues chaînes, l'indexation et le changement des valeurs des éléments du cluster peuvent consommer plus de mémoire et ralentir l'exécution.

Lorsque vous indexez un élément dans le tableau global, cet élément est copié. Par conséquent, le cluster et le grand sous-tableau ou la longue chaîne correspondant sont copiés. Comme la taille des chaînes et des tableaux est variable, le processus de copie peut comporter des appels d'allocation de mémoire pour qu'une chaîne ou un sous-tableau ait la taille appropriée, et ces appels s'ajoutent au temps système pris par la copie des données d'une chaîne ou d'un sous-tableau. Ceci ne sera peut-être pas significatif si vous ne le faites que quelques fois. Mais si votre application repose sur un accès fréquent à cette structure de données, le temps système pris par la mémoire et l'exécution peut vite devenir considérable.

La solution consiste à représenter vos données d'une autre façon. Les trois études de cas suivantes présentent trois applications différentes et des suggestions sur les meilleures structures de données pour chaque cas.

Étude de cas 1 : comment éviter les types de données compliqués

Considérez une application dans laquelle vous voulez enregistrer les résultats de plusieurs tests. Dans les résultats, vous voulez une chaîne qui décrit le test et un tableau des résultats du test. Vous pourriez utiliser le type de données présenté dans la face-avant suivante pour enregistrer ces informations.

Pour changer un élément du tableau, vous devez indexer un élément du tableau global. Ensuite, vous devez désassembler les éléments de ce cluster pour avoir accès au tableau. Puis vous remplacez un élément du tableau et enregistrez le tableau résultant dans le cluster. Pour finir, vous enregistrez le cluster résultant dans le tableau d'origine. Le diagramme suivant représente un exemple.

Chaque niveau de désassemblage/indexation peut provoquer la génération d'une copie de ces données. Remarquez qu'une copie n'est pas toujours générée. La copie de données consomme du temps et de la mémoire. Pour résoudre ce problème, il faut essayer de rendre les structures de données aussi plates que possible. Par exemple, dans cette étude de cas, répartissez la structure de données en deux tableaux. Le premier tableau est le tableau de chaînes. Le second tableau est un tableau 2D, dans lequel chaque ligne correspond aux résultats d'un test donné. Ce résultat est représenté dans la face-avant suivante.

Avec cette structure de données, vous pouvez remplacer directement un élément d'un tableau en utilisant la fonction Remplacer une portion d'un tableau, comme le montre le diagramme suivant.

Étude de cas 2 : table globale de données de types différents

Voici une autre application dans laquelle vous voulez maintenir une table d'informations. Dans cette application, vous voulez que les données soient accessibles globalement. Cette table peut contenir les paramètres d'un instrument, y compris le gain, les limites de tensions inférieure et supérieure, et le nom utilisé pour faire référence à la voie.

Pour que les données soient accessibles de toute l'application, vous pourriez créer un ensemble de sous-VIs pour accéder aux données de la table comme les sous-VIs Changer les infos d'une voie et Supprimer les infos d'une voie de l'illustration suivante.

Les sections suivantes présentent trois implémentations différentes de ces VIs.

Implémentation évidente

Avec cet ensemble de fonctions, il y a plusieurs structures de données à considérer pour la table sous-jacente. D'abord, vous pourriez utiliser une variable globale contenant un tableau de clusters, chaque cluster contenant le gain, les limites inférieure et supérieure, et le nom de la voie.

Comme décrit dans la section précédente, cette structure de données est difficile à manipuler efficacement car il faut en général passer par plusieurs niveaux d'indexation et de désassemblage pour accéder aux données. En outre, comme la structure de données est un assemblage de plusieurs informations, vous ne pouvez pas utiliser la fonction Rechercher dans un tableau 1D pour trouver une voie. Vous pouvez utiliser la fonction Rechercher dans un tableau 1D pour trouver un cluster précis dans un tableau de clusters, mais vous ne pouvez pas l'utiliser pour trouver un élément dans un cluster particulier.

Autre implémentation - 1

Comme dans l'exemple précédent, choisissez de conserver les données dans deux tableaux. L'un des tableaux contient les noms des voies. L'autre contient les données des voies. L'index d'un nom de voie donné dans le tableau de noms permet de trouver les données de cette voie dans l'autre tableau.

Le tableau de chaînes étant séparé des données, vous pouvez utiliser la fonction Rechercher dans un tableau 1D pour trouver une voie.

En pratique, si vous créez un tableau de 1000 voies avec le VI Changer les infos d'une voie, l'implémentation est à peu près deux fois plus rapide que la version précédente. Ce changement n'est pas très significatif car les performances sont affectées par d'autres temps système.

Lorsque vous lisez une variable globale, une copie des données de cette variable est générée. Par conséquent, une copie intégrale des données du tableau est générée à chaque fois que vous accédez à un élément. La méthode suivante montre une façon plus efficace qui élimine ce temps système.

Autre implémentation - 2

Il existe une autre méthode pour enregistrer les données globales ; elle utilise un registre à décalage non initialisé. Par définition, si vous ne câblez pas de valeur initiale, un registre à décalage se souvient de sa valeur d'un appel à l'autre.

Le compilateur LabVIEW gère efficacement l'accès aux registres à décalage. La lecture de la valeur d'un registre à décalage ne génère pas nécessairement une copie des données. En fait, vous pouvez indexer un tableau enregistré dans un registre à décalage, et changer ou mettre à jour sa valeur, sans générer de copies supplémentaires du tableau global. Le problème des registres à décalage, c'est que seul le VI qui contient le registre peut accéder aux données du registre. En revanche, le registre à décalage a l'avantage d'être modulaire.

Vous pouvez construire un sous-VI unique avec une entrée de mode qui indique si vous voulez lire, modifier ou supprimer une voie, ou si vous voulez mettre les données de toutes les voies à zéro.

Le sous-VI contient une boucle While avec deux registres à décalage : un pour les données des voies, l'autre pour les noms des voies. Ni l'un ni l'autre de ces registres à décalage n'est initialisé. Ensuite, vous placez une structure Condition dans la boucle While et vous la connectez à l'entrée de mode. Selon la valeur du mode, vous pouvez lire et peut-être changer les données du registre à décalage.

L'illustration suivante représente un sous-VI dont l'interface gère ces trois modes. Seul le code de Changer les infos d'une voie est présenté.

Pour 1000 éléments, cette implémentation est deux fois plus rapide que la précédente, et quatre fois plus rapide que la première.

Étude de cas 3 : table globale statique de chaînes

L'exemple précédent concernait une application dans laquelle la table contenait divers types de données et était susceptible de changer souvent. Dans beaucoup d'applications, vous avez une table d'informations qui reste assez statique une fois créée. La table peut être lue à partir d'un fichier tableur. Une fois lue en mémoire, elle est utilisée principalement pour rechercher des informations.

Dans ce cas, votre implémentation peut consister des deux fonctions suivantes : Initialiser un tableau à partir d'un fichier et Obtenir un enregistrement d'un tableau.

Une façon d'implémenter la table est d'utiliser un tableau de chaînes à deux dimensions. Remarquez que le compilateur enregistre chaque chaîne d'un tableau de chaînes dans un bloc différent de la mémoire. S'il y a beaucoup de chaînes (plus de 5000 chaînes, par exemple), cela peut charger le gestionnaire de mémoire. Cette charge peut se traduire par une dégradation perceptible des performances car le nombre d'objets individuels augmente.

Une alternative pour stocker une grande table est de lire la table en une seule chaîne. Ensuite, construisez un autre tableau qui contient les offsets de chaque enregistrement dans la chaîne. Au lieu d'avoir potentiellement des milliers de blocs de mémoire relativement petits, vous avez maintenant un grand bloc de mémoire (la chaîne) et un autre bloc de mémoire plus petit (le tableau d'offsets).

Cette méthode est peut-être plus difficile à implémenter, mais elle peut s'avérer beaucoup plus rapide pour les grandes tables.

CET ARTICLE VOUS A-T-IL ÉTÉ UTILE ?

Pas utile