IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Fonctionnement de la mémoire vive d’un ordinateur

La mémoire vive permet au processeur de stocker et d’accéder à des données pour les utiliser dans ses calculs. Cet article détaille le fonctionnement de la mémoire vive et explique comment le processeur y accède.

Commentez Donner une note à l´article (5)

Article lu   fois.

L'auteur

Profil ProSite personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Mémoire vive▲

La mémoire RAM (pour Random Access Memory, ou mémoire à accès aléatoire) est un composant de l’ordinateur qui stocke des données nécessaires au fonctionnement des logiciels. Le processeur peut accéder à n’importe quel bloc de données, quel que soit son emplacement physique en mémoire, d’où l’utilisation du terme « aléatoire » (« random »).

Plus précisément, la mémoire vive de l’ordinateur est du type DDR-SDRAM. Cette appellation reflète le fonctionnement de la mémoire :

  • DRAM (Dynamic Random Access Memory) : une mémoire RAM dont les données stockées doivent être régulièrement rafraîchies pour ne pas être perdues.
  • SDRAM (Synchronous Dynamic Random Access Memory) : une mémoire DRAM dont les réponses sont synchronisées avec l’horloge du système plutôt que de fonctionner indépendamment du processeur ;
  • DDR (Double Data Rate) : une évolution de la mémoire SDRAM dont les performances ont été améliorées grâce à l’utilisation des fronts montants et des fronts descendants d’un cycle d’horloge pour transférer les données. La mémoire SDRAM originelle n’utilisait qu’un seul front et peut donc être estampillée « Single Data Rate » (SDR).

La mémoire vive des PC est fournie dans un format physique communément appelé « barrette ». Un tel module mémoire abrite une série de puces DDR-SDRAM et peut facilement être inséré ou retiré de la carte mère de l’ordinateur. Ce format physique se nomme Dual Inline Memory Module (DIMM) ou Small Outline DIMM (SO-DIMM). Un module mémoire au format DIMM dispose notamment de contacts distincts de chaque côté du module (d’où l’appellation « dual »). Les modules DIMM et SO-DIMM ont un fonctionnement similaire, la différence principale étant leur taille physique. Un module SO-DIMM est moitié moins grand qu’un module DIMM. Les modules DIMM sont prévus pour les PC de bureau, les stations de travail ou les serveurs, tandis que les SO-DIMM sont destinés aux PC portables ou mini-PC.

Il existe d’autres catégories de puces ayant une utilisation similaire, comme les puces LPDDR (DDR « Low Power »). Ces puces disposent de fonctionnalités et d’une conception particulières adaptées aux équipements mobiles. Historiquement, la LPDDR est le plus souvent soudée directement à la carte mère afin de réduire l'encombrement et la consommation d’énergie. Toutefois, des formats modulaires récents comme LPCAMM2 permettent d'utiliser de la LPDDR5X sur un module remplaçable.

On pourrait aussi mentionner les puces utilisées pour la mémoire vive des cartes graphiques telles que la GDDR (Graphics DDR) ou encore, les puces HBM (High Bandwidth Memory). Ces dernières sont constituées de puces DRAM empilées.

Cet article se concentre sur les mémoires DDR-SDRAM.

I-A. Fonctionnement▲

Les modules mémoire doivent respecter les normes établies par le « Joint Electron Device Engineering Council » (JEDEC). Cet organisme regroupe de nombreuses entreprises dont Apple, Samsung, Kingston, Microsoft, Huawey, Realtek, Texas Instrument….
La norme détaille le fonctionnement de la mémoire vive : tensions d’alimentation d’un module, vitesses, commandes pour manipuler les données, dimensions, et ainsi de suite.

Une barrette DIMM contient entre 8 et 32 puces de mémoire DDR-SDRAM. Les puces sont manipulées et utilisées en parallèle, formant un ensemble nommé rang (en anglais, « rank »).

Une puce DDR-SDRAM contient plusieurs banques de données (banks). Une banque de données peut être illustrée par un tableau à deux dimensions dans lequel chaque cellule stocke un bit, grâce à un condensateur.

Un module mémoire DDR-SDRAM dispose d’un bus de données utiles de 64 bits. Chaque puce d’un même rang remplit une partie de ce bus.

Avec la DDR5, ces 64 bits sont divisés en deux sous-canaux indépendants de 32 bits, afin d'améliorer l'efficacité des accès.

La lecture d’une donnée depuis la mémoire vive suit le processus suivant :

  • le CPU envoie une requête pour obtenir une donnée située à une adresse physique spécifique (l’adresse étant une valeur comprise entre 0 et l’espace d’adressage physique de la mémoire) ;
  • le contrôleur mémoire décode cette adresse physique afin d’obtenir l’emplacement exact de la donnée sur le module mémoire, le rang, la banque, la ligne et la colonne ;
  • la ligne contenant la donnée souhaitée est activée (Row Address Strobe, RAS). Son contenu est amplifié grâce aux amplificateurs de détection (« sense amplifiers »). La ligne est alors dite ouverte. Il faut ensuite accéder à une colonne donnée (Column Adress Strobe, CAS) pour remplir les bits d’entrée/sortie de la puce. Cette opération est réalisée en parallèle sur les différentes puces intégrant le même rang permettant ainsi le remplissage des 64 bits de données à envoyer au processeur.

Selon l’organisation des puces mémoire, une ligne représente généralement plusieurs kilo-octets (souvent 8 ou 16 Ko).

Le processus d’écriture est similaire (ouverture de la ligne puis de la colonne). Simplement, au lieu de lire la donnée stockée, les données provenant du processeur sont appliquées aux amplificateurs de détection, qui modifient l’état des condensateurs des cellules mémoire correspondantes.

I-B. Timings▲

Les constructeurs de modules mémoire indiquent les performances de leur produit grâce à quatre temps (exprimés en cycles d’horloge du contrôleur mémoire) :

  • CL (CAS Latency) : le nombre exact de cycles d’attente entre la réception d’une commande de lecture par la puce et le début de l’envoi des données (lorsque la ligne est déjà ouverte) ;
  • TRCD (Row Address to Column Address Delay) : le nombre minimal de cycles d’horloge nécessaires pour ouvrir une ligne, puis accéder à une colonne de cette ligne. Par conséquent, si la donnée est stockée sur une ligne qui n’est pas ouverte, le nombre minimal de cycles d’attente sera TRCD + CL.
    Certains constructeurs indiquent deux valeurs pour ce temps : une pour la lecture et une pour l’écriture (TRCDwr et TRCDrd) ;
  • TRP (Row Precharge Time) : le nombre minimal de cycles d’horloge entre la demande de préchargement de ligne et l’ouverture de celle-ci. Autrement dit, cela équivaut au temps nécessaire pour fermer une ligne afin d’en charger une autre. Par conséquent, si la donnée est stockée sur une ligne autre que celle ouverte, il faut attendre, au minimum TRP + TRCD + CL cycles d’horloges ;
  • TRAS (Row Active Time) : nombre de cycles d’horloge minimal pendant lesquels une ligne doit rester active après son activation avant de pouvoir être préchargée (refermée). Ce temps est généralement égal ou supérieur à TRCD + CL..

Ces valeurs indiquent les délais à prévoir pour les différentes phases du processus d’accès aux données en mémoire vive.

Par contre, ces valeurs doivent être interprétées avec précaution, car elles indiquent un nombre de cycles d’horloge. Le délai réel, de l’ordre des nanosecondes, dépend de ces temps et de la fréquence de l’horloge.

Prenons l’exemple d’un module DDR ayant une horloge cadencée à 3 GHz (soit 6 000 MT/s) et mentionnant une latence CAS (CL) de 30 cycles d’horloge. Nous savons donc qu’il faut 10 ns pour obtenir les données (si la ligne est déjà chargée). Toutefois, le temps d’accès sera le même avec un module ayant une fréquence d’horloge plus élevée, par exemple, 3,8 GHz (soit 7 600 MT/s), et un CL de 38 cycles d’horloge.

I-B-1. Vitesse▲

La vitesse d’un module de mémoire se calcule en utilisant la fréquence de fonctionnement du module, multipliée par deux, car pour une mémoire DDR (Double Data Rate), les fronts montants et descendants de l’horloge sont utilisés pour le transfert de données. Cela permet d’obtenir le nombre de transferts par seconde (exprimé en millions de transferts par seconde, ou MT/s).
Ce nombre est ensuite multiplié par 64, ce qui correspondant à la taille du bus en bits. Enfin, il faut diviser le résultat par huit pour obtenir un débit en octets par seconde.

Par exemple, un module DDR5-4000 est cadencé à 2 000 MHz, soit deux milliards de cycles d’horloge par seconde. Le nombre de transferts par seconde est donc de quatre milliards par seconde (Double Data Rate), soit 4 000 millions de transferts par seconde (d’où le 4 000 MT/s). Le module peut donc transférer 4 000 MT/s * 64 bits = 256 000 millions de bits par seconde (Mbit/s), soit 256 Gbit/s, soit 32 Go/s.

Ce débit est toutefois théorique, car l’accès aux données (activation des lignes et colonnes de la mémoire, temps de latence, etc.) nécessite un certain temps et réduit les performances effectives.

I-C. Mémoire avec code correcteur d’erreur▲

Des erreurs peuvent survenir lorsque le contrôleur essaie de lire ou d’écrire des données sur les puces DRAM (changement de bit, appelé « bit flip »). Un module de mémoire de type ECC (« Error Correcting Code ») est doté d’un code correcteur d’erreur permettant de détecter, voire de corriger, de telles erreurs. Pour cela, un module contiendra une ou plusieurs puces DRAM supplémentaires et transférera 8 bits supplémentaires aux 64 bits de données utiles. Grâce à ces 8 bits supplémentaires, le contrôleur mémoire peut détecter l’erreur et en informer le reste du système. Les mémoires ECC sont principalement utilisées dans les stations de travail et les serveurs.

Les modules mémoire ECC nécessitent une carte mère et un processeur compatible.

Lorsqu’une erreur affecte une donnée stockée en mémoire vive sans code correcteur d’erreur, le changement dans les données est invisible pour le système. Par exemple, si cela se produit lors de la copie d’un fichier, ce dernier sera modifié, voire corrompu, sans que la copie échoue. Selon l’utilisation de la donnée stockée, les conséquences peuvent être nulles, entraîner la corruption d’un fichier ou d’une information utile aux calculs d’un programme, voire entraîner le plantage complet du système.

Finalement, la présence du code correcteur d’erreur entraîne une légère dégradation des performances, due à des timings plus élevés, comme mentionné dans la vidéo de Linus Tech Tips dédiée au sujet. Ce coût dépend de la configuration de la machine (processeur, génération de la mémoire, contrôleur, timings, etc).

La norme DDR5 impose l’implémentation d’un code correcteur d’erreur directement dans les puces mémoires DDR-SDRAM (et non pas en utilisant une puce supplémentaire), appelé On-Die ECC. Ce changement par rapport aux normes précédentes a pour objectif premier de permettre l’utilisation de puces ayant un haut taux d’erreur, conséquence d’une plus grande densité de stockage. L’inconvénient de cette implémentation est qu’aucune information n’est transmise au reste du système lorsqu’une correction est effectuée ou qu’une erreur est détectée. Le On-Die ECC protège uniquement les données stockées à l’intérieur des puces mémoire, mais ne protège pas d’une erreur lors du transfert des données.

II. D’autres formats de mémoire▲

Les mémoires DIMM (et SO-DIMM) ont une conception relativement « simple » : un ensemble de puces DRAM. Toutefois, il existe quelques variations, reposant sur l’ajout d’un registre : un circuit logique qui s’interface entre le contrôleur mémoire du système et les puces DRAM. L’objectif est de réduire la charge électrique sur le contrôleur mémoire afin d’améliorer la stabilité, notamment pour les systèmes devant gérer un grand nombre de modules. Par contre, cela augmente la latence.

  • UDIMM (Unbuffered DIMM) : couramment appelé DIMM, c’est le module mémoire classique ;
  • CUDIMM (Clocked Unbuffered DIMM) : ajoute un contrôleur d’horloge visant à reconditionner le signal provenant du contrôleur mémoire pour assurer une meilleure intégrité. Un tel contrôleur est recommandé à partir de 6 400 MT/s ;
  • HUDIMM (Half Unbuffered DIMM) : un concept non standardisé par le JEDEC dans lequel les rangs disposent uniquement de la moitié des puces nécessaires (afin de réduire les coûts). Par conséquent, la latence est doublée pour remplir les 64 bits de données du bus ;
  • RDIMM (Registered DIMM) : un registre met en tampon les signaux d’adresse et de commande afin de réduire la charge électrique sur le contrôleur mémoire ;
  • LRDIMM (Load Reduced DIMM) : le registre permet de stocker et de redistribuer les signaux de données, ainsi que les signaux d’adresse et de commande ;
  • MRDIMM (Multiplexed Rank DIMM) : un multiplexeur est ajouté pour permettre le travail de plusieurs rangs en parallèle. Cette technique, introduite avec la DDR5, permet aussi d’utiliser des fréquences supérieures (au-dessus de 8 800 MT/s) ;
  • FBDIMM (Fully Buffered DIMM) : un composant intermédiaire (Advanced Memory Buffer) remplace le bus parallèle par une liaison série ;

La carte mère et le processeur doivent être compatibles avec le type de module mémoire. Il est toutefois possible d’utiliser un module CUDIMM avec une plateforme ne prenant pas en charge cette technologie grâce à un mode « passthrough », dans lequel le pilote d’horloge est contourné.

Finalement, en 2022, Dell a mis au point un nouveau module mémoire qui a été standardisé par le JEDEC en 2024. Nommé CAMM2 (Compression Attached Memory Module), ce module dispose d’une connectique en grille offrant ainsi plus de connexions et propose un format plus plat, adapté aux ordinateurs portables. L’objectif est d’améliorer l’intégrité des signaux électriques entre le module mémoire et la carte mère, afin de permettre l’utilisation de fréquences d’horloge plus élevées.

III. Accès aux données▲

Jusqu’à présent, nous nous sommes focalisés sur la mémoire vive. Toutefois, ce n’est pas le seul élément à prendre en compte lorsque le processeur doit utiliser une valeur en mémoire. En effet, le processeur dispose de mémoires internes appelées cache L1, L2, L3, voire L4.

Les caches reposent sur une mémoire statique (SRAM) qui ne nécessite pas d’opérations de rafraîchissement. En revanche, la SRAM nécessite plus de composants électroniques pour stocker les bits que la DRAM.

Ces mémoires sont plus rapides, mais offrent une capacité de stockage plus faible. Elles sont directement intégrées au processeur. Le cache L1 dispose de très peu de mémoire (une centaine de kilo-octets) et est le plus rapide. Le cache L2 dispose de quelques méga-octets, mais est plus lent. Il en va de même que pour le cache L3, qui est plus grand, mais encore moins rapide. Par contre, ces caches sont tous plus rapides que la mémoire vive.

En réalité, le cache L1 est souvent divisé en deux parties : l’une est utilisée pour les données manipulées par le processeur et l’autre partie pour stocker les instructions traitées par le processeur.
Il est possible d’implémenter un processeur de façon à ce que chaque cœur dispose de son propre cache L1.

Il est ainsi possible de donner une image plus complète du processus d’accès à une donnée :

  • le programme veut effectuer une addition entre deux variables. Pour effectuer une opération telle que l’addition, le processeur doit placer les valeurs à manipuler au cours de l’opération dans une mémoire spéciale appelée « registre ». Un processeur ne dispose que d’une dizaine de registres (dont certains ont une utilisation spécifique).
  • le processeur regarde d’abord dans le cache L1 ; puis dans le L2 et enfin le L3 (cette vérification peut être parallélisée dans certains processeurs). Si la donnée y est trouvée, elle sera utilisée (cache-hit).

    Le cache est géré par lignes (ou blocs) de 64 octets typiquement. De plus, pour chaque ligne, le cache stocke également une étiquette permettant d’identifier l’adresse d’où provient la donnée.

  • La variable n’étant toujours pas trouvée (cache-miss), le processeur demande au contrôleur mémoire de récupérer la valeur depuis la mémoire vive. Le processus décrit ci-dessusFonctionnement (activation d’une ligne, puis d’une colonne, etc) se déroule alors jusqu’à ce que le processeur obtienne la variable, qui sera alors utilisée et suivant l’implémentation, stockée dans les caches.

III-A. Optimisations▲

En tant que développeur, il est intéressant de se demander s’il est possible d’optimiser ces accès à la mémoire vive.
Le graphe suivant montre les performances pouvant être obtenues lors du calcul de la somme des éléments d’un tableau, avec un accès linéaire (qui profite donc des caches) en comparaison avec un accès aléatoire :

Résultat d'un benchmark mesurant l'impact d'un accès linéaire contre un accès aléatoire sur le parcours d'un tableau

Il est important de veiller à ce que le programme puisse tirer le meilleur parti des caches afin que le processeur accède aux données nécessaires aussi rapidement que possible. En effet, si les données dont il a besoin se trouvent dans la mémoire vive, le processeur attendra et dépendra de la vitesse et des latences de cette dernière. Pour favoriser l’utilisation des caches, deux principes sont à considérer :

  • une proximité temporelle : les données sont mises en cache après avoir été récupérées. Il faut donc essayer de les utiliser immédiatement avant qu’elles ne soient déchargées du cache ;
  • une proximité spatiale : le processeur dispose de plusieurs mécanismes et optimisations pour récupérer les données. Il est possible que le processeur récupère non seulement la donnée utile à un moment T, mais aussi celles qui suivent (récupération en amont ou « prefetch »).

La proximité temporelle peut être améliorée par un réagencement des boucles et des variables utilisées, de façon à regrouper les différentes utilisations des mêmes données. Par exemple, au lieu de découper un programme travaillant sur plusieurs ensembles de valeurs en étapes logiques : initialisation, calcul, affichage des résultats, il peut être plus efficace de réorganiser le programme pour effectuer les trois étapes sur un ensemble de valeurs à la fois.

III-A-1. Organisation des données▲

La proximité spatiale des données permet d’offrir de nombreux scénarios d’optimisation pour le processeur. En effet, le processeur va lire la donnée nécessaire au calcul à effectuer, mais aussi d’autres données. Si les données se suivent en mémoire, alors les données supplémentaires ainsi lues, seront utiles pour les prochains calculs. Afin de tirer parti de ce mécanisme, il est préférable d’éviter l’utilisation de tableaux de pointeurs, notamment des listes chaînées qui obligeraient le processeur à lire une première donnée (le pointeur), puis la valeur nécessaire au calcul. Dans ce cas, le processeur ne pourra pas récupérer à l’avance la prochaine valeur, car celle-ci peut se trouver à un tout autre endroit en mémoire, ce qui est imprévisible et ne peut être déterminé qu’en lisant le prochain pointeur.

Un autre exemple est celui des matrices (ou tableaux à plusieurs dimensions). L’ordre dans lequel les composants sont stockés doit correspondre à l’ordre dans lequel les éléments sont utilisés. Par exemple, si une ligne de cache ne peut contenir que deux éléments d’une matrice 2x2. Le premier accès récupère les éléments 1, 2, mais dans le calcul, ce sont les éléments 1 et 3 qui sont utilisés. Le processeur devra alors récupérer l’élément 3 pour effectuer le calcul, alors qu’il avait déjà les éléments 1 et 2. En revanche, si les données sont réordonnées, elles se trouvent alors dans l’ordre 1, 3, 2, 4 et le premier calcul peut démarrer directement après une seule récupération de données.

L’optimisation de l’utilisation des lignes de cache est importante et le problème des matrices peut être généralisé dès qu’une structure est utilisée. La question est de savoir s’il est plus efficace d’avoir un tableau de structure (Array or Structure (AoS)) ou une structure contenant des tableaux (Structure of Array (SoA)).
Encore une fois, cela dépend de l’utilisation qui est faite des données. À titre d’exemple théorique, si le calcul ne nécessite qu’un seul des champs de la structure, alors récupérer l’ensemble des champs contenus dans la structure est une perte de temps. Il sera alors préférable d’utiliser une structure de tableaux pour laquelle le processeur ne s’occupera que du tableau contenant les données à traiter.

Voici un exemple des résultats pouvant être obtenus (sur un processeur Intel Core i5-8250U (1.60GHz)) avec le code suivant :

 
Sélectionnez
struct Values {
    double v1;
    double v2;
    double v3;
};
using AoSValues = std::vector<Values>;
double sum(const AoSValues& values)
{
    double sum = 0;
    for (auto const& v : values)
    {
        sum += v.v1 + v.v2 + v.v3;
    }
    return sum;
}

struct SoAValues {
    std::vector<double> v1;
    std::vector<double> v2;
    std::vector<double> v3;
};
double sum(const SoAValues& values)
{
    double sum = 0;
    for (size_t i = 0 ; i < values.v1.size() ; ++i)
    {
        sum += values.v1[i] + values.v2[i] + values.v3[i];
    }
    return sum;
}

Nombre d’éléments

Temps en millisecondes

 

Tableau de structure

Structure de tableaux

100

0.0002375

0.00019846

1000

0.00212108

0.00170223

10000

0.0208796

0.0177755

100000

0.191585

0.124118

1000000

2.18071

1.47868

10000000

22.8755

15.1504

100000000

229.635

152.116

D’une part, la différence entre les deux implémentations est remarquable, d’autre part, le résultat peut ne pas toujours être intuitif (un processeur étant un composant extrêmement ingénieux et complexe). La meilleure façon de déterminer comment obtenir les meilleures performances est de mettre en place un benchmark et d’utiliser un profileur.

III-A-2. Faux partage▲

Les processeurs multicœurs peuvent disposer d’un cache L1 dédié à chaque cœur. Une ligne de cache pouvant contenir plusieurs données (par exemple, deux entiers), il est possible que le premier entier soit nécessaire au calcul à effectuer par le premier cœur et le deuxième entier par le deuxième cœur. Les deux cœurs devront donc accéder à la même ligne de cache, qui sera alors dupliquée dans chacun des caches. Toutefois, si un cœur modifie l’une de ces données, l’autre cœur devra en être informé (au cas où il devrait lire la donnée en question). Par conséquent, la ligne de cache est invalidée pour le deuxième cœur et devra être rechargée avec la valeur nouvellement écrite par le premier cœur. Autrement dit, un cœur invalide le cache en mettant à jour une donnée, ce qui entraîne un ralentissement du deuxième cœur qui devra relire le cache pour être à jour et pouvoir travailler. Si ce deuxième cœur écrit aussi une nouvelle valeur, alors, à son tour, il ralentira le premier cœur. Ce problème s’appelle faux partage (ou « false sharing »).

Voici un exemple, en C++, permettant de montrer l’impact du « false sharing » :

 
Sélectionnez
struct FalseSharing {
    std::atomic<size_t> count1{0};
    std::atomic<size_t> count2{0};
};

struct AlignedSharing {
    alignas(hardware_destructive_interference_size) std::atomic<size_t> count1{0};
    alignas(hardware_destructive_interference_size) std::atomic<size_t> count2{0};
};

double run(size_t iterations) {
    AlignedSharing data;
    // FalseSharing data;
    auto work1 = [&data, iterations]() {
        for (size_t i = 0; i < iterations; ++i) {
            data.count1.fetch_add(1, std::memory_order_relaxed);
        }
    };
    auto work2 = [&data, iterations]() {
        for (size_t i = 0; i < iterations; ++i) {
            data.count2.fetch_add(1, std::memory_order_relaxed);
        }
    };

    std::thread t1(work1);
    std::thread t2(work2);
    t1.join();
    t2.join();

    return static_cast<double>(data.count1 + data.count2);
}

Et les temps obtenus, sur un processeur Intel Core i5-8250U, avec deux cœurs en « conflit » :

Nombre d’éléments

Temps en millisecondes

 

Sans alignement

Alignement pour éviter le false sharing

1000000

45.285

5.58668

10000000

433.977

54.6404

Pour éviter ce problème, il est possible de forcer le compilateur à utiliser l’intégralité d’une ligne de cache pour une donnée ou une structure, afin de garantir que cette ligne de cache ne soit pas partagée avec un autre cœur.

III-A-3. Outils▲

Comme énoncé précédemment, pour appréhender correctement le traitement d’un programme par le processeur, il est nécessaire de bien comprendre sont fonctionnement et comment il accède à la mémoire.
Pour faciliter le travail d’optimisation, il est possible d’utiliser des outils tels que perf, Intel Vtune et le profileur de Microsoft Visual Studio qui seront capables de rapporter le nombre d’accès aux différents niveaux de cache, mais aussi le nombre d’accès lorsque la donnée n’est pas en cache (cache-miss). Ces outils reposent sur les compteurs de performances (performance counters) qui permettent de compter le nombre de fois qu’un événement s’est produit. Ces événements peuvent provenir aussi bien du noyau, du processeur ou de toute autre source.

III-B. Accès à canal multiple▲

Il est possible d’accélérer la lecture et l’écriture des données mémoire en les répartissant sur plusieurs modules mémoire. Le contrôleur mémoire peut alors utiliser les modules en parallèle et ainsi transférer une plus grande quantité de données pour une même unité de temps. Cette technique s’appelle double canal (ou « dual channel »), et peut également être aussi triple, quadruple, etc. Elle est directement gérée au niveau de la carte mère et du contrôleur mémoire.
Pour obtenir un fonctionnement optimal, les modules doivent toutefois offrir les mêmes caractéristiques.

III-C. Rafraîchissement de la mémoire DRAM▲

Pour rappel, le D de l’acronyme DRAM signifie « Dynamic ». Au sein des puces, les bits sont stockés à l’aide de condensateurs. Ce composant électronique peut être comparé à une pile que l’on peut charger et décharger. Toutefois, le condensateur se décharge rapidement.
Dans un ordinateur, une charge élevée est interprétée comme un 1 binaire. Sinon, c’est un 0. Si un condensateur est chargé afin de stocker un 1 et qu’il se décharge spontanément, alors le 1 devient un 0 et la donnée est donc altérée.
Pour contrer cet effet de décharge, la puce DRAM relit et réécrit régulièrement les valeurs stockées afin de restaurer la charge des condensateurs. Ce processus s’appelle un rafraîchissement (« refresh »). Plus précisément, la puce effectue ce rafraîchissement ligne par ligne, en suivant un processus interne de balayage des lignes de mémoire. C’est automatique et géré par la puce elle-même.
Par contre, cela implique une contrainte : certaines opérations mémoire doivent attendre que le rafraîchissement soit effectué et cela ne peut être contrôlé ni directement monitoré par le processeur.

III-C-1. Contourner la latence du rafraîchissement▲

En théorie, le rafraîchissement des données d’une puce de mémoire DRAM est totalement invisible. En pratique, il est mesurable en effectuant des lectures répétées en mémoire vive et en comparant la vitesse de chaque lecture. À noter : pour que la mesure soit correcte, il faut éviter que les données ne soient en cache.
Malgré une possible constatation du problème, il est difficile à contourner :

  • le processeur et le système ne sont pas informés du rafraîchissement ;
  • le rafraîchissement est effectué de manière périodique et peut se produire pendant les accès mémoire sans être synchronisé avec le processeur ;
  • le noyau, le processeur et le contrôleur mémoire ajoutent plusieurs couches (virtualisation de la mémoire, caches, contrôleur mémoire, etc.) qui rendent le fonctionnement interne de la mémoire opaque pour le programme.

La vidéo suivante, de LaurieWired, rappelle le fonctionnement de la mémoire vive et explore une solution pour contourner la latence induite par le rafraîchissement. Pour ce faire, les données sont répliquées sur plusieurs barrettes de mémoire vive et les accès sont effectués en parallèle sur celles-ci. Comme il est peu probable que tous les répliquats soient bloqués par une opération de rafraîchissement, le temps d’accès à la donnée devrait être optimal.


Cliquez pour lire la vidéo


Son code est disponible sur GitHub.

IV. Conclusion▲

Cet article détaille le fonctionnement de la mémoire vive des ordinateurs et explique comment le processeur accède aux données stockées dans celle-ci. Le cache des processeurs étant conçu pour atténuer l’impact de la latence de la mémoire, il est nécessaire d’optimiser les programmes afin de bénéficier au maximum de celui-ci. Pour cela, deux aspects sont à prendre en compte : l’agencement du programme et l’agencement des données en mémoire. Avec la popularisation des processeurs multi-cœurs à mémoire cache dédiée, le programmeur doit également éviter le « faux partage » des données entre cœurs.

V. Remerciements▲

Logo de Kingston

Developpez.com tient à remercier Kingston Techology pour avoir répondu à nos multiples questions sur le fonctionnement de la mémoire vive.
Vous pouvez retrouver les produits Kingston sur le site officiel du fabricant.

Merci à Franck Talbart, f-leb et fred1599 pour leurs retours technique et orthographique.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2026 Équipe de la rubrique Hardware. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.