Accélérer la lecture et l'écriture de fichiers sur machine distante en utilisant un cache

La lecture ou l'écriture de fichier enregistrement par enregistrement ou ligne par ligne sur une machine distante peut, pour des fichiers possédant un très grand nombre d'éléments, prendre beaucoup de temps. Afin d'accélérer substantiellement ces opérations, il est possible de passer par un cache mémoire et lire/écrire le fichier en une seule opération.

Par contre, sur une seule et unique machine, l'intérêt de cette méthode est très limité, voire nul, car le système utilise déjà implicitement une mise en cache.

Commentez Donner une note à l'article (5)

Article lu   fois.

L'auteur

Profil ProSite personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

Introduction

Dans le processus de développement d'une application, un des postes importants est la détection des nœuds d'étranglement sur lesquels il faut faire un effort particulier d'optimisation. Les lectures et sauvegardes de données sur disque peuvent faire partie de ces points plus délicats, surtout s'ils ont lieu vers ou depuis une machine distante et si le nombre d'accès disque est très élevé.
Alors que la lecture et l'écriture de fichiers sur une machine locale sont généralement bien optimisées par les systèmes d'exploitation, il n'en va pas de même pour des accès via le réseau et des lectures ou écritures de fichiers contenant un très grand nombre d'éléments (d'enregistrements ou de lignes de texte) peuvent devenir pénalisants pour une application.

Cet article présente une solution parmi d'autres pour optimiser les lectures et écritures de fichiers : l'utilisation d'un cache mémoire, dans lequel vont se faire toutes les lectures et écritures de données. Pour la lecture, on allouera un cache ayant la même taille que le fichier et on y chargera tout le contenu du fichier en une seule fois. Pour l'écriture, on calculera la taille du fichier à créer, on allouera un cache de cette taille, on y copiera toutes les données et on écrira l'entièreté du fichier en une seule fois.

Ami lecteur, il est préférable pour comprendre l'article d'être rompu à l'utilisation des pointeurs. Si ce n'est pas le cas, prenez le temps de consulter les très bons tutorielsTutoriels d'introduction au langage Pascal à votre disposition sur ce site.

Le compilateur utilisé pour l'exemple développé ci-dessous est Virtual Pascal 2.1Téléchargez Virtual Pascal. Ce sont les fonctions de l'Image non disponibleAPI Windows qui sont utilisées. Il faudra donc déclarer l'unité Windows dans la clause Uses.

Lecture de fichier

L'objectif de la présente méthode est donc de charger le contenu d'un fichier dans un cache mémoire, en un seul accès disque. Cela fait, nous pourrons lire les données du fichier directement dans le cache puis détruire celui-ci.

Création du cache

Il est évident que nous devons créer un cache suffisamment grand que pour pouvoir recevoir l'entièreté du fichier. Pour cela, il faut connaître la taille de ce dernier.

La fonction GetFileSize de l'API Windows permet de le faire mais il faut lui communiquer le handle du fichier. Nous ouvrons donc le fichier au préalable avec la fonction CreateFile :

 
Sélectionnez

Var HandleFichier : tHandle;
   Taille : Cardinal;

(* Ouverture du fichier *)
HandleFichier := CreateFile(Chemin,GENERIC_READ,FILE_SHARE_READ,Nil,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,0);
if HandleFichier <> INVALID_HANDLE_VALUE
  then
    begin
      (* Détermination de la taille *)
      Taille := GetFileSize(HandleFichier,Nil);
    end;

Si la valeur renvoyée par GetFileSize est -1 ($FFFFFFFF), c'est qu'il y a une erreur.

Une fois que la taille du fichier est connue, nous pouvons allouer le cache avec GetMem :

 
Sélectionnez

Var Cache : pChar;
   Taille : Cardinal;

GetMem(Cache,Taille);

Vous remarquerez que la variable Cache est de type pChar (de l'unité Strings ). Ce n'est bien sûr pas une obligation et vous pouvez utiliser simplement le type Pointer . Le type pChar est toutefois intéressant car il permet des additions et soustractions d'adresses et d'entiers avec beaucoup de souplesse, sans transtypage. C'est juste une question de goût. :)

Chargement du fichier dans le cache

À présent que le cache est alloué dans le tas global, nous allons y charger tout le contenu du fichier, en une seule opération, à l'aide de la fonction ReadFile :

 
Sélectionnez

Var Cache : pChar;
   NbBytesLus : DWord;
   Taille : Cardinal;

if ReadFile(HandleFichier,Cache^,Taille,NbBytesLus,Nil) and (NbBytesLus = Taille)
  then
    (* ... *)

Pour s'assurer que tout s'est bien déroulé, il faut tester que ReadFile a bien retourné True et que le nombre d'octets lus correspond bien à la taille du fichier.

Lecture des données dans le cache

L'accès aux données dans le cache peut à présent se faire séquentiellement ou aléatoirement, exactement de la même manière que si les données étaient dans le fichier. Il faudra simplement connaître l'adresse de la donnée à accéder dans le cache.
La routine StrMove de l'unité Strings ne se limite pas aux chaînes de caractères ; elle permet de transférer n'importe quel contenu. Ainsi, pour lire un nombre entier dont on connaît l'adresse dans le cache :

 
Sélectionnez

Var Entier : LongInt;
   Adresse : pChar;

StrMove(pChar(@Entier),Adresse,SizeOf(Entier));

Ou une chaîne de caractères :

 
Sélectionnez

Var Chaine : String;
   Adresse : pChar;

StrMove(pChar(@Chaine),Adresse,SizeOf(Chaine));

Le calcul de Adresse est simplement l'adresse du cache augmentée du déplacement nécessaire pour atteindre la donnée désirée. Par exemple, pour atteindre le 10e enregistrement d'un fichier d'enregistrements de taille fixe :

 
Sélectionnez

Adresse := Cache + 9 * SizeOf(Enregistrement);

Simple, non ?

Dans le cas d'un fichier texte ou d'un fichier d'enregistrements de tailles variables, évidemment, il ne sera pas question d'accès aléatoire dans le cache, tout comme il ne serait pas question d'accès aléatoire dans le fichier lui-même. Le cache devra être parcouru séquentiellement.

Destruction du cache

La technique d'utilisation du cache n'étant intéressante que pour des fichiers de grande taille, cela signifie trivialement que le cache utilise beaucoup de mémoire. Il vaut donc mieux le détruire dès qu'il n'est plus utile. C'est FreeMem qui va s'en charger :

 
Sélectionnez

Var Cache : pChar;
   Taille : Cardinal;

FreeMem(Cache,Taille);

Écriture de fichier

Pour l'enregistrement sur disque, la technique consiste à stocker toutes les données dans un cache et à enregistrer le contenu du cache en un seul accès disque.

Création du cache

Il va falloir calculer la taille définitive du fichier pour allouer un cache suffisamment grand pour recevoir toutes les données à enregistrer. Le calcul dépend bien entendu du type de données : s'il s'agit d'enregistrements de taille fixe, il suffit de multiplier le nombre d'enregistrements par leur taille ; mais s'il s'agit de texte ou d'enregistrements de tailles variables, il va peut-être falloir parcourir toutes les données pour additionner leur taille respective.

Vous pourriez alors penser qu'il s'agit là d'une perte de temps : d'abord parcourir les données pour calculer la taille totale du fichier, puis les parcourir une seconde fois pour les écrire dans le cache, puis enfin écrire le contenu du cache sur disque. Mais ce n'est pas le cas : le gain en rapidité sera malgré tout substantiel par rapport à un parcours simple des données et à leur écriture directe sur disque.

C'est de nouveau GetMem qui va nous permettre d'allouer le cache dans le tas global :

 
Sélectionnez

Var Cache : pChar;
   Taille : Cardinal;

GetMem(Cache,Taille);

Écriture des données dans le cache

Nous utiliserons de nouveau la procédure StrMove (de l'unité Strings) pour transférer les données. Par exemple, pour un enregistrement :

 
Sélectionnez

Var Enreg = record
             Nom, Prenom : String [50];
             Age : DWord;
           end;
   Adresse : pChar;
               
StrMove(Adresse,pChar(@Enreg),SizeOf(Enreg));

Le calcul de Adresse est simple : chaque fois qu'une donnée est écrite dans le cache, il suffit de l'augmenter de la taille de la donnée qui vient d'être écrite pour obtenir l'adresse exacte où la donnée suivante pourra être écrite.

Sauvegarde du cache dans le fichier

Une fois que le cache contient toutes les données, nous enregistrons son contenu sur disque en une seule opération.

Nous faisons appel pour cela à la fonction CreateFile pour créer le fichier puis à WriteFile pour en écrire le contenu :

 
Sélectionnez

Var Cache : pChar;
   HandleFichier : tHandle;
   NbBytesEcrits : DWord;
   Taille : Cardinal;

HandleFichier := CreateFile(Chemin,GENERIC_WRITE,FILE_SHARE_WRITE,Nil,CREATE_ALWAYS,FILE_ATTRIBUTE_ARCHIVE,0);
if HandleFichier <> INVALID_HANDLE_VALUE
  then
    WriteFile(HandleFichier,Cache^,Taille,NbBytesEcrits,Nil);

Pour s'assurer que tout s'est bien déroulé, il faut tester que WriteFile retourne bien True.

Destruction du cache

C'est FreeMem qui va de nouveau se charger de détruire le cache :

 
Sélectionnez

Var Cache : pChar;
   Taille : Cardinal;

FreeMem(Cache,Taille);

Un exemple concret

Voici le codesource complet d'un programme qui lit un fichier de plusieurs centaines de milliers d'enregistrements en utilisant la technique du cache. Les enregistrements sont chargés dans une liste chaînée, modifiés puis réenregistrés sur disque.
Je ne me suis pas cassé la tête, il tourne dans une petite fenêtre WinCRT.

Tout à la fin de la page, vous trouverez un code source permettant de créer le fichier de test.

 
Sélectionnez

Program DEMO_CACHE;

(* ---------------------------------------------------------------------------------------------------------------------- *)
(*                                                                                                                        *)
(*  Exemple de lecture/écriture de fichier en utilisant un cache.                                                         *)
(*                                                                                                                        *)
(*  30-10-2011 par Alcatîz pour Developpez.com.                                                                           *)
(*  Compilateur : Virtual Pascal 2.1 build 279.                                                                           *)
(*                                                                                                                        *)
(*  Pour la lecture, on alloue un cache ayant la même taille que le fichier et on y charge tout le contenu du fichier en  *)
(*  une seule fois.                                                                                                       *)
(*  Pour l'écriture, on calcule la taille du fichier à créer, on alloue un cache de cette taille, on y copie toutes les   *)
(*  données et on écrit l'entièreté du fichier en une seule fois.                                                         *)
(*                                                                                                                        *)
(* ---------------------------------------------------------------------------------------------------------------------- *)


{$B-,D+,H-,I-,J+,P-,Q-,R-,S-,T-,V+,W-,X+,Z-}
{&AlignCode+,AlignData+,AlignRec+,Asm-,Cdecl-,Comments-,Delphi+,Frame+,G5+}
{&LocInfo+,Open32-,Optimise+,OrgName-,SmartLink+,Speed+,Use32+,ZD-}
{$M 32768}


Uses Strings,   (* Chaînes AZT *)
    WinCRT,    (* Emulation de console *)
    Windows;   (* API Windows *)


Type (* Liste chaînée bidirectionnelle contenant les données à modifier *)

    pDonnees = ^tDonnees;

    tDonnees = Record
                 Entier : LongInt;
                 Chaine : String;
                 pPrec, pSuiv : pDonnees;
               end;


Const (* Tête et queue de la liste chaînée *)

     pTete : pDonnees = Nil;
     pQueue : pDonnees = Nil;


Var hWindow : HWnd;                 (* Handle de la fenêtre WinCRT *)
   p : pDonnees;                   (* Elément de la liste chaînée *)
   NbEnreg : Cardinal;             (* Nombre d'enregistrements *)
   HrDebut, HrFin : tSystemTime;   (* Heure de début et de fin pour le chronomètre *)


(* ---------------------------------------------------------------------------------------------------------------------- *)
(*                               Désallocation de la liste chaînée utilisée par le programme                              *)
(* ---------------------------------------------------------------------------------------------------------------------- *)

Procedure DESALLOCATION_LISTE (
                              var pTete, pQueue : pDonnees   (* Tête et queue de la liste chaînée *)
                             );
(* Désallocation de la liste chaînée *)
Var p1, p2 : pDonnees;
Begin
 p1 := pTete;
 while p1 <> Nil do
   begin
     p2 := p1^.pSuiv;
     Dispose(p1);
     p1 := p2;
   end;
 pTete := Nil;
 pQueue := Nil;
End;


(* ---------------------------------------------------------------------------------------------------------------------- *)
(*                                                       Chronomètre                                                      *)
(* ---------------------------------------------------------------------------------------------------------------------- *)

Function TEMPS_ECOULE (
                      HrDebut, HrFin : tSystemTime   (* Mesure du début et de fin *)
                     ) : Cardinal;                   (* Différence en millisecondes *)
(* Retourne le temps écoulé entre deux mesures, en millisecondes.
  Par facilité, ne gère pas le cas  la mesure s'est effectuée sur plus d'une seule journée. *)
Begin
 TEMPS_ECOULE := Cardinal(HrFin.wHour) * 3600000 +
                 Cardinal(HrFin.wMinute) * 60000 +
                 Cardinal(HrFin.wSecond) * 1000 +
                 Cardinal(HrFin.wMilliseconds) -
                 Cardinal(HrDebut.wHour) * 3600000 -
                 Cardinal(HrDebut.wMinute) * 60000 -
                 Cardinal(HrDebut.wSecond) * 1000 -
                 Cardinal(HrDebut.wMilliseconds);
End;


(* ---------------------------------------------------------------------------------------------------------------------- *)
(*                                   Chargement et sauvegarde de fichier via un cache                                     *)
(* ---------------------------------------------------------------------------------------------------------------------- *)

Function CACHE_LECTURE (
                       hWindow : hWnd;          (* Handle de la fenêtre appelante *)
                       Chemin : pChar;          (* Chemin complet du fichier à charger *)
                       var Cache : pChar;       (* Adresse du cache *)
                       var Taille : Cardinal    (* Taille du cache *)
                      ) : LongBool;             (* True si pas d'erreur *)
(* Alloue un cache mémoire et y charge l'entièreté du fichier spécifié *)
Var HandleFichier : tHandle;          (* Handle du fichier *)
   NbBytesLus : DWord;               (* Nombre de bytes lus dans le fichier lors de la dernière opération *)
   OkOuverture : LongBool;           (* Résultat de l'ouverture du fichier *)
   OkTaille : LongBool;              (* Lecture de la taille du fichier *)
   OkLecture : LongBool;             (* Résultat de la lecture du fichier *)
   OkAllocation : LongBool;          (* Résultat de l'allocation de mémoire dynamique *)
   OkFermeture : LongBool;           (* Résultat de la fermeture du fichier *)
   Titre : Array [0..51] of Char;    (* Titre de la boîte à messages *)
   Texte : Array [0..299] of Char;   (* Texte de la boîte à messages *)
Begin
 Cache := Nil;
 OkTaille := True;
 OkOuverture := True;
 OkLecture := True;
 OkAllocation := True;
 OkFermeture := True;
 (* Ouverture du fichier *)
 HandleFichier := CreateFile(Chemin,GENERIC_READ,FILE_SHARE_READ,Nil,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,0);
 OkOuverture := (HandleFichier <> INVALID_HANDLE_VALUE);
 if OkOuverture
    then
      begin
        (* Lecture de la taille du fichier *)
        Taille := GetFileSize(HandleFichier,Nil);
        OkTaille := (Taille <> $FFFFFFFF);
        if OkTaille
           then   (* Allocation du cache mémoire *)
             begin
               GetMem(Cache,Taille);
               OkAllocation := (Cache <> Nil);
               if OkAllocation
                  then   (* Copie de l'entièreté du fichier dans le cache *)
                    begin
                      if ReadFile(HandleFichier,Cache^,Taille,NbBytesLus,Nil)
                         then
                           begin
                             if NbBytesLus < Taille
                                then
                                  OkLecture := False;
                           end
                         else
                           OkLecture := False;
                    end;
             end;
        (* Fermeture du fichier *)
        OkFermeture := CloseHandle(HandleFichier);
      end;
 (* Message d'erreur éventuel *)
 if not OkTaille
    then
      StrCopy(Texte,'Impossible de d'#233'terminer la taille du fichier')
    else
      if not OkOuverture
         then
           StrCopy(Texte,'Impossible d''ouvrir le fichier')
         else
           if not OkLecture
              then
                StrCopy(Texte,'Erreur de lecture du fichier')
              else
                if not OkFermeture
                   then
                     StrCopy(Texte,'Erreur lors de la fermeture du fichier')
                   else
                     if not OkAllocation
                        then
                          StrCopy(Texte,'M'#233'moire insuffisante pour cr'#233'er le cache de lecture du fichier');
 if (not OkTaille) or (not OkOuverture) or (not OkLecture) or (not OkFermeture) or (not OkAllocation)
    then
      begin
        StrCat(Texte,#10#13);
        StrCat(Texte,Chemin);
        StrCopy(Titre,'Chargement de fichier');
        MessageBox(hWindow,Texte,Titre,mb_IconHand or mb_Ok);
      end;
 (* Résultat de la fonction *)
 CACHE_LECTURE := OkTaille and OkOuverture and OkLecture and OkFermeture and OkAllocation;
End;


Function CACHE_ECRITURE (
                        hWindow : hWnd;      (* Handle de la fenêtre appelante *)
                        Chemin : pChar;      (* Chemin complet du fichier à écrire *)
                        var Cache : pChar;   (* Adresse du cache à sauvegarder *)
                        Taille : Cardinal    (* Taille du cache *)
                       ) : LongBool;         (* True si pas d'erreur *)
(* Ecrit le contenu du cache mémoire dans un fichier *)
Var HandleFichier : tHandle;          (* Handle du fichier *)
   NbBytesEcrits : DWord;            (* Nombre de bytes écrits dans le fichier lors de la dernière opération *)
   OkCreation : LongBool;            (* Résultat de la création du fichier *)
   OkEcriture : LongBool;            (* Résultat de l'écriture du fichier *)
   OkFermeture : LongBool;           (* Résultat de la fermeture du fichier *)
   Titre : Array [0..51] of Char;    (* Titre de la boîte à messages *)
   Texte : Array [0..299] of Char;   (* Texte de la boîte à messages *)
Begin
 OkCreation := True;
 OkEcriture := True;
 OkFermeture := True;
 HandleFichier := CreateFile(Chemin,GENERIC_WRITE,FILE_SHARE_WRITE,Nil,CREATE_ALWAYS,FILE_ATTRIBUTE_ARCHIVE,0);
 OkCreation := (HandleFichier <> INVALID_HANDLE_VALUE);
 if OkCreation
    then
      begin
        OkEcriture := WriteFile(HandleFichier,Cache^,Taille,NbBytesEcrits,Nil);
        OkFermeture := CloseHandle(HandleFichier);
      end;
 (* Message d'erreur éventuel *)
 if not OkCreation
    then
      StrCopy(Texte,'Erreur lors de la cr'#233'ation du fichier')
    else
      if not OkEcriture
         then
           StrCopy(Texte,'Erreur d'''#233'criture dans le fichier')
         else
           if not OkFermeture
              then
                begin
                  StrCopy(Texte,'Erreur lors de la fermeture du fichier');
                  StrCat(Texte,#10#13);
                  StrCat(Texte,Chemin);
                end;
 if (not OkCreation) or (not OkEcriture) or (not OkFermeture)
    then
      begin
        StrCat(Texte,#10#13);
        StrCat(Texte,Chemin);
        StrCopy(Titre,'Sauvegarde de fichier');
        MessageBox(hWindow,Texte,Titre,mb_IconHand or mb_Ok);
      end;
 (* Résultat de la fonction *)
 CACHE_ECRITURE := OkCreation and OkEcriture and OkFermeture;
End;


Function CHARGEMENT (
                    hWindow : hWnd;                 (* Handle de la fenêtre appelante *)
                    FichierSource : pChar;          (* Chemin complet du fichier à charger *)
                    var pTete, pQueue : pDonnees;   (* Tête et queue de la liste chaînée *)
                    var NbEnreg : Cardinal          (* Compteur d'enregistrements *)
                   ) : LongBool;                    (* True si pas d'erreur *)
(* Chargement de fichier dans une liste chaînée bidirectionnelle *)
Var AncienCurseur : hCursor;          (* Ancien curseur *)
   Cache : pChar;                    (* Cache dans lequel le fichier est entièrement chargé *)
   Taille : Cardinal;                (* Taille du fichier *)
   Index : Cardinal;                 (* Index dans le parcours séquentiel du cache *)
   Adresse : pChar;                  (* Adresse dans le cache depuis laquelle il faut transférer les données *)
   OkCache : LongBool;               (* Indique si la mise en cache s'est bien déroulée *)
   OkAllocation : LongBool;          (* Résultat de l'allocation de mémoire dynamique *)
   p : pDonnees;                     (* Elément à ajouter dans la liste chaînée *)
   Titre : Array [0..51] of Char;    (* Titre de la boîte à messages *)
   Texte : Array [0..299] of Char;   (* Texte de la boîte à messages *)
Begin
 AncienCurseur := SetCursor(LoadCursor(0,IDC_WAIT));
 (* Mise en cache du fichier *)
 OkCache := CACHE_LECTURE(hWindow,FichierSource,pChar(Cache),Taille);
 if OkCache
    then
      begin
        NbEnreg := 0;
        Adresse := Cache;
        Index := 0;
        while (Index < Taille) and OkAllocation do
          begin
            (* Allocation de l'élément suivant à insérer dans la boîte liste *)
            New(p);
            if p <> Nil
               then
                 begin
                   Inc(NbEnreg);
                   FillChar(p^,SizeOf(p^),0);
                   (* Entier *)
                   StrMove(pChar(@p^.Entier),Adresse,SizeOf(p^.Entier));
                   Inc(Index,SizeOf(p^.Entier));
                   Adresse := Cache + Index;
                   (* Chaîne *)
                   StrMove(pChar(@p^.Chaine),Adresse,SizeOf(p^.Chaine));
                   Inc(Index,SizeOf(p^.Chaine));
                   Adresse := Cache + Index;
                   (* Ajout dans la liste chaînée *)
                   if pQueue = Nil
                      then
                        pTete := p
                      else
                        begin
                          pQueue^.pSuiv := p;
                          p^.pPrec := pQueue;
                        end;
                   pQueue := p;
                 end
               else   (* Mémoire insuffisante *)
                 OkAllocation := False;
          end;
        (* Désallocation du cache *)
        FreeMem(Cache,Taille);
     end;
 SetCursor(AncienCurseur);
 (* Message d'erreur éventuel (les erreurs liées au fichier sont affichées par Cache_Lecture) *)
 if not OkAllocation
    then
      begin
        StrCopy(Texte,'M'#233'moire insuffisante pour charger le fichier');
        StrCat(Texte,#10#13);
        StrCat(Texte,FichierSource);
        StrCopy(Titre,'Chargement de fichier');
        MessageBox(hWindow,Texte,Titre,mb_IconHand or mb_Ok);
      end;
 (* Résultat de la fonction *)
 CHARGEMENT := OkCache and OkAllocation;
End;


Function TAILLE_CACHE_SAUVEGARDE (
                                 NbEnreg : Cardinal   (* Nombre d'enregistrements *)
                                ) : Cardinal;         (* Taille en octets *)
(* Calcule la taille du cache d'écriture du fichier *)
Begin
 TAILLE_CACHE_SAUVEGARDE := NbEnreg * (SizeOf(Cardinal) + SizeOf(String));
End;


Function SAUVEGARDE (
                    hWindow : hWnd;             (* Handle de la fenêtre appelante *)
                    FichierDest : pChar;        (* Chemin complet du fichier à sauvegarder *)
                    pTete, pQueue : pDonnees;   (* Tête et queue de la liste chaînée *)
                    NbEnreg : Cardinal          (* Nombre d'enregistrements *)
                   ) : LongBool;                (* True si pas d'erreur *)
(* Sauvegarde du contenu de la liste chaînée dans un fichier *)
Var AncienCurseur : hCursor;          (* Ancien curseur *)
   OkAllocation : LongBool;          (* Résultat de l'allocation de mémoire dynamique *)
   OkEcriture : LongBool;            (* Résultat de l'écriture dans le fichier *)
   Cache : pChar;                    (* Cache dans lequel l'entièreté du fichier est chargée *)
   TailleCache : Cardinal;           (* Taille du cache *)
   Adresse : pChar;                  (* Adresse courante dans le cache *)
   OkCache : LongBool;               (* Indique si la mise en cache s'est bien déroulée *)
   Index : Cardinal;                 (* Index courant dans le cache *)
   n : Cardinal;                     (* Compteur d'enregistrements *)
   p : pDonnees;                     (* Elément à sauvegarder *)
   Titre : Array [0..51] of Char;    (* Titre de la boîte à messages *)
   Texte : Array [0..299] of Char;   (* Texte de la boîte à messages - Buffer d'écriture dans le fichier *)
Begin
 AncienCurseur := SetCursor(LoadCursor(0,IDC_WAIT));
 (* Création du cache *)
 OkAllocation := True;
 TailleCache := TAILLE_CACHE_SAUVEGARDE(NbEnreg);
 GetMem(Cache,TailleCache);
 OkCache := (Cache <> Nil);
 if OkCache
    then   (* Mise en cache *)
      begin
        Adresse := Cache;
        Index := 0;
        p := pTete;
        while p <> Nil do
          begin
            (* Entier *)
            Adresse := Cache + Index;
            StrMove(Adresse,pChar(@p^.Entier),SizeOf(p^.Entier));
            Inc(Index,SizeOf(p^.Entier));
            (* Chaîne *)
            Adresse := Cache + Index;
            StrMove(Adresse,pChar(@p^.Chaine),SizeOf(p^.Chaine));
            Inc(Index,SizeOf(p^.Chaine));
            (* Passage à l'élément suivant *)
            p := p^.pSuiv;
          end;
        (* Ecriture du fichier *)
        OkEcriture := CACHE_ECRITURE(hWindow,FichierDest,Cache,TailleCache);
        (* Désallocation du cache *)
        FreeMem(Cache,TailleCache);
      end
    else
      OkAllocation := False;
 SetCursor(AncienCurseur);
 (* Message d'erreur éventuel (les erreurs liées au fichier sont affichées par CACHE_ECRITURE) *)
 if not OkAllocation
    then
      begin
        StrCopy(Texte,'M'#233'moire insuffisante pour cr'#233'er le cache d'''#233'criture du fichier');
        Strcat(Texte,#10#13);
        StrCat(Texte,FichierDest);
        StrCopy(Titre,'Sauvegarde de fichier');
        MessageBox(hWindow,Texte,Titre,mb_IconHand or mb_Ok);
      end;
 (* Résultat de la fonction *)
 SAUVEGARDE := OkCache and OkAllocation and OkEcriture;
End;


(* ---------------------------------------------------------------------------------------------------------------------- *)
(*                                                   Programme principal                                                  *)
(* ---------------------------------------------------------------------------------------------------------------------- *)

Begin
 InitWinCRT;
 WriteLn('D'#233'monstration de lecture/'#233'criture de fichier via un cache.');
 WriteLn('30-10-2011 par Alcat'#238'z pour Developpez.com');
 Writeln;
 Write('Pressez Enter pour d'#233'marrer : ');
 ReadLn;
 WriteLn;
 (* Récupération du handle de la fenêtre WinCRT *)
 hWindow := GetFocus;
 (* Chargement du contenu du fichier dans la liste chaînée *)
 WriteLn('Chargement du fichier... ');
 GetLocalTime(HrDebut);   (* Démarrage du chrono *)
 if CHARGEMENT(hWindow,'\\Machine_Distante\Repertoire\Demo.dat',pTete,pQueue,NbEnreg)
    then
      begin
        GetLocalTime(HrFin);   (* Arrêt du chrono *)
        WriteLn('   OK - Dur'#233'e : ',TEMPS_ECOULE(HrDebut,HrFin),' millisecondes.');
        (* Modification des données *)
        p := pTete;
        while p <> Nil do
          begin
            p^.Chaine := p^.Chaine + ' a '#233't'#233' modifi'#233' !';
            p := p^.pSuiv;
          end;
        (* Sauvegarde des données modifiées *)
        WriteLn('Sauvegarde du fichier... ');
        GetLocalTime(HrDebut);   (* Démarrage du chrono *)
        if SAUVEGARDE(hWindow,'\\Machine_Distante\Repertoire\DemoModif.dat',pTete,pQueue,NbEnreg)
           then
             begin
               GetLocalTime(HrFin);   (* Arrêt du chrono *)
               WriteLn('   OK - Dur'#233'e : ',TEMPS_ECOULE(HrDebut,HrFin),' millisecondes.');
             end
           else
             WriteLn('   Erreur !');
        (* Désallocation de la liste chaînée *)
        DESALLOCATION_LISTE(pTete,pQueue);
      end
    else
      WriteLn('   Erreur !');
 Write('Pressez Enter pour quitter : ');
 ReadLn;
 DoneWinCRT;
End.

Tests

À titre de comparaison, vous pouvez remplacer la procédure CHARGEMENT par celle-ci, n'utilisant pas de cache :

 
Sélectionnez


(* ---------------------------------------------------------------------------------------------------------------------- *)
(*                                     Chargement et sauvegarde de fichier sans cache                                     *)
(* ---------------------------------------------------------------------------------------------------------------------- *)

Function CHARGEMENT_CLASSIQUE (
                              hWindow : hWnd;                 (* Handle de la fenêtre appelante *)
                              FichierSource : pChar;          (* Chemin complet du fichier à charger *)
                              var pTete, pQueue : pDonnees;   (* Tête et queue de la liste chaînée *)
                              var NbEnreg : Cardinal          (* Compteur d'enregistrements *)
                             ) : LongBool;                    (* True si pas d'erreur *)
(* Chargement de fichier dans une liste chaînée bidirectionnelle *)
Var AncienCurseur : hCursor;          (* Ancien curseur *)
   HandleFichier : tHandle;          (* Handle du fichier *)
   NbBytesLus : DWord;               (* Nombre de bytes lus dans le fichier lors de la dernière opération *)
   OkOuverture : LongBool;           (* Résultat de l'ouverture du fichier *)
   OkLecture : LongBool;             (* Résultat de la lecture du fichier *)
   OkAllocation : LongBool;          (* Résultat de l'allocation de mémoire dynamique *)
   FinFichier : LongBool;            (* Indique que la fin du fichier est atteinte *)
   OkFermeture : LongBool;           (* Résultat de la fermeture du fichier *)
   p : pDonnees;                     (* Elément à ajouter dans la liste chaînée *)
   Titre : Array [0..51] of Char;    (* Titre de la boîte à messages *)
   Texte : Array [0..299] of Char;   (* Texte de la boîte à messages - Buffer de lecture du cache *)
Begin
 AncienCurseur := SetCursor(LoadCursor(0,IDC_WAIT));
 HandleFichier := CreateFile(FichierSource,GENERIC_READ,FILE_SHARE_READ,Nil,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,0);
 OkOuverture := (HandleFichier <> INVALID_HANDLE_VALUE);
 if OkOuverture
    then
      begin
        OkLecture := True;
        FinFichier := False;
        OkAllocation := True;
        repeat
          (* Allocation de l'élément suivant à insérer dans la boîte liste *)
          New(p);
          if p <> Nil
             then
               begin
                 Inc(NbEnreg);
                 FillChar(p^,SizeOf(p^),0);
                 (* Entier *)
                 if ReadFile(HandleFichier,p^.Entier,SizeOf(p^.Entier),NbBytesLus,Nil)
                    then
                      begin
                        if NbBytesLus < SizeOf(p^.Entier)
                           then
                             FinFichier := True;
                      end
                    else
                      OkLecture := False;
                 (* Chaîne *)
                 if OkLecture and not FinFichier
                    then
                      if ReadFile(HandleFichier,p^.Chaine,SizeOf(p^.Chaine),NbBytesLus,Nil)
                         then
                           begin
                             if NbBytesLus < SizeOf(p^.Chaine)
                                then
                                  FinFichier := True;
                           end
                         else
                           OkLecture := False;
                 (* Ajout dans la liste chaînée *)
                 if OkLecture and not FinFichier
                    then
                      begin
                        if pQueue = Nil
                           then
                             pTete := p
                           else
                             begin
                               pQueue^.pSuiv := p;
                               p^.pPrec := pQueue;
                             end;
                        pQueue := p;
                      end
                    else   (* Désallocation de l'élément *)
                      Dispose(p);
                 end
               else   (* Mémoire insuffisante *)
                 OkAllocation := False;
        until FinFichier or (not OkAllocation) or (not OkLecture);
      end;
 OkFermeture := CloseHandle(HandleFichier);
 SetCursor(AncienCurseur);
 (* Message d'erreur éventuel *)
 if not OkOuverture
    then
      StrCopy(Texte,'Impossible d''ouvrir le fichier')
    else
      if not OkLecture
         then
           StrCopy(Texte,'Erreur de lecture du fichier')
         else
           if not OkFermeture
              then
                StrCopy(Texte,'Erreur lors de la fermeture du fichier')
              else
                if not OkAllocation
                   then
                     StrCopy(Texte,'M'#233'moire insuffisante pour charger le fichier');
 if (not OkOuverture) or (not OkLecture) or (not OkFermeture) or (not OkAllocation)
    then
      begin
        StrCat(Texte,#10#13);
        StrCat(Texte,FichierSource);
        StrCopy(Titre,'Chargement de fichier');
        MessageBox(hWindow,Texte,Titre,mb_IconHand or mb_Ok);
      end;
 (* Résultat de la fonction *)
 CHARGEMENT_CLASSIQUE := OkOuverture and OkLecture and OkFermeture and OkAllocation;
End;

Remplacez alors l'appel de CHARGEMENT dans le programme principal par celui-ci :

 
Sélectionnez

if CHARGEMENT_CLASSIQUE(hWindow,'\\Machine_Distante\Repertoire\Demo.dat',pTete,pQueue,NbEnreg)

Pour un fichier de 400 000 enregistrements comprenant un entier long (4 octets) et une chaîne de caractères (256 octets), le temps de chargement sur mon réseau local passe de 28,344 à 19,178 secondes grâce à l'utilisation du cache, soit un gain de plus de 30 %. Le gain enregistré sur le réseau de mon entreprise est même de l'ordre de 70 % aux heures de pointe !

Par contre, lorsque le fichier est stocké sur ma machine de test même, les performances sont pratiquement identiques, avec ou sans cache.

Pour garantir l'objectivité de vos tests (en évitant toute optimisation par le système), supprimez et recréez chaque fois votre fichier Demo.dat ou bien créez à chaque fois un fichier avec un nouveau nom.

Comme promis, voici le code source du petit programme de création du fichier Demo.dat :

 
Sélectionnez


Program CREATE_DEMO_FILE;

Type tDonnees = Record
                 Entier : LongInt;
                 Chaine : String;
               end;

Var Fichier : File of tDonnees;
   n : Cardinal;
   s : String;
   Buffer : tDonnees;

Begin
 InitWinCRT;
 Assign(Fichier,'\\Machine_distante\Repertoire\Demo.dat');
 Rewrite(Fichier);
 for n := 1 to 400000 do
   begin
     Buffer.Entier := n;
     Str(n,s);
     Buffer.Chaine := 'Enregistrement ' + s;
     Write(Fichier,Buffer);
   end;
 Close(Fichier);
End.

Conclusion

La technique exposée dans cet article n'est peut-être pas la panacée en matière d'optimisation mais elle a été testée avec succès en production. Il est sans doute possible de l'améliorer.

Remerciements

Je remercie Claude LeloupProfil de Claude Leloup pour sa relecture.

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 © 2011 Jean-Luc Gofflot. Aucune reproduction, même partielle, ne peut être faite de ce site et 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.