IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
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

Pascal Discussion :

Gestion de la mémoire


Sujet :

Pascal

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre confirmé

    Inscrit en
    Novembre 2008
    Messages
    69
    Détails du profil
    Informations forums :
    Inscription : Novembre 2008
    Messages : 69
    Billets dans le blog
    1
    Par défaut Gestion de la mémoire
    Bonjour,
    J'observe une curiosité que je n'arrive pas à expliquer quant à la gestion de la mémoire utilisée. Mon sujet est assez complexe aussi permettez-moi d'en donner le contexte :
    Je fais tourner sur mon ordinateur personnel un serveur pour jouer au bridge gratuitement. Il y a environ 900 joueurs qui viennent par jour et couramment plusieurs dizaines de joueurs jouent simultanément à partir de leur navigateur, soit sur ordinateur, soit sur tablette. En pointe le nombre de requêtes qui arrivent sur le serveur est environ de 20 requêtes par seconde.
    Or tous les navigateurs - sauf Firefox sur ordinateur - ne respectent pas le point 8.1 de la norme HTTP/1.1 qui stipule qu'ils doivent rester connectés pendant toute la session, mais ils se déconnectent après chaque réponse du serveur pour se reconnecter aussitôt pour la requête suivante qui arrive en général dans la foulée.
    Or à chaque nouvelle connexion, un thread est créé et ce thread doit gérer tout ce qui se passe dans la connexion. Ceci est fait au moyen d'un objet TContact qui est assez conséquent puisqu'il doit assurer beaucoup de fonctions entre l'identification du joueur, créer son objet ou le retrouver en mémoire, gérer les erreurs de connexion, interpréter la requête et invoquer la méthode appropriée du joueur et enfin répondre.
    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
    TContact = class(TObject)
    private
    protected
    public
      UnJoueur: TJoueurDonne;   //peut être complet ou simple
      FairePhoto: Boolean;      //Faire une copie de l'état de la reconnexion
    {$IFDEF WebJournal}
      WebWrite: Boolean;        //pour limiter l'éventuelle écriture dans le journal web
    {$ENDIF}
      Function DecodeAdresseEmail(address: string):String;  //
      procedure DemandePwd(Str1: String);
      Procedure DisplayLock(Str1: String); //affichage dans la fenêtre principale avec verrouillage de l'accès
      Procedure EnvoiFichierType(Fichier,Contenu: String;NoCache: boolean=false);
      procedure ForBrowserHTMLComment;
      procedure ForBrowserHTMLResult;
      procedure GetExecute;     //Traitement des requêtes GET générales
      procedure LireRequete;
      procedure LireSimpleRequete; //pour les joueurs en train de jouer
      function LongStringWrite(var F: File;S: String): boolean; //Procédure d'écriture dans le fichier
      procedure PostExecute;    //traitement des requêtes POST générales
      procedure OnTheCloudConnect(Jouer: Boolean=True; POC: Boolean=false);  //déclenché par la page d'accueil
      Procedure RépondErreur;              //Réponse en cas d'erreur prioritaire sur la réponse normale
      Procedure Répondre;                  //Envoie la Réponse à la requête
      Function SearchOrCreateJoueur: TJoueurDonne;
      Function SearchJoueur: TJoueurDonne;
      Procedure SelectMajorMethod;
      Procedure SelectOtherMethod;
      procedure TraiterDivers;
     
    end;
    Chaque nouvelle connexion implique le chargement en mémoire d'un nouvel objet Contact, ce qui consomme des ressources aussi bien de CPU de temps et de mémoire.
    Il existe une relation grossière entre le nombre de joueurs qui se sont connectés et la mémoire occupée par le serveur qui est environ d'1Mo par joueur. Comme il y a 900 joueurs qui viennent chaque jour et qu'ils sont conservés en mémoire, l'occupation mémoire peut monter jusqu'à 900 Mo, ce qui ne semble pas pour l'instant poser de problème. je constate par ailleurs des bizarreries dans l'occupation mémoire car ce n'est pas une fonction monotone du nombre de joueurs mais cela pourrait faire partie d'une autre discussion.
    maintenant que j'ai exposé le contexte, j'en arrive à ma question...
    Au lieu de charger les objets contacts à chaque nouvelle connexion, j'ai eu l'idée de faire une "écurie" de contacts. Quand une nouvelle connexion s'établit, il suffit de regarder dans l'écurie s'il y a un contact disponible et de l'enfourcher. Sinon on en crée un. Lors de la déconnexion, au lieu de libérer l'objet contact, on le remet à "l'écurie".
    Le code en compilation conditionnelle afin de pouvoir faire une rapide marche arrière :
    création de l'écurie :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    {$IFDEF ListeContactsDispo}
      ListeContactsDispo:= TList.Create;
      csListeContacts:= TCriticalSection.Create;
    {$ELSE}
    {$ENDIF}
    Gestion d'une nouvelle connexion :
    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
    {$IFDEF ListeContactsDispo}
      if ListeContactsDispo.Count>0 then
      Begin              //Il y a des Objets TContat disponib
        csListecontacts.Acquire;
        try
          UnContact:=Tcontact(ListeContactsDispo[0]);   //prendre le pus ancien
          ListeContactsDispo.Delete(0);      //le retirer de la liste
        finally
          csListecontacts.Release;
        end;
      End else
      Begin
        UnContact:= TContact.Create;
      End;
    {$ELSE}
      UnContact:= TContact.Create;
    {$ENDIF}
    Gestion des déconnexions :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    {$IFDEF ListeContactsDispo}
        UnContact.UnJoueur:= nil; //effacer l'affectation
        UnContact.Tampon.clear;
        csListecontacts.Acquire;
        try
          ListeContactsDispo.Add(UnContact);    //ceci ajoute le pointeur sur le contact dans la liste
        finally
          csListecontacts.Release;
        end;
    {$ELSE}
        UnContact.Free;
    {$ENDIF}
    Or au lieu de voir la mémoire occupée diminuer, elle explose d'un facteur 10 !
    maintenant elle est indépendante du nombre de joueurs et ne fait que croitre au fur et à mesure que les joueurs jouent ! Sur l'outil de développement, elle prend environ 5Mo par nouvelle donne jouée par un joueur jusque bien sûr cela plante vers environ 1600Mo occupés...
    j'ai vérifié que le nombre de contacts à l"écurie restait stable : ce n'est pas lui qui fait gonfler la mémoire...
    Quelqu'un a-t-il un début d'explication à ce phénomène ?

  2. #2
    Expert confirmé
    Avatar de Jipété
    Profil pro
    Inscrit en
    Juillet 2006
    Messages
    11 132
    Détails du profil
    Informations personnelles :
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations forums :
    Inscription : Juillet 2006
    Messages : 11 132
    Par défaut
    Bonjour,

    Citation Envoyé par GerardJ Voir le message
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    try
          UnContact:=Tcontact(ListeContactsDispo[0]);   //prendre le plus ancien
          ListeContactsDispo.Delete(0);      //le retirer de la liste
        finally
    Vit' fait : la souris sur Delete puis F1 montre
    The memory the pointer is pointing to is not deallocated.
    Solution : peut-être déclarer le pointeur nil et utiliser Pack :
    Remove Nil pointers from the list and free unused memory.
    Juste mes 2 cts, jamais utilisé Pack, et le TList ça fait des millénaires (oui, le temps passe vite avec Lazarus, très vite...) que je n'ai pas joué avec.

  3. #3
    Rédacteur/Modérateur
    Avatar de M.Dlb
    Inscrit en
    Avril 2002
    Messages
    2 466
    Détails du profil
    Informations personnelles :
    Âge : 40

    Informations forums :
    Inscription : Avril 2002
    Messages : 2 466
    Par défaut
    Si les objecs TContact doivent rester alloués en mémoire, j'imagine qu'il doit y avoir un flag dans l'objet TContact lui même pour savoir si il est libre ou pas. Dans de cas, lors de l'arrivée d'un nouveau contact, on parcourt la liste pour prendre le premier contact libre, au lieu de prendre le premier de la liste.

  4. #4
    Membre confirmé

    Inscrit en
    Novembre 2008
    Messages
    69
    Détails du profil
    Informations forums :
    Inscription : Novembre 2008
    Messages : 69
    Billets dans le blog
    1
    Par défaut
    Il me parait normal de ne pas désallouer le pointeur sur le contact qui existe toujours et que je souhaite réutiliser... Le fait de le retirer de la liste dit qu'il n'est plus disponible car il a été alloué à un joueur.
    C'est d'ailleurs le flag qu'on relâche lorsqu'on le met à l'écurie en mettant à nil le pointeur sur le joueur qui lui aussi existe quelque part en mémoire...

    Mais ne sont mis dans la liste que les pointeurs sur les contacts qui ont été libérés... On pourrait prendre n'importe lequel. J'ai choisi le premier car il y en a toujours un l’orque la liste n'est pas vide...
    Il se trouve que c'est le plus ancien. j'aurais pu prendre le dernier (il y en a toujours un aussi !) mais cela aurait été un peu plus compliqué à écrire...
    c'est visiblement les actions du joueur et je suppose que quelque chose doit s'accumuler dans l'objet contact au fur et à mesure du jeu, mais je ne vois vraiment pas quoi !

  5. #5
    Membre expérimenté

    Homme Profil pro
    Diverses
    Inscrit en
    Février 2014
    Messages
    122
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : France, Gironde (Aquitaine)

    Informations professionnelles :
    Activité : Diverses

    Informations forums :
    Inscription : Février 2014
    Messages : 122
    Par défaut
    Je n'ai pas vérifié mais je pense que le type TList n'est pas une liste chaînée mais un tableau. Le fait de toujours récupérer l'élément d'indice 0 est donc une mauvaise idée car pour le retirer de la liste il faut ensuite décaler tous les autres éléments d'un rang. En revanche, retirer toujours le dernier est presque gratuit. Pour éviter de multiples réallocations du tableau (de la liste) tu peux aussi essayer d'en augmenter la capacité aussitôt après sa création.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    ListeContactsDispo:= TList.Create;
    ListeContactsDispo.Capacity := 1000;  // assez de place pour 1000 joueurs
    Ces 2 conseils peuvent t'économiser un peu de temps et de mémoire mais ça ne doit pas résoudre ton bug. Si tu utilises FreePascal tu peux essayer de compiler ton programme avec l'unité heaptrc pour voir où se trouve ta fuite de mémoire s'il y en a vraiment une.

  6. #6
    Rédacteur/Modérateur
    Avatar de M.Dlb
    Inscrit en
    Avril 2002
    Messages
    2 466
    Détails du profil
    Informations personnelles :
    Âge : 40

    Informations forums :
    Inscription : Avril 2002
    Messages : 2 466
    Par défaut
    Comment sont gérés les autres contacts alloués et vraiment liés à un joueur ? Si c'est dans une autre TList, pourquoi ne pas tout gérer dans la même avec un flag au sein de l'objet contact ? Cela éviterait les mouvements de pointeurs. Simple suggestion.

    Sinon, je ne vois pas trop pourquoi il y aurait une fuite de mémoire, elle ne semble pas être dans ton code, si il n'y a pas d'allocation spécifique dans le Create ou autres méthodes de TContact. Comme l'a indiqué yamer, il me semble bien que l'objet liste est plus un tableau dynamique qu'une liste chaînée, donc il peut y avoir des effets de bord à l'ajout et à la suppression massive de pointeurs.

  7. #7
    Expert confirmé
    Avatar de Jipété
    Profil pro
    Inscrit en
    Juillet 2006
    Messages
    11 132
    Détails du profil
    Informations personnelles :
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations forums :
    Inscription : Juillet 2006
    Messages : 11 132
    Par défaut
    Citation Envoyé par yamer Voir le message
    Je n'ai pas vérifié mais je pense que le type TList n'est pas une liste chaînée mais un tableau. Le fait de toujours récupérer l'élément d'indice 0 est donc une mauvaise idée car pour le retirer de la liste il faut ensuite décaler tous les autres éléments d'un rang.

    Toujours issu de l'aide sur Delete (c'est moi qui mets en gras) :
    Delete removes the pointer at position Index from the list, shifting all following pointers one position up (or to the left).
    Ok, c'est les perfs qui en prennent un coup, mais c'est le système qui fait le boulot, pas à se prendre la tête avec ça.

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Réponses: 17
    Dernier message: 02/02/2006, 12h03
  2. gestion de la mémoire
    Par moldavi dans le forum C++
    Réponses: 17
    Dernier message: 04/02/2005, 23h18
  3. Réponses: 11
    Dernier message: 26/12/2004, 22h50
  4. Gestion de la mémoire entre plusieurs DLL
    Par Laurent Gomila dans le forum C++
    Réponses: 7
    Dernier message: 27/07/2004, 15h28
  5. Gestion des variables - mémoire ?
    Par RIVOLLET dans le forum Langage
    Réponses: 4
    Dernier message: 26/10/2002, 12h44

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