Les E/S de disque asynchrones s’affichent comme synchrones sur Windows
Cet article vous aide à résoudre le problème où le comportement par défaut des E/S est synchrone, mais il apparaît comme asynchrone.
Version du produit d’origine : Windows
Numéro de base de connaissances d’origine : 156932
Résumé
Les E/S de fichier sur Microsoft Windows peuvent être synchrones ou asynchrones. Le comportement par défaut pour les E/S est synchrone, où une fonction d’E/S est appelée et retourne une fois l’E/S terminée. Les E/S asynchrones permettent à une fonction d’E/S de retourner immédiatement l’exécution à l’appelant, mais l’E/S n’est pas supposée être terminée jusqu’à un certain temps. Le système d’exploitation avertit l’appelant lorsque l’E/S est terminée. Au lieu de cela, l’appelant peut déterminer l’état de l’opération d’E/S en attente à l’aide des services du système d’exploitation.
L’avantage des E/S asynchrones est que l’appelant a le temps d’effectuer d’autres tâches ou d’émettre plus de demandes pendant que l’opération d’E/S est terminée. Le terme E/S superposé est fréquemment utilisé pour les E/S asynchrones et les E/S non superposées pour les E/S synchrones. Cet article utilise les termes asynchrones et synchrones pour les opérations d’E/S. Cet article suppose que le lecteur connaît les fonctions d’E/S de fichier telles que CreateFile
, ReadFile
, WriteFile
.
Fréquemment, les opérations d’E/S asynchrones se comportent comme des E/S synchrones. Certaines conditions décrites dans cet article dans les sections ultérieures, ce qui rend les opérations d’E/S terminées de manière synchrone. L’appelant n’a pas de temps pour le travail en arrière-plan, car les fonctions d’E/S ne retournent pas tant que l’E/S n’est pas terminée.
Plusieurs fonctions sont liées aux E/S synchrones et asynchrones. Cet article utilise ReadFile
et WriteFile
en tant qu’exemples. De bonnes alternatives seraient ReadFileEx
et WriteFileEx
. Bien que cet article traite uniquement des E/S de disque spécifiquement, bon nombre des principes peuvent être appliqués à d’autres types d’E/S, tels que les E/S série ou les E/S réseau.
Configurer des E/S asynchrones
L’indicateur FILE_FLAG_OVERLAPPED
doit être spécifié lors CreateFile
de l’ouverture du fichier. Cet indicateur permet aux opérations d’E/S sur le fichier d’être effectuées de manière asynchrone. Voici un exemple :
HANDLE hFile;
hFile = CreateFile(szFileName,
GENERIC_READ,
0,
NULL,
OPEN_EXISTING,
FILE_FLAG_NORMAL | FILE_FLAG_OVERLAPPED,
NULL);
if (hFile == INVALID_HANDLE_VALUE)
ErrorOpeningFile();
Soyez prudent lorsque vous codez des E/S asynchrones, car le système se réserve le droit d’effectuer une opération synchrone si nécessaire. Il est donc préférable d’écrire le programme pour gérer correctement une opération d’E/S qui peut être effectuée de manière synchrone ou asynchrone. L’exemple de code illustre cette considération.
Il existe de nombreuses choses qu’un programme peut faire en attendant que les opérations asynchrones se terminent, telles que la mise en file d’attente d’opérations supplémentaires ou le travail en arrière-plan. Par exemple, le code suivant gère correctement la saisie semi-automatique et non superposée d’une opération de lecture. Il ne fait rien de plus que d’attendre que les E/S en suspens se terminent :
if (!ReadFile(hFile,
pDataBuf,
dwSizeOfBuffer,
&NumberOfBytesRead,
&osReadOperation )
{
if (GetLastError() != ERROR_IO_PENDING)
{
// Some other error occurred while reading the file.
ErrorReadingFile();
ExitProcess(0);
}
else
// Operation has been queued and
// will complete in the future.
fOverlapped = TRUE;
}
else
// Operation has completed immediately.
fOverlapped = FALSE;
if (fOverlapped)
{
// Wait for the operation to complete before continuing.
// You could do some background work if you wanted to.
if (GetOverlappedResult( hFile,
&osReadOperation,
&NumberOfBytesTransferred,
TRUE))
ReadHasCompleted(NumberOfBytesTransferred);
else
// Operation has completed, but it failed.
ErrorReadingFile();
}
else
ReadHasCompleted(NumberOfBytesRead);
Note
&NumberOfBytesRead
passé à ReadFile
est différent de &NumberOfBytesTransferred
passé dans GetOverlappedResult
. Si une opération a été effectuée de façon asynchrone, GetOverlappedResult
elle est utilisée pour déterminer le nombre réel d’octets transférés dans l’opération une fois l’opération terminée. Le &NumberOfBytesRead
passé ReadFile
est insensé.
En revanche, si une opération est terminée immédiatement, elle &NumberOfBytesRead
ReadFile
est valide pour le nombre d’octets lus. Dans ce cas, ignorez la OVERLAPPED
structure passée ; ReadFile
ne l’utilisez pas avec GetOverlappedResult
ou WaitForSingleObject
.
Une autre mise en garde avec une opération asynchrone est que vous ne devez pas utiliser une OVERLAPPED
structure tant que son opération en attente n’est pas terminée. En d’autres termes, si vous avez trois opérations d’E/S en suspens, vous devez utiliser trois OVERLAPPED
structures. Si vous réutilisez une OVERLAPPED
structure, vous recevrez des résultats imprévisibles dans les opérations d’E/S et vous risquez d’être endommagé par les données. En outre, vous devez l’initialiser correctement afin qu’aucune donnée restante n’affecte la nouvelle opération avant de pouvoir utiliser une OVERLAPPED
structure pour la première fois ou avant de la réutiliser une fois qu’une opération antérieure a terminé.
Le même type de restriction s’applique à la mémoire tampon de données utilisée dans une opération. Une mémoire tampon de données ne doit pas être lue ou écrite tant que son opération d’E/S correspondante n’est pas terminée ; la lecture ou l’écriture de la mémoire tampon peut entraîner des erreurs et des données endommagées.
Les E/S asynchrones semblent toujours synchrones
Si vous avez suivi les instructions décrites précédemment dans cet article, toutefois, toutes vos opérations d’E/S sont toujours terminées de manière synchrone dans l’ordre émis, et aucune des ReadFile
opérations ne retourne FALSE avec GetLastError()
le retour ERROR_IO_PENDING
, ce qui signifie que vous n’avez pas de temps pour tout travail en arrière-plan. Pourquoi cela se produit-il ?
Il existe plusieurs raisons pour lesquelles les opérations d’E/S se terminent de manière synchrone même si vous avez codé pour une opération asynchrone.
Compression
L’une des obstacles à l’opération asynchrone est la compression NTFS (New Technology File System). Le pilote du système de fichiers n’accède pas de manière asynchrone aux fichiers compressés ; à la place, toutes les opérations sont effectuées de façon synchrone. Cette obstruction ne s’applique pas aux fichiers compressés avec des utilitaires similaires à COMPRESS ou PKZIP.
Chiffrement NTFS
À l’instar de la compression, le chiffrement de fichiers entraîne la conversion des E/S asynchrones en E/S asynchrones. Si les fichiers sont déchiffrés, les requêtes d’E/S sont asynchrones.
Étendre un fichier
Une autre raison pour laquelle les opérations d’E/S sont effectuées de manière synchrone est les opérations elles-mêmes. Sur Windows, toute opération d’écriture dans un fichier qui étend sa longueur sera synchrone.
Note
Les applications peuvent rendre l’opération d’écriture mentionnée précédemment asynchrone en modifiant la longueur de données valide du fichier à l’aide de la SetFileValidData
fonction, puis en émettant un WriteFile
.
À l’aide SetFileValidData
de (qui est disponible sur Windows XP et versions ultérieures), les applications peuvent étendre efficacement les fichiers sans entraîner de pénalité de performances pour les remplissages zéro.
Étant donné que le système de fichiers NTFS ne remplit pas les données jusqu’à la longueur de données valide (VDL) définie par , cette fonction a des implications de SetFileValidData
sécurité lorsque le fichier peut être affecté à des clusters précédemment occupés par d’autres fichiers. Par conséquent, SetFileValidData
exige que l’appelant ait le nouveau SeManageVolumePrivilege
activé (par défaut, cela est attribué uniquement aux administrateurs). Microsoft recommande que les éditeurs de logiciels indépendants (ISV) examinent attentivement les implications de l’utilisation de cette fonction.
Cache
La plupart des pilotes d’E/S (disque, communications et autres) ont un code de cas particulier dans lequel, si une demande d’E/S peut être effectuée immédiatement, l’opération est terminée et la ReadFile
WriteFile
fonction retourne TRUE. De toutes façons, ces types d’opérations semblent être synchrones. Pour un appareil disque, en général, une demande d’E/S peut être effectuée immédiatement lorsque les données sont mises en cache en mémoire.
Les données ne sont pas dans le cache
Toutefois, le schéma de cache peut fonctionner sur vous si les données ne se trouvent pas dans le cache. Le cache Windows est implémenté en interne à l’aide de mappages de fichiers. Le gestionnaire de mémoire dans Windows ne fournit pas de mécanisme d’erreur de page asynchrone pour gérer les mappages de fichiers utilisés par le gestionnaire de cache. Le gestionnaire de cache peut vérifier si la page demandée est en mémoire. Par conséquent, si vous émettez une lecture mise en cache asynchrone et que les pages ne sont pas en mémoire, le pilote du système de fichiers suppose que vous ne souhaitez pas que votre thread soit bloqué et que la requête sera gérée par un pool limité de threads de travail. Le contrôle est retourné à votre programme après votre ReadFile
appel avec la lecture toujours en attente.
Cela fonctionne correctement pour un petit nombre de requêtes, mais étant donné que le pool de threads de travail est limité (actuellement trois sur un système de 16 Mo), il n’y aura toujours que quelques requêtes mises en file d’attente vers le pilote de disque à un moment donné. Si vous émettez de nombreuses opérations d’E/S pour les données qui ne se trouvent pas dans le cache, le gestionnaire de cache et le gestionnaire de mémoire deviennent saturés et vos requêtes sont effectuées de manière synchrone.
Le comportement du gestionnaire de cache peut également être influencé selon que vous accédez à un fichier de manière séquentielle ou aléatoire. Les avantages du cache sont visibles le plus souvent lors de l’accès séquentiel aux fichiers. L’indicateur FILE_FLAG_SEQUENTIAL_SCAN
dans l’appel CreateFile
optimise le cache pour ce type d’accès. Toutefois, si vous accédez à des fichiers de manière aléatoire, utilisez l’indicateur FILE_FLAG_RANDOM_ACCESS
pour CreateFile
indiquer au gestionnaire de cache d’optimiser son comportement pour l’accès aléatoire.
N’utilisez pas le cache
L’indicateur FILE_FLAG_NO_BUFFERING
a le plus d’effet sur le comportement du système de fichiers pour l’opération asynchrone. Il est le meilleur moyen de garantir que les demandes d’E/S sont asynchrones. Il indique au système de fichiers de ne pas utiliser de mécanisme de cache du tout.
Note
Il existe certaines restrictions à l’utilisation de cet indicateur qui doit être lié à l’alignement de la mémoire tampon de données et à la taille du secteur de l’appareil. Pour plus d’informations, consultez la référence de fonction dans la documentation relative à la fonction CreateFile sur l’utilisation de cet indicateur correctement.
Résultats des tests réels
Voici quelques résultats de test de l’exemple de code. L’ampleur des nombres n’est pas importante ici et varie de l’ordinateur à l’ordinateur, mais la relation des nombres par rapport aux autres éclaire l’effet général des indicateurs sur les performances.
Vous pouvez vous attendre à voir les résultats similaires à l’un des éléments suivants :
Test 1
Asynchronous, unbuffered I/O: asynchio /f*.dat /n Operations completed out of the order in which they were requested. 500 requests queued in 0.224264 second. 500 requests completed in 4.982481 seconds.
Ce test montre que le programme mentionné précédemment a émis rapidement 500 demandes d’E/S et a eu beaucoup de temps pour effectuer d’autres tâches ou émettre plus de demandes.
Test 2
Synchronous, unbuffered I/O: asynchio /f*.dat /s /n Operations completed in the order issued. 500 requests queued and completed in 4.495806 seconds.
Ce test montre que ce programme a passé 4,495880 secondes à appeler ReadFile pour effectuer ses opérations, mais que le test 1 n’a passé que 0,224264 seconde pour émettre les mêmes demandes. Dans le test 2, il n’y avait pas de temps supplémentaire pour que le programme effectue un travail en arrière-plan.
Test 3
Asynchronous, buffered I/O: asynchio /f*.dat Operations completed in the order issued. 500 requests issued and completed in 0.251670 second.
Ce test illustre la nature synchrone du cache. Toutes les lectures ont été émises et terminées en 0,251670 seconde. En d’autres termes, les requêtes asynchrones ont été effectuées de façon synchrone. Ce test illustre également les performances élevées du gestionnaire de cache lorsque les données se trouvent dans le cache.
Test 4
Synchronous, buffered I/O: asynchio /f*.dat /s Operations completed in the order issued. 500 requests and completed in 0.217011 seconds.
Ce test illustre les mêmes résultats que dans le test 3. Les lectures synchrones du cache se terminent un peu plus rapidement que les lectures asynchrones du cache. Ce test illustre également les performances élevées du gestionnaire de cache lorsque les données se trouvent dans le cache.
Conclusion
Vous pouvez choisir la méthode la mieux, car elle dépend du type, de la taille et du nombre d’opérations effectuées par votre programme.
L’accès au fichier par défaut sans spécifier d’indicateurs spéciaux est CreateFile
une opération synchrone et mise en cache.
Note
Vous obtenez un comportement asynchrone automatique dans ce mode, car le pilote du système de fichiers effectue des écritures asynchrones asynchrones en lecture anticipée et asynchrone différée d’écriture différée de données modifiées. Bien que ce comportement ne rende pas asynchrone les E/S de l’application, il s’agit du cas idéal pour la grande majorité des applications simples.
En revanche, si votre application n’est pas simple, vous devrez peut-être effectuer un profilage et une surveillance des performances pour déterminer la meilleure méthode, comme les tests illustrés précédemment dans cet article. Le profilage du temps passé dans le ou WriteFile
de la ReadFile
fonction, puis la comparaison de ce temps à la durée nécessaire pour que les opérations d’E/S réelles se terminent sont utiles. Si la majorité du temps est consacré à l’émission réelle des E/S, votre E/S est terminée de façon synchrone. Toutefois, si le temps passé à émettre des demandes d’E/S est relativement faible par rapport au temps nécessaire pour que les opérations d’E/S soient terminées, vos opérations sont traitées de manière asynchrone. L’exemple de code mentionné précédemment dans cet article utilise la QueryPerformanceCounter
fonction pour effectuer son propre profilage interne.
La surveillance des performances peut vous aider à déterminer l’efficacité de votre programme à l’aide du disque et du cache. Le suivi de l’un des compteurs de performances de l’objet Cache indique les performances du gestionnaire de cache. Le suivi des compteurs de performances pour les objets Disque physique ou Disque logique indique les performances des systèmes de disque.
Il existe plusieurs utilitaires utiles dans la surveillance des performances. PerfMon
et DiskPerf
sont particulièrement utiles. Pour que le système collecte des données sur les performances des systèmes de disque, vous devez d’abord émettre la DiskPerf
commande. Après avoir émettre la commande, vous devez redémarrer le système pour démarrer la collecte de données.