Gestion de la mémoire
Segments de mémoire
Introduction
Un module est découpé en composants. Chacun de ces composants peut avoir des déclarations privées introduites par Prive, des déclarations protégées introduites par Protege ou des déclarations publiques introduites par Public.
Les déclarations privées d'un composant sont uniquement utilisables par ce composant.
Les déclarations protégées d'un composant sont utilisables par ce composant mais aussi par les autres composants du module qui sont énumérés par ce composant dans le source du module.
Les déclarations publiques des composants sont utilisables par tous les composants de tous les modules important l'interface du module. L'interface d'un module n'est en fait que le regroupement des déclarations publiques des composants de ce module.
Lorsque ces déclarations induisent des données (types, variables, exceptions), celles-ci sont regroupées dans des segments. Il y deux segments par composant, un pour les données issues des déclarations privées et un pour les données issues des déclarations protégées. Il y a un segment par module, pour les données issues des déclarations publiques. Ces trois types de segment sont respectivement identifiés par les instructions SegmentPrive, SegmentProtege et SegmentPublic.
Si ces données ne sont pas des objets (variables correspondant à un entier non nul, à un réel non nul ou un énuméré non nul), elles sont directement mémorisées dans le segment.
Si ces données sont des objets (des représentants des types, des entrepôts, des modules, ou des variables ne correspondant pas à un entier non nul, un réel non nul ou à un énuméré), alors une référence vers cet objet est mémorisée dans le segment. L'objet est alors alloué dynamiquement dans un entrepôt de stockage.
Classification des programmes
Vis-à-vis de la gestion de la mémoire, nous pouvons classer les besoins des programmes en trois catégories. Il y a :
- Les programmes devant partager des données.
Cela a notamment lieu lorsque l'applicatif est découpé en plusieurs programmes communiquants.
- Les programmes devant offrir des services à un ou plusieurs clients.
Cela a notamment lieu dans une architecture client-serveur deux tiers ou trois tiers.
- Les programmes autonomes vis-à-vis des autres programmes.
Cette situation est la plus usuelle.
Instances d'un programme
Plaçons nous dans le cadre d'un programme offrant des services à plusieurs programmes clients. Une des difficultés de la programmation du programme serveur est de gérer le contexte de chaque client, servant à conserver par exemple le résultat des dernières actions réalisées. Cela est particulièrement pertinent dans le cas d'Internet.
Up ! Virtual Technical Machine possède pour cela le concept d'instance permettant de gérer plusieurs copies d'un programme ou de portions d'un programme au sein d'un même processus. Il y a une instance par client du serveur. Les API de Up ! System permettent de créer une nouvelle instance (CreerInstance), de détruire une instance devenue inutile (ArreterInstance) ou de changer d'instance (CommuterInstance). Un programme classique possède une unique instance.
Une seule instance est active à la fois. C'est à dire qu'une seule demande d'un client est servie à la fois pour un serveur donné.
Segments privés
Déclarer des segments de mémoire privée permet de déclarer des données qui sont privées à chaque instance du programme. Cela s'effectue au moyen de l'instruction MemoirePrivee.
Lors de la création d'une nouvelle instance d'un programme, une nouvelle copie du segment de mémoire privée est créée. Cette copie est initialisée comme si le programme redémarrait. Une initialisation personnalisée peut être effectuée au moyen de la procédure DebuterComposant.
Lors de la terminaison d'une instance d'un programme, la copie du segment de mémoire privée est détruite. Cette copie est détruite comme si le programme s'arrêtait. Une terminaison personnalisée peut être effectuée au moyen de la procédure TerminerComposant.
Segments protégés
Déclarer des segments de mémoire protégée permet de déclarer des données qui sont partagées par chaque instance du processus mais en revanche propre à chaque processus. Cela s'effectue au moyen de l'instruction MemoireProtegee.
Lors du démarrage du programme, le segment de mémoire protégé est créé. Une initialisation personnalisée peut être effectuée au moyen de la procédure DebuterComposant.
Lors de la terminaison du programme, le segment de mémoire protégé est détruit. Une terminaison personnalisée peut être effectuée au moyen de la procédure TerminerComposant.
Segments publics
Déclarer des segments de mémoire public permet de partager aisément des données entre des programmes. Cela s'effectue au moyen de l'instruction MemoirePublique.
Lors du démarrage du premier programme utilisant ce module, le segment de mémoire public est créé. Une initialisation personnalisée peut être effectuée au moyen de la procédure DebuterComposant.
Lors de la terminaison du dernier programme utilisant ce module, le segment de mémoire public est détruit. Une terminaison personnalisée peut être effectuée au moyen de la procédure TerminerComposant.
Exemple d'emploi de segments privés, protégés et publics
Il y a deux programmes P1 et P2. P1 est constitué des modules A, B, C et des modules de Up ! Virtual Technical Machine. P2 est constitué des modules C, D et des modules de Up ! Virtual Technical Machine.
P1 est un serveur. Dans le cas présent, il gère deux clients ; il y a donc deux instances. P2 est un programme classique ; il y a donc une instance.
Le module A est composé de segments privés. Il y a donc une copie de ses segments par instance du programme P1.
Le module B est composé de segments protégés. Ses segments sont donc partagés par les deux instances du programme P1.
Le module C est composé de segments publics. Ses segments sont donc partagés par les deux instances du programme P1 et par l'instance du programme P2.
Le module D est composé de segments privés. Il y a donc une copie de ses segments par instance du programme P2.
Accès aux données des segments
Up ! Application System permet de réaliser des programmes multi-tâches et des applicatifs multi-processus. Le problème est alors de gérer l'accès concurrent à ces données entre ces différentes tâches. En effet, deux tâches peuvent vouloir mettre à jour ou lire simultanément la même donnée.
Pour les objets, ceux-ci sont stockés dans des entrepôts de mémoire. Up ! Object Management System assure alors un contrôle automatique à leur accès au moyen de synchronisations implicites. Ainsi :
- Une unique tâche peut accéder à un objet pour une écriture.
- Plusieurs tâches peuvent accéder simultanément à un objet pour une lecture.
Si une concurrence d'accès a lieu, alors tâche qui tente de prendre un objet est bloquée tant que celui-ci n'est pas relâché.
Pour les données qui ne sont pas des objets, ou si des synchronisations élémentaires ne sont pas suffisantes pour maintenir les données en cohérence, il est alors possible de déclarer des synchronisations au moyen du type Synchronisation de Up ! System.
Ramasse-miette
Lors de la programmation en Up ! 5GL, il est nullement nécessaire de se préoccuper de l'allocation des objets. Cela s'effectue automatiquement par génération d'instructions implicites spécialisées. Ces dernières allouent tous les objets dans la mémoire de travail du processus. Il existe deux types objets :
- Les objets explicites référencés par une variable ou une propriété d'un type.
- Les objets implicites référencés par un temporaire de calcul. Les temporaires sont des variables automatiques déclarées implicitement par le générateur.
Lorsqu'un objet n'est plus utile, sa place mémoire est libérée en vue d'être utilisée ultérieurement. Par exemple :
- Lorsque le programme quitte une procédure, les objets référencés par les variables locales ne sont plus utiles si ceci sont uniquement référencés par ces variables.
- Suite à des calculs, les objets référencés par les temporaires deviennent désormais inutiles.
Le rôle du ramasse-miettes implémenté dans Up ! Object Management System est de gérer le cycle de vie des objets :
- Les allouer dans la mémoire de travail sur demande.
- Les retrouver d'après une référence qui les identifie de manière unique.
- Contrôler leurs accès.
- Libérer la place qu'ils occupent lorsque ces derniers deviennent inutiles.
Au besoin, lorsque cela est nécessaire, Up ! Object Management System compacte la mémoire de travail en vue de regrouper les parties libres de la mémoire de travail. L'objectif est que la mémoire de travail ne ressemble pas à un énorme gruyère où, à force d'allocations et de désallocations, il n'y aurait plus de trous que de fromage !
Entrepôts
Introduction
Les segments de données correspondent à de la mémoire statique. En dehors de la création ou de la destruction des instances ou des programmes, elle ne varie pas. Cette mémoire statique est complétée à l'exécution par de la mémoire dynamique qui est la mémoire de travail d'Up ! Object Management System.
Les caractéristiques de la mémoire de travail sont fixées à l'exécution, ce qui la rend indépendante du programme. Cela permet de pouvoir régler les programmes en fonction des différents usages que l'utilisateur et sans avoir à les recompiler.
Partitionnement de la mémoire de travail
La mémoire de travail d'un processus en technologie Up ! Virtual Technical Machine peut être découpée en différentes zones appelées entrepôts. Ces entrepôts constituent des réserves de mémoire dynamique pour Up ! Object Management System dans lesquelles sont stockés tous les objets manipulés par les tâches.
Un entrepôt particulièr est l'entrepôt Système. Il est utilisé par Up ! Object Management System pour son fonctionnement interne, mais il peut être utilisé par les traitements du programme. Par défaut, il n'y a pas d'entrepôt utilisateur, mais il est possible d'en créer au plus 32766 (soit 32767 en tout avec l'entrepôt Système).
L'intérêt de découper la mémoire de travail en entrepôts est multiple :
- Cela permet de typer la mémoire de travail en fonction des besoins.
- Cela permet d'optimiser l'accès aux objets.
Ainsi, les objets dont l'accès est critique seront rangés dans un entrepôt fixe. En revanche, les objets en grand nombre seront rangés dans un entrepôt de stockage mobile.
Entrepôts de mémoire fixe
Un entrepôt de stockage fixe garantit l'accès le plus rapide aux objets puisqu'un tel entrepôt est uniquement constitué de mémoire physique. Ainsi, tous les objets de ces entrepôts sont simultanément en mémoire.
En contrepartie, la taille de ces entrepôts est limitée à la taille physique de la mémoire de l'ordinateur, sachant que cette dernière est partagée par tous les processus en cours d'exécution.
Entrepôts de mémoire mobile
Un entrepôt de stockage mobile permet de manipuler un grand nombre d'objets, bien supérieur à celui qu'il pourrait être envisagé avec l'emploi d'un entrepôt de mémoire fixe. Un tel entrepôt est constitué de mémoire physique qui est adossée à un fichier d'échange, ce qui permet de disposer d'environ de 132 tera octets de données !
Ainsi, tous les objets de ces entrepôts ne sont pas simultanément en mémoire. Ils le sont tour à tour au gré des besoins. En contrepartie, l'accès à ces objets est plus lent, surtout si les fichiers d'échange sont beaucoup activés.
Up ! Object Management System gère automatiquement la pagination de la mémoire mobile dans un ou plusieurs fichiers d'échange. Ce module effectue des statistiques sur l'usage des objets en vue de minimiser les paginations.
Classification des programmes
Vis-à-vis de la gestion de la mémoire, nous pouvons classer les besoins des programmes en trois catégories. Il y a :
- Les programmes autonomes vis-à-vis des autres programmes.
Cette situation est la plus usuelle. Il est alors nécessaire d'employer un entrepôt de stockage privée.
- Les programmes devant partager des données et ils sont exécutés sur le même ordinateur.
Cette situation correspond à un applicatif utilisant une base d'objets accédée par plusieurs programmes. Il est alors nécessaire d'employer un entrepôt de stockage protégée.
- Les programmes devant partager des données et ils sont exécutés sur plusieurs ordinateurs homogènes ou non.
Cette situation correspond à un applicatif distribué comportant des bases à objets dont la localisation est à priori inconnu. Il est alors nécessaire d'employer un entrepôt de stockage publique.
Entrepôts de mémoire privée
Les entrepôts de mémoire privée est la situation est la plus simple. Les objets conservés dans cet entrepôt sont uniquement accessibles aux tâches du programme à qui appartient cet entrepôt.
Ce type d'entrepôt correspond à la mémoire dynamique dans le cas d'une programmation classique.
Entrepôts de mémoire protégée
Les objets d'un entrepôt de stockage protégée sont accessibles à plusieurs programmes simultanément. Les programmes doivent s'exécuter sur la même machine.
Le programme qui est propriétaire de l'entrepôt est particulier : il est le serveur. C'est lui qui contrôle l'accès aux objets et qui éventuellement gère le fichier d'échange si l'entrepôt est mobile.
Ce type d'entrepôt permet à un programme de mettre à jour une liste de données qui est ensuite exploitée par un autre programme. Elles sont utilisées pour réaliser des bases d'objets.
Entrepôts de mémoire publique
Les objets d'un entrepôt de stockage publique sont accessibles à plusieurs programmes simultanément. Les programmes peuvent s'exécuter sur une même machine ou peuvent s'exécuter sur un autre ordinateur. Les ordinateurs doivent être alors reliés par un moyen de communication géné par Up ! Network.
Le programme qui est propriétaire de l'entrepôt est particulier : il est le serveur. C'est lui qui contrôle l'accès aux objets et qui éventuellement gère le fichier d'échange si l'entrepôt est mobile.
Sur chaque machine qui utilise un entrepôt de stockage publique, un processus particulier est le clone du serveur de l'entrepôt. C'est au travers de lui que se font les échanges réseau au moyen de socquettes. Si les ordinateurs ont des architectures différentes, Up ! Object Management System en tient compte. Nous pouvons citer par exemple :
- Les tailles des données du fait des architectures 16 bis, 32 bits et 64 bits.
- La différence dans la codification des numériques (big endian et little endian).
- Les pages de code pour les chaînes de caractère.
Exemple d'emploi de entrepôts privés, protégés et publics
Il y a trois programmes P1, P2 et P3. P1 et P2 s'exécutent sur la machine O1. P3 s'exécute sur la machine O2.
P1 utilise trois entrepôts M1, M2 et M5. P2 utilise trois entrepôts M2, M3 et M5. P3 utilise deux entrepôts M4 et M5.
Les entrepôts M1, M2 et M4 sont des entrepôts privés.
L'entrepôt M3 est un entrepôt protégé. Il donc utilisable par les processus P1 et P2 de la machine O1.
L'entrepôt M5 est un entrepôt public. Il donc utilisable par les processus P1, P2 et P3 de la machine O1 et O2. La liaison entre les machines s'effectue par un réseau Tcp/Ip.
Performances à l'exécution
Influence de l'architecture du noyau d'exécution
Les performances à l'exécution tiennent pour une grande part à la configuration du noyau d'exécution. En effet, l'usage de mémoire mobile, de contrôle par un thread des fichiers d'échange, de mémoire publique n'offre pas les mêmes temps de réponse que de la mémoire fixe privée. Voici un tableau synthétique permettant de comparer les différents cas de figure :
Type de configuration | Niveau de performance |
Mémoire fixe privée. | ++++++++ |
Mémoire fixe privée en multi-tâches ou mémoire fixe protégée. | ++++ |
Mémoire mobile privée. | ++++++ |
Mémoire mobile privée mode multi-tâches ou mémoire mobile protégée. | ++++ |
Mémoire publique. | ++ |
Mémoire mobile publique. | + |
De ce fait, il est conseillé de ne pas employer le multi-tâches si l'applicatif n'en nécessite pas. Toutefois, dans certains cas de figure, le multi-tâches est particulièrement intéressant. Il permet par exemple de :
- Eviter de bloquer totalement l'applicatif lorsque celui-ci est en attente d'une réponse d'un service.
Le temps d'une requête Structured Query Language (SQL) en cours d'exécution sur le serveur de Système de Gestion de Bases de Données Relationnelles (SGBDR).
- Réaliser des tâches en fond d'exécution.
Le temps de l'impression ou de la pagination d'un document, de la scrutation d'une boîte à messages.
Lors de l'emploi de mémoire mobile ou de mémoire protégée ou publique, il est conseillé de réaliser tous les traitements dans des entrepôts de mémoire fixe privée, puis, au dernier moment, de transférer les résultats de ces traitements dans les entrepôts de mémoire mobile ou de mémoire protégée ou publique. Toutefois, le temps des transferts ne doit pas excéder le temps des traitements.
Influence de la configuration du noyau d'exécution
L'allocation unique des blocs de mémoire est préférable, surtout pour les applicatifs dont le temps de réponse est critique ou dont le temps d'exécution est court.
Pour les entrepôts de mémoire mobile, si le nombre de blocs de mémoire est faible, il est préférable que la valeur du paramètre nbblocscontigus ne soit pas trop élevée sinon les fichiers d'échange seront trop sollicités.
Influence de l'écriture du programme
Evitez de faire des calculs redondants. Il est bien souvent préférable de mémoriser le résultat d'un calcul dans une variable que de le refaire. Toutefois, Up ! Compiler factorise automatiquement le code redondant. Cette factorisation ne porte uniquement que sur des expressions dans un bloc de code local (une procédure, une boucle, etc).
Utiliser au maximum les API de Up ! Kernel. Par exemple :
- Utilisez les listes mis en oeuvre par le type Liste au lieu de gérer vos propres listes.
- Utiliser la fonction Compter du type Caractere pour compter les occurrences d'une sous-chaîne au lieu d'écrire vous même une fonction analogue.
Si vous manipulez des données en nombre important, il est préférable d'utiliser un entrepôt de stockage mobile au lieu de :
- Tout mettre en mémoire fixe ce qui aura pour effet de faire paginer le système d'exploitation. Cela pénalisera non seulement votre programme mais aussi les autres processus s'exécutant sur la machine.
- D'enregistrer ces données dans des fichiers temporaires.