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.
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.
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;
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 :
Gestion d'une nouvelle connexion :
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 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
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}
Or au lieu de voir la mémoire occupée diminuer, elle explose d'un facteur 10 !
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}
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 ?
Partager