Les E/S de disque asynchrones apparaissent comme synchrones sur Windows

Cet article vous aide à résoudre le problème où le comportement par défaut pour les E/S est synchrone, mais il apparaît comme asynchrone.

              Version d’origine du produit : Windows
Numéro de la 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 les E/S ne sont pas supposées être terminées avant 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 la status 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 davantage de demandes pendant que l’opération d’E/S est terminée. Le terme E/S superposées 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 Asynchrone et Synchrone pour les opérations d’E/S. Cet article part du principe que le lecteur est familiarisé avec les fonctions d’E/S de fichier telles que CreateFile, ReadFile, WriteFile.

Souvent, 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, qui rendent les opérations d’E/S effectuées de manière synchrone. L’appelant n’a pas de temps pour travailler 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 comme exemples. De bonnes alternatives seraient ReadFileEx et WriteFileEx. Bien que cet article traite uniquement des E/S de disque en particulier, de nombreux 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é dans CreateFile lors de l’ouverture du fichier. Cet indicateur permet d’effectuer des opérations d’E/S sur le fichier 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 de rendre 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.

Un programme peut effectuer de nombreuses opérations en attendant la fin des opérations asynchrones, telles que la mise en file d’attente d’opérations supplémentaires ou l’exécution d’un travail en arrière-plan. Par exemple, le code suivant gère correctement l’exécution d’une opération de lecture qui se chevauche et ne se chevauche pas. 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);

Remarque

&NumberOfBytesRead passé dans ReadFile est différent de &NumberOfBytesTransferred passé dans GetOverlappedResult. Si une opération a été rendue asynchrone, GetOverlappedResult est utilisée pour déterminer le nombre réel d’octets transférés dans l’opération une fois qu’elle s’est terminée. Le &NumberOfBytesRead passé dans ReadFile n’a pas de sens.

En revanche, si une opération est effectuée immédiatement, le &NumberOfBytesRead passage à ReadFile est valide pour le nombre d’octets lus. Dans ce cas, ignorez la OVERLAPPED structure passée dans ReadFile; ne l’utilisez pas avec GetOverlappedResult ou WaitForSingleObject.

Une autre mise en garde avec l’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 de rencontrer une altération des 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 une opération antérieure terminée.

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 être synchrones

Si vous avez suivi les instructions plus haut dans cet article, toutefois, toutes vos opérations d’E/S sont généralement effectuées de manière synchrone dans l’ordre d’émission, 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 un 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 l’opération asynchrone.

Compression

La compression NTFS (New Technology File System) constitue un obstacle à l’opération asynchrone. Le pilote du système de fichiers n’accède pas aux fichiers compressés de manière asynchrone ; au lieu de cela, toutes les opérations sont effectuées de manière 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 synchrones par le pilote système. Si les fichiers sont déchiffrés, les demandes 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 est synchrone.

Remarque

Les applications peuvent rendre l’opération d’écriture mentionnée précédemment asynchrone en modifiant la longueur des données valides du fichier à l’aide de la SetFileValidData fonction , puis en émettant un WriteFile.

À l’aide SetFileValidData de (qui est disponible sur Windows XP et les versions ultérieures), les applications peuvent étendre efficacement les fichiers sans entraîner de pénalité de performances pour les remplir sans les remplir.

Étant donné que le système de fichiers NTFS ne remplit pas à zéro les données jusqu’à la longueur de données valide (VDL) définie par SetFileValidData, cette fonction a des implications en matière de sécurité lorsque le fichier peut recevoir des clusters précédemment occupés par d’autres fichiers. Par conséquent, SetFileValidData nécessite que l’appelant ait activé le nouveau SeManageVolumePrivilege (par défaut, il 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, etc.) ont un code de cas spécial où, si une demande d’E/S peut être effectuée immédiatement, l’opération est terminée et la ReadFile fonction ou WriteFile retourne TRUE. De toutes les façons, ces types d’opérations semblent être synchrones. Pour un périphérique de 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 contre 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 asynchrone d’erreur de page 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 demande 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 demandes, 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 effectuez de nombreuses opérations d’E/S pour des données qui ne se trouvent pas dans le cache, le gestionnaire de cache et le gestionnaire de mémoire sont saturés et vos demandes sont effectuées de façon 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 les plus visibles 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 aux fichiers de manière aléatoire, utilisez l’indicateur FILE_FLAG_RANDOM_ACCESS dans CreateFile pour 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 s’agit du 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.

Remarque

L’utilisation de cet indicateur présente certaines restrictions liées à l’alignement de la mémoire tampon de données et à la taille du secteur de l’appareil. Pour plus d’informations, consultez les informations de référence sur la fonction CreateFile dans la documentation sur l’utilisation correcte de cet indicateur.

Résultats réels des tests

Voici quelques résultats de test de l’exemple de code. L’ampleur des nombres n’est pas importante ici et varie d’un ordinateur à l’autre, mais la relation entre les nombres les uns par rapport aux autres éclaire l’effet général des indicateurs sur les performances.

Vous pouvez vous attendre à voir des résultats similaires à l’un des 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 500 demandes d’E/S rapidement 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 terminer ses opérations, mais que le test 1 n’a passé que 0,224264 seconde pour émettre les mêmes requêtes. Dans le test 2, le programme n’avait pas de temps supplémentaire pour effectuer 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 demandes asynchrones ont été effectuées de manière synchrone. Ce test montre é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 montre les mêmes résultats que dans le test 3. Les lectures synchrones à partir du cache sont un peu plus rapides que les lectures asynchrones à partir du cache. Ce test montre également les performances élevées du gestionnaire de cache lorsque les données se trouvent dans le cache.

Conclusion

Vous pouvez choisir la meilleure méthode, car tout 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’indicateur spécial à CreateFile est une opération synchrone et mise en cache.

Remarque

Vous obtenez un comportement asynchrone automatique dans ce mode, car le pilote de système de fichiers effectue une lecture prédictive asynchrone et une écriture différée asynchrone des données modifiées. Bien que ce comportement ne rende pas les E/S de l’application asynchrones, 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 plus haut dans cet article. Il est utile de profiler le temps passé dans la ReadFile fonction ou , WriteFile puis de comparer ce temps au temps nécessaire à l’exécution des opérations d’E/S réelles. Si la majorité du temps est consacrée à émettre réellement les E/S, votre E/S est effectuée de manière synchrone. Toutefois, si le temps consacré à l’émission des demandes d’E/S est relativement faible par rapport au temps nécessaire à l’exécution des opérations d’E/S, vos opérations sont traitées de manière asynchrone. L’exemple de code mentionné plus haut dans cet article utilise la QueryPerformanceCounter fonction pour effectuer son propre profilage interne.

L’analyse des performances peut vous aider à déterminer l’efficacité de votre programme à l’aide du disque et du cache. Le suivi 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.

Plusieurs utilitaires sont utiles pour l’analyse des performances. PerfMon et DiskPerf sont particulièrement utiles. Pour que le système collecte des données sur les performances des systèmes à disque, vous devez d’abord émettre la DiskPerf commande . Après avoir créé la commande, vous devez redémarrer le système pour démarrer la collecte de données.

References

E/S synchrones et asynchrones