Question

J'aimerais poser une question, puis la suivre avec ma propre réponse, mais aussi voir les réponses des autres.

Nous avons deux gros fichiers que nous aimerions lire simultanément à partir de deux threads distincts.Un thread lira séquentiellement le fichierA tandis que l'autre thread lira séquentiellement le fichierB.Il n'y a pas de verrouillage ni de communication entre les threads, les deux lisent séquentiellement aussi vite qu'ils le peuvent et les deux rejettent immédiatement les données qu'ils lisent.

Notre expérience avec cette configuration sous Windows est très mauvaise.Le débit combiné des deux threads est de l’ordre de 2 à 3 Mio/s.Le lecteur semble passer la plupart de son temps à chercher d'avant en arrière entre les deux fichiers, lisant probablement très peu après chaque recherche.

Si nous désactivons l'un des threads et examinons temporairement les performances d'un seul thread, nous obtenons une bien meilleure bande passante (~ 45 Mio/s pour cette machine).Il est donc clair que les mauvaises performances à deux threads sont un artefact du planificateur de disque du système d'exploitation.

Pouvons-nous faire quelque chose pour améliorer les performances de lecture des threads simultanés ? Peut-être en utilisant différentes API ou en modifiant les paramètres du planificateur de disque du système d'exploitation d'une manière ou d'une autre.

Quelques détails:

Les fichiers sont de l'ordre de 2 Go chacun sur une machine dotée de 2 Go de RAM.Pour les besoins de cette question, nous considérons qu'ils ne sont pas mis en cache et parfaitement défragmentés.Nous avons utilisé des outils de défragmentation et redémarré pour nous assurer que c'est le cas.

Nous n'utilisons aucune API spéciale pour lire ces fichiers.Le comportement est reproductible sur diverses API standard telles que CreateFile de Win32, fopen de C, std::ifstream de C++, FileInputStream de Java, etc.

Chaque thread tourne dans une boucle en appelant la fonction de lecture.Nous avons fait varier le nombre d'octets demandés à l'API à chaque itération, entre des valeurs comprises entre 1 Ko et 128 Mo.Varier cela n'a eu aucun effet, donc clairement la quantité que le système d'exploitation lit physiquement après chaque recherche de disque n'est pas dictée par ce nombre.C’est exactement ce à quoi il faut s’attendre.

La différence spectaculaire entre les performances à un et deux threads est reproductible sous Windows 2000, Windows XP (32 bits et 64 bits), Windows Server 2003, ainsi qu'avec et sans matériel RAID5.

Était-ce utile?

La solution

Le problème semble provenir de la politique de planification des E/S de Windows.D'après ce que j'ai trouvé ici il existe de nombreuses façons pour un système d'exploitationpour planifier les requêtes de disque.Alors que Linux et d'autres peuvent choisir entre différentes politiques, avant Vista, Windows était verrouillé dans une seule politique :une file d'attente FIFO, où toutes les requêtes étaient divisées en blocs de 64 Ko.Je pense que cette politique est à l'origine du problème que vous rencontrez :le planificateur mélangera les requêtes des deux threads, provoquant une recherche continue entre différentes zones du disque.
Maintenant, la bonne nouvelle est que, selon ici et ici, Vista a introduit un planificateur de disque plus intelligent, dans lequel vous pouvez définir la priorité de vos requêtes et également allouer une bande passante minimale pour votre processus.
La mauvaise nouvelle est que je n'ai trouvé aucun moyen de modifier la stratégie de disque ou la taille des tampons dans les versions précédentes de Windows.De plus, même si augmenter la priorité d'E/S disque de votre processus améliorera les performances par rapport aux autres processus, vous rencontrerez toujours des problèmes de concurrence entre vos threads.
Ce que je peux suggérer, c'est de modifier votre logiciel en introduisant une politique d'accès au disque que vous avez créée vous-même.
Par exemple, vous pouvez utiliser une stratégie comme celle-ci dans votre fil de discussion B (similaire pour le fil de discussion A) :

if THREAD A is reading from disk then wait for THREAD A to stop reading or wait for X ms
Read for X ms (or Y MB)
Stop reading and check status of thread A again  

Vous pouvez utiliser des sémaphores pour vérifier l'état ou utiliser des compteurs de performances pour obtenir l'état de la file d'attente du disque réelle.Les valeurs de X et/ou Y peuvent également être ajustées automatiquement en vérifiant les taux de transfert réels et en les modifiant lentement, maximisant ainsi le débit lorsque l'application s'exécute sur différentes machines et/ou systèmes d'exploitation.Vous constaterez peut-être que les niveaux de cache, de mémoire ou de RAID les affectent d'une manière ou d'une autre, mais avec le réglage automatique, vous obtiendrez toujours les meilleures performances dans tous les scénarios.

Autres conseils

J'aimerais ajouter quelques notes supplémentaires dans ma réponse.Tous les autres systèmes d'exploitation non Microsoft que nous avons testés ne souffrent pas de ce problème.Linux, FreeBSD et Mac OS X (ce dernier sur un matériel différent) se dégradent tous beaucoup plus gracieusement en termes de bande passante globale lors du passage d'un thread à deux.Linux, par exemple, s'est dégradé de ~45 Mio/s à ~42 Mio/s.Ces autres systèmes d'exploitation doivent lire des morceaux plus gros du fichier entre chaque recherche et ne passent donc pas presque tout leur temps à attendre sur le disque pour effectuer la recherche.

Notre solution pour Windows consiste à passer le FILE_FLAG_NO_BUFFERING drapeau à CreateFile et utilisez des lectures volumineuses (~ 16 Mo) à chaque appel pour ReadFile.Ceci n’est pas optimal pour plusieurs raisons :

  • Les fichiers ne sont pas mis en cache lorsqu'ils sont lus de cette manière, il n'y a donc aucun des avantages que la mise en cache offre normalement.
  • Les contraintes liées au travail avec cet indicateur sont beaucoup plus compliquées que la lecture normale (alignement des tampons de lecture sur les limites des pages, etc.).

(En guise de remarque finale.Cela explique-t-il pourquoi le swap sous Windows est si infernal ?C'est-à-dire que Windows est incapable d'effectuer des E/S sur plusieurs fichiers simultanément avec une quelconque efficacité, donc lors de l'échange, toutes les autres opérations d'E/S sont obligées d'être disproportionnellement lentes.)


Modifier pour ajouter quelques détails supplémentaires sur Will Dean :

Bien entendu, selon ces différentes configurations matérielles, les chiffres bruts ont changé (parfois de manière substantielle).Le problème réside cependant dans la dégradation constante des performances que seul Windows subit lors du passage d'un thread à deux.Voici un récapitulatif des machines testées :

  • Plusieurs postes de travail Dell (Intel Xeon) d'âges divers exécutant Windows 2000, Windows XP (32 bits) et Windows XP (64 bits) avec un seul lecteur.
  • Un serveur Dell 1U (Intel Xeon) exécutant Windows Server 2003 (64 bits) avec RAID 1+0.
  • Une station de travail HP (AMD Opteron) avec Windows XP (64 bits) et Windows Server 2003 et matériel RAID 5.
  • Mon PC personnel sans marque (AMD Athlon64) exécutant Windows XP (32 bits), FreeBSD (64 bits) et Linux (64 bits) avec un seul lecteur.
  • Mon MacBook personnel (Intel Core1) exécutant Mac OS X, un seul lecteur SATA.
  • Ma maison Koolu PC sous Linux.Largement sous-alimenté par rapport aux autres systèmes, mais j'ai démontré que même cette machine peut surpasser un serveur Windows avec RAID5 lors de lectures de disque multithread.

L'utilisation du processeur sur tous ces systèmes était très faible pendant les tests et l'antivirus était désactivé.

J'ai oublié de le mentionner avant mais nous avons également essayé le Win32 normal CreateFile API avec le FILE_FLAG_SEQUENTIAL_SCAN ensemble de drapeaux.Ce drapeau n'a pas résolu le problème.

Il semble un peu étrange que vous ne voyiez aucune différence entre un large éventail de versions de Windows et rien entre un seul disque et un raid-5 matériel.

Ce n'est qu'une «intuition», mais cela me fait douter qu'il s'agisse vraiment d'un simple problème de recherche.À part OS X et Raid5, tout cela a-t-il été essayé sur la même machine ? Avez-vous essayé une autre machine ?Votre utilisation du processeur est-elle pratiquement nulle pendant ce test ?

Quelle est l'application la plus courte que vous puissiez écrire et qui illustre ce problème ?- Je serais intéressé de l'essayer ici.

Je créerais une sorte de verrouillage thread-safe en mémoire.Chaque thread pouvait attendre sur le verrou jusqu'à ce qu'il soit libre.Lorsque le verrou devient libre, prenez-le et lisez le fichier pendant une durée définie ou une quantité de données définie, puis relâchez le verrou pour tous les autres threads en attente.

Utilises tu Ports d'achèvement d'IO sous Windows ?Windows via C++ a un chapitre détaillé sur ce sujet et comme par hasard, il est également disponible sur MSDN.

Paul - a vu la mise à jour.Très intéressant.

Il serait intéressant de l'essayer sur Vista ou Win2008, car les gens semblent signaler des améliorations considérables des E/S dans certaines circonstances.

Ma seule suggestion concernant une API différente serait d'essayer de mapper la mémoire des fichiers - avez-vous essayé cela ?Malheureusement, avec 2 Go par fichier, vous ne pourrez pas mapper plusieurs fichiers entiers sur une machine 32 bits, ce qui signifie que ce n'est pas aussi trivial qu'il pourrait l'être.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top