Pourquoi ce programme C ++ est-il plus lent sous Windows que sous Linux?

Considérons le programme suivant:

#define _FILE_OFFSET_BITS 64 // Allow large files. #define REVISION "POSIX Revision #9" #include  #include  #include  const int block_size = 1024 * 1024; const char block[block_size] = {}; int main() { std::cout << REVISION << std::endl; std::time_t t0 = time(NULL); std::cout << "Open: 'BigFile.bin'" << std::endl; FILE * file; file = fopen("BigFile.bin", "wb"); if (file != NULL) { std::cout << "Opened. Writing..." << std::endl; for (int n=0; n<4096; n++) { size_t written = fwrite(block, 1, block_size, file); if (written != block_size) { std::cout << "Write error." << std::endl; return 1; } } fclose(file); std::cout << "Success." << std::endl; time_t t1 = time(NULL); if (t0 == ((time_t)-1) || t1 == ((time_t)-1)) { std::cout << "Clock error." << std::endl; return 2; } double ticks = (double)(t1 - t0); std::cout << "Seconds: " << ticks << std::endl; file = fopen("BigFile.log", "w"); fprintf(file, REVISION); fprintf(file, " Seconds: %f\n", ticks); fclose(file); return 0; } std::cout << "Something went wrong." << std::endl; return 1; } 

Il écrit simplement 4 Go de zéros dans un fichier sur le disque et le temps nécessaire.

Sous Linux, cela prend en moyenne 148 secondes. Sous Windows, sur le même PC, il faut en moyenne 247 secondes.

Que diable fais-je mal?!

Le code est compilé sous GCC pour Linux et Visual Studio pour Windows, mais je ne peux pas imaginer un univers dans lequel le compilateur utilisé devrait faire toute différence mesurable avec un test de performances d’E / S pur. Le système de fichiers utilisé dans tous les cas est NTFS.

Je ne comprends tout simplement pas pourquoi une telle différence de performance existe. Je ne sais pas pourquoi Windows est si lent. Comment puis-je forcer Windows à fonctionner à pleine vitesse dont le disque est clairement capable?

(Les chiffres ci-dessus concernent OpenSUSE 13.1 32 bits et Windows XP 32 bits sur un ancien ordinateur portable Dell. Cependant, j’ai observé des différences de vitesse similaires sur plusieurs ordinateurs au bureau, exécutant différentes versions de Windows.)

Edit: l’exécutable et le fichier qu’il écrit résident tous deux sur un disque dur USB externe formaté en NTFS et presque complètement vide. La fragmentation n’est presque certainement pas un problème. Cela pourrait être une sorte de problème de pilote, mais j’ai vu la même différence de performance sur plusieurs autres systèmes exécutant des versions différentes de Windows. Il n’y a pas d’antivirus installé.

Juste pour rire, j’ai essayé de le changer pour utiliser directement l’API Win32. (Évidemment, cela ne fonctionne que pour Windows.) Le temps devient un peu plus irrégulier, mais rest à quelques pour cent de ce qu’il était auparavant. Sauf si je spécifie FILE_FLAG_WRITE_THROUGH ; alors il va beaucoup plus lentement. Quelques autres drapeaux le rendent plus lent, mais je ne trouve pas celui qui le fait aller plus vite

Vous devez synchroniser le contenu des fichiers sur le disque, sinon vous ne faites que mesurer le niveau de mise en cache effectué par le système d’exploitation.

Appelez fsync avant de fermer le fichier.

Si vous ne le faites pas, la majeure partie du temps d’exécution est probablement passée à attendre que le cache soit vidé afin que de nouvelles données puissent y être stockées, mais une partie des données que vous écrivez ne sera certainement pas écrite sur le disque. l’heure à laquelle vous fermez le fichier. La différence de temps d’exécution est donc probablement due à la mise en cache de linux par une plus grande quantité d’écritures avant qu’elle ne soit à court d’espace de cache disponible. En revanche, si vous appelez fsync avant de fermer le fichier, toutes les données écrites doivent être vidées sur le disque avant la mesure du temps.

Je suppose que si vous ajoutez un appel fsync , le temps d’exécution sur les deux systèmes ne différera pas tellement.

Votre test n’est pas un très bon moyen de mesurer les performances car il existe des endroits où différentes optimisations dans différents systèmes d’exploitation et bibliothèques peuvent faire une énorme différence (le compilateur lui-même n’a pas à faire une grande différence).

Tout d’abord, nous pouvons considérer que fwrite (ou tout élément qui opère sur FILE* ) est une couche de bibliothèque au-dessus de la couche OS. Il peut y avoir différentes stratégies de mise en mémoire tampon qui font la différence. Par exemple, une manière intelligente d’implémenter fwrite serait de vider les tampons, puis d’envoyer le bloc de données directement au système d’exploitation au lieu de passer par la couche tampon. Cela peut entraîner un énorme avantage à la prochaine étape

Deuxièmement, nous avons le système d’exploitation / kernel qui peut gérer l’écriture différemment. Une optimisation intelligente consisterait à copier les pages en les aliasant simplement, puis à utiliser la méthode copy-on-write si elles sont modifiées dans l’un des alias. Linux le fait déjà (ou presque) lorsqu’il alloue de la mémoire au processus (y compris la section BSS où se trouve le tableau) – il marque simplement la page comme étant des zéros et peut en conserver une seule pour toutes ces pages quelqu’un change de page zéro. Faire à nouveau cette astuce signifie que le kernel pourrait simplement alias une telle page dans le tampon de disque. Cela signifie que le kernel ne serait pas à court de cache disque lors de l’écriture de tels blocs de zéros, car il ne prend que 4 Ko de mémoire réelle (sauf pour les tables de pages). Cette stratégie est également possible s’il existe des données réelles dans le bloc de données.

Cela signifie que les écritures peuvent se terminer très rapidement sans qu’aucune donnée ne soit réellement transférée sur le disque (avant que fwrite ne fwrite terminé), même sans que les données doivent être copiées d’un endroit à un autre en mémoire.

Donc, vous utilisez différentes bibliothèques et différents systèmes d’exploitation et il n’est pas surprenant qu’ils effectuent des tâches différentes à des moments différents.

Il existe des optimisations spéciales pour les pages qui sont toutes des zéros. Vous devez remplir la page avec des données aléatoires avant de l’écrire.