1. #1
    Membre du Club

    Inscrit en
    novembre 2008
    Messages
    28
    Détails du profil
    Informations forums :
    Inscription : novembre 2008
    Messages : 28
    Points : 52
    Points
    52
    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 éminent
    Avatar de Jipété
    Profil pro
    Inscrit en
    juillet 2006
    Messages
    5 762
    Détails du profil
    Informations personnelles :
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations forums :
    Inscription : juillet 2006
    Messages : 5 762
    Points : 7 919
    Points
    7 919

    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.
    Il a à vivre sa vie comme ça et il est mûr sur ce mur se creusant la tête : peutêtre qu'il peut être sûr, etc.
    Oui, je milite pour l'orthographe et le respect du trait d'union à l'impératif.
    Après avoir posté, relisez-vous ! Et en cas d'erreur ou d'oubli, il existe un bouton « Modifier », à utiliser sans modération
    On a des lois pour protéger les remboursements aux faiseurs d’argent. On n’en a pas pour empêcher un être humain de mourir de misère.
    Mes 2 cts,
    --
    jp

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

    Informations forums :
    Inscription : avril 2002
    Messages : 2 409
    Points : 4 083
    Points
    4 083

    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.
    M.Dlb - Modérateur z/OS - Rédacteur et Modérateur Pascal

  4. #4
    Membre du Club

    Inscrit en
    novembre 2008
    Messages
    28
    Détails du profil
    Informations forums :
    Inscription : novembre 2008
    Messages : 28
    Points : 52
    Points
    52
    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 averti

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

    Informations professionnelles :
    Activité : Diverses

    Informations forums :
    Inscription : février 2014
    Messages : 122
    Points : 419
    Points
    419

    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 409
    Détails du profil
    Informations personnelles :
    Âge : 32

    Informations forums :
    Inscription : avril 2002
    Messages : 2 409
    Points : 4 083
    Points
    4 083

    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.
    M.Dlb - Modérateur z/OS - Rédacteur et Modérateur Pascal

  7. #7
    Expert éminent
    Avatar de Jipété
    Profil pro
    Inscrit en
    juillet 2006
    Messages
    5 762
    Détails du profil
    Informations personnelles :
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations forums :
    Inscription : juillet 2006
    Messages : 5 762
    Points : 7 919
    Points
    7 919

    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.
    Il a à vivre sa vie comme ça et il est mûr sur ce mur se creusant la tête : peutêtre qu'il peut être sûr, etc.
    Oui, je milite pour l'orthographe et le respect du trait d'union à l'impératif.
    Après avoir posté, relisez-vous ! Et en cas d'erreur ou d'oubli, il existe un bouton « Modifier », à utiliser sans modération
    On a des lois pour protéger les remboursements aux faiseurs d’argent. On n’en a pas pour empêcher un être humain de mourir de misère.
    Mes 2 cts,
    --
    jp

  8. #8
    Rédacteur/Modérateur
    Avatar de Andnotor
    Profil pro
    Inscrit en
    septembre 2008
    Messages
    4 346
    Détails du profil
    Informations personnelles :
    Localisation : Suisse

    Informations forums :
    Inscription : septembre 2008
    Messages : 4 346
    Points : 8 776
    Points
    8 776

    Par défaut

    L'entrée dans la section critique est mal placée, elle doit être avant la condition.
    S'il y a un élément libre mais dix demandes en cours, les dix sont susceptibles de passer le teste Count > 0 (avant le verrouillage) mais neuf réaffectations échoueront.

    N'aurais-tu pas en plus du code montré un
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    try
      ...
    except
      UnContact := TContact.Create;
    end;
    ou quelque chose d'approchant ?

    Sinon, supprimer une entrée d'un tableau n'est qu'un Move de la mémoire allant de l'index à la fin du tableau à l'index-1. Ce n'est pas élément par élément, c'est ultra rapide

  9. #9
    Membre averti

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

    Informations professionnelles :
    Activité : Diverses

    Informations forums :
    Inscription : février 2014
    Messages : 122
    Points : 419
    Points
    419

    Par défaut

    Citation Envoyé par Jipété Voir le message

    Toujours issu de l'aide sur Delete (c'est moi qui mets en gras) :

    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.
    J'expliquais justement le fonctionnement interne. Je n'ai peut être pas été clair mais je n'ai jamais voulu dire qu'il fallait se taper les décalages à la main...

+ 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