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'API 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 :
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 :
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 :
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 :
Var
Entier : LongInt
;
Adresse : pChar;
StrMove(pChar(@Entier),Adresse,SizeOf(Entier));
Ou une chaîne de caractères :
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 :
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 :
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 :
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 :
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 :
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 :
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.
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 où 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 :
(* ---------------------------------------------------------------------------------------------------------------------- *)
(* 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 :
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 :
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.