Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Langage C++ Discussion :

Optimisation usage mémoire cache et fuite mémoire


Sujet :

Langage C++

  1. #1
    Membre du Club
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    septembre 2006
    Messages
    34
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : septembre 2006
    Messages : 34
    Points : 67
    Points
    67
    Par défaut Optimisation usage mémoire cache et fuite mémoire
    Bonjour,

    J'ai effectué une optimisation de la vitesse de calcul d'une opération en optimisant la mémoire cache et je me pose des questions sur la libération de la mémoire cache pour éviter des fuites mémoires.

    J'ai une liste d'objet de types différents qui héritent d'une classe commune. En temps normal, j'utiliserais une liste de pointeur vers cette classe commune et je ferrais un simple parcours de la liste. Cela signifie que l'on a une liste d'adresse mémoire, cette liste d'adresse est elle même allouée à un emplacement particulier.

    En terme de gestion de la mémoire, cela signifie que le programme va faire un accès à adresse mémoire de la liste, ce qui enclenche un chargement dans la mémoire cache d'une portion de la RAM qui contient cette liste d'adresse. Ensuite lorsque l'on accede à un élément de la liste, on va charger dans la mémoire cache la portion qui concerne cet objet. Enfin, une fois l'objet traité, on accède de nouveau à la liste en chargeant de nouveau la mémoire cache. Ainsi à chaque changement de portion de mémoire, on perd du temps. Sans compter que le traitement d'un objet peut dépendre d'un objet voisin ce qui demande d'autres mise à jour de la mémoire cache.

    La solution que j'ai implémenté consiste à utiliser une liste chaînée dont les objets sont placés de manière consécutive dans la mémoire, ainsi les chargements de la mémoire cache deviennent plus rare.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
     
    // exemple de trois objets de type différents qui héritent de ClassP
    // 1) taille de chaque type de classe
    uint sizeA = sizeof(ClassA);
    uint sizeB = sizeof(ClassB);
    uint sizeC = sizeof(ClassC);
    // 2) allocation de l'espace mémoire et calcul des adresses des futurs objets 
    void* address = malloc(sizeA+sizeB+sizeC);
    void* addressA = address;
    void* addressB = addressA+sizeA; // l'addition n'étant pas valable pour le void* en réalité je passe par un cast vers char*, je n'écris pas ces opérations par soucis de lisibilité
    void* addressC = addressB+sizeB;
    // 3) creation des objets aux addresses définies
    ClassA* a = new(addressA) ClassA(); // a == addressA == address
    ClassB* b = new(addressB) ClassB(); // b == addressB
    ClassC* c = new(addressC) ClassC(); // c == addressC
    // 4) paramétrage des objets
    a->next = b;
    b->next = c;
    c->next = nullptr;
    // 5) traitement
    ClassP *current = a;
    do
    {
      a->exec();
      current = current->next;
    }
    while(current != nullptr)
    // 6) liberation de l'espace mémoire
    delete a;
    delete b,
    delete c;
    free(address);
    tout d'abord, ai-je réinventé la roue et existe t'il un type standard de liste permettant d'effectuer la même opération?

    ensuite, ce qui me gène est la libération de l'espace mémoire:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    delete a; // a == address
    free(address);
    je fais appel à 'delete a' pour accéder au destructeur avant de désallouer, mais dans les faits je désalloue deux fois la même adresse mémoire (ce qui ne fonctionne pas).

    j'imagine que lors de l'allocation, il existe une liste dans le système de gestion du programme qui fait le lien entre les addresses et la taille des objets alloués. De cette manière si j'alloue l'emplacement pour un objet le programme est capable d’empêcher une allocation sur cet espace mémoire et de désallouer la bonne taille.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    char* c = (char*) malloc(15);
    free(c) // on n'indique pas la taille à la libération, cela signifie que le programme connait la taille de l'allocation d'une autre manière
     
    ClassP* p = new ClassA();
    delete(p) // on a perdu l'information de la classe d'origine: sizeof(ClassA) != sizeof(classP),
              // donc le programme connait la taille de l'allocation sans avoir besoin de connaitre le type
    Dans mon exemple plus haut, j'effectue une sorte de double allocation et je désalloue deux fois.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    void* address = malloc(size)
    ClassA* a = new(address) ClassA();
    delete a;
    free(address);
    - Est ce que `delete a` seul ne désalloue pas en même temps le pointeur address?
    - Est ce que `free(address)` est il suffisant pour désallouer en même temps `a`, `b` et `c`? quid du destructeur?

    merci d'avance

  2. #2
    Membre averti

    Profil pro
    Inscrit en
    décembre 2013
    Messages
    321
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : décembre 2013
    Messages : 321
    Points : 405
    Points
    405
    Par défaut
    > tout d'abord, ai-je réinventé la roue et existe t'il un type standard de liste permettant d'effectuer la même opération?

    Non, mais ce que tu veux faire ressemble au pattern Object Pool. Cf par exemple https://gameprogrammingpatterns.com/object-pool.html

    > je fais appel à 'delete a' pour accéder au destructeur avant de désallouer, mais dans les faits je désalloue deux fois la même adresse mémoire (ce qui ne fonctionne pas).
    > Est ce que `delete a` seul ne désalloue pas en même temps le pointeur address?
    > Est ce que `free(address)` est il suffisant pour désallouer en même temps `a`, `b` et `c`? quid du destructeur?

    Oui. C'est un des rares cas où appeler directement le destructeur (donc sans passer par delete) est valide. Cf les codes d'exemple de la doc : https://en.cppreference.com/w/cpp/la...#Placement_new

    Dans tous les cas, mesures bien les performances. Parce que c'est typiquement le genre d'optimisation qui peut avoir un gain nul (ou négligeable) si ce n'est pas nécessaire. D'ailleurs, as tu décidé de faire cela parce que tu as observer une perte de performance due au cache en faisant du profiling ? Ou c'est juste une optimisation random ?

  3. #3
    Membre émérite
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    juin 2011
    Messages
    615
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : juin 2011
    Messages : 615
    Points : 2 938
    Points
    2 938
    Par défaut
    Cela me fait penser à Boost.intrusive, le pool d'allocation en moins. La liste ne gère pas l'allocation qui se fait à l'extérieur, même si celle-ci peut contenir des nœuds de pointeur comme unique_ptr et contenir des types différents (manipulé sous la forme d'un pointeur commun).

    Pour le pool, mon avis que std::pmr::unsynchronized_pool_resource fait le taf. Voir carrément un std::list avec cet allocateur.

    Le faire à la main impose pas mal de piège et manipulation manuelle. Par exemple, ici tu ne prends pas en compte l'alignement des classes, cela peut fonctionner au prix d'un surcoût sur certaines architectures et un plantage sur d'autres.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    struct A{char c;};
    struct B{int i;};
    struct C{A a; B; };
     
    sizeof(A) == 1
    sizeof(B) == 4
    sizeof(C) == 8
     
    new(addr + sizeA) B => l'instance n'est pas aligné sur la frontière d'un int.
    Chose qui peut être corrigé avec std::align.

Discussions similaires

  1. optimiser java mémoire, charge
    Par cdm1024 dans le forum Général Java
    Réponses: 3
    Dernier message: 24/02/2009, 17h17
  2. Optimiser la mémoire pour réduire le temp d'import
    Par Bourak dans le forum Administration
    Réponses: 20
    Dernier message: 03/11/2008, 10h23
  3. taux d'usage mémoire et processeur
    Par dc.sara dans le forum C++
    Réponses: 2
    Dernier message: 14/02/2008, 15h20
  4. Optimiser la mémoire d'une app VB 2005
    Par Diabless6 dans le forum VB.NET
    Réponses: 34
    Dernier message: 21/08/2007, 19h18
  5. Optimisation de mémoire / rapiditée
    Par Zenol dans le forum C++
    Réponses: 9
    Dernier message: 25/09/2005, 11h18

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo