J’ai testé deux configurations d’écriture:
1) Mise en mémoire tampon des stream:
// Initialization const unsigned int length = 8192; char buffer[length]; std::ofstream stream; stream.rdbuf()->pubsetbuf(buffer, length); stream.open("test.dat", std::ios::binary | std::ios::trunc) // To write I use : stream.write(reinterpret_cast(&x), sizeof(x));
2) mise en mémoire tampon manuelle:
// Initialization const unsigned int length = 8192; char buffer[length]; std::ofstream stream("test.dat", std::ios::binary | std::ios::trunc); // Then I put manually the data in the buffer // To write I use : stream.write(buffer, length);
Je m’attendais au même résultat …
Mais ma mise en mémoire tampon manuelle améliore les performances d’un facteur 10 pour écrire un fichier de 100 Mo, et la mise en mémoire tampon des stream ne change rien par rapport à la situation normale (sans redéfinir un tampon).
Est-ce que quelqu’un a une explication de cette situation?
EDIT: Voici les nouvelles: un benchmark vient d’être fait sur un supercalculateur (architecture Linux 64 bits, système de fichiers Lustre et … compilateurs bien configurés). (et je n’explique pas la raison de la “résonance” pour un tampon manuel de 1 Ko …)
EDIT 2: Et la résonance à 1024 B (si quelqu’un a une idée à ce sujet, je suis intéressé):
Cela est essentiellement dû à l’appel de fonction overhead et indirection. La méthode ofstream :: write () est héritée de ostream. Cette fonction n’est pas intégrée dans libstdc ++, qui est la première source de surcharge. Ensuite, ostream :: write () doit appeler rdbuf () -> sputn () pour effectuer l’écriture proprement dite, qui est un appel de fonction virtuel.
De plus, libstdc ++ redirige sputn () vers une autre fonction virtuelle xsputn () qui ajoute un autre appel de fonction virtuelle.
Si vous mettez les caractères dans le tampon vous-même, vous pouvez éviter cette surcharge.
Je voudrais expliquer quelle est la cause du pic dans le deuxième graphique
En fait, les fonctions virtuelles utilisées par std::ofstream
entraînent une std::ofstream
de la performance, comme on peut le voir sur la première image, mais cela ne permet pas de savoir pourquoi la performance la plus élevée se produit lorsque la taille de la mémoire tampon manuelle est inférieure à 1024 octets.
Le problème concerne le coût élevé des appels système writev()
et write()
) et l’implémentation interne de la classe interne std::ofstream
de std::ofstream
.
Pour montrer l’influence de write()
sur les performances, j’ai effectué un test simple à l’aide de l’outil dd
sur ma machine Linux pour copier un fichier de 10 Mo avec différentes tailles de tampon (option bs):
test@test$ time dd if=/dev/zero of=zero bs=256 count=40000 40000+0 records in 40000+0 records out 10240000 bytes (10 MB) copied, 2.36589 s, 4.3 MB/s real 0m2.370s user 0m0.000s sys 0m0.952s test$test: time dd if=/dev/zero of=zero bs=512 count=20000 20000+0 records in 20000+0 records out 10240000 bytes (10 MB) copied, 1.31708 s, 7.8 MB/s real 0m1.324s user 0m0.000s sys 0m0.476s test@test: time dd if=/dev/zero of=zero bs=1024 count=10000 10000+0 records in 10000+0 records out 10240000 bytes (10 MB) copied, 0.792634 s, 12.9 MB/s real 0m0.798s user 0m0.008s sys 0m0.236s test@test: time dd if=/dev/zero of=zero bs=4096 count=2500 2500+0 records in 2500+0 records out 10240000 bytes (10 MB) copied, 0.274074 s, 37.4 MB/s real 0m0.293s user 0m0.000s sys 0m0.064s
Comme vous pouvez le constater, moins la mémoire tampon est importante, moins la vitesse d’écriture est élevée et le temps passé par dd
dans l’espace système. La vitesse de lecture / écriture diminue donc lorsque la taille du tampon diminue.
Mais pourquoi la vitesse la plus élevée était-elle lorsque la taille de la mémoire tampon manuelle était inférieure à 1024 octets dans le sujet? Pourquoi c’était presque constant?
L’explication concerne l’implémentation std::ofstream
, en particulier pour std::basic_filebuf
.
Par défaut, il utilise un tampon de 1024 octets (variable BUFSIZ). Ainsi, lorsque vous écrivez vos données en utilisant moins de 1024, l’appel système writev()
(not write()
) est appelé au moins une fois pour deux opérations ofstream::write()
(les tailles ont une taille de 1023 <1024 - la première est écrite à la mémoire tampon, et la deuxième force d'écriture des premier et deuxième). Sur cette base, nous pouvons conclure que la ofstream::write()
ne dépend pas de la taille de la mémoire tampon manuelle avant que le pic ( write()
soit appelé au moins deux fois rarement).
Lorsque vous essayez d’écrire un tampon supérieur ou égal à 1024 octets à la fois, en utilisant l’ ofstream::write()
appel système writev()
est appelé pour chaque ofstream::write
. Ainsi, vous voyez que la vitesse augmente lorsque le tampon manuel est supérieur à 1024 (après le pic).
En outre, si vous souhaitez définir std::ofstream
tampon std::ofstream
supérieur à 1024 buffer (par exemple, tampon de 8192 octets) en utilisant streambuf::pubsetbuf()
et appeler ostream::write()
pour écrire des données en taille 1024, vous serait surpris que la vitesse d’écriture sera la même que vous utiliserez 1024 tampon. C’est parce que l’ implémentation de std::basic_filebuf
– la classe interne de std::ofstream
– est codée en dur pour forcer l’ appel système writev()
pour chaque appel ofstream::write()
lorsque le tampon transmis est supérieur ou égal à 1024 octets ( voir le code source basic_filebuf :: xsputn () ). Il y a également un problème ouvert dans le bugzilla de GCC qui a été signalé au 2014-11-05 .
Ainsi, la solution de ce problème peut être faite en utilisant deux cas possibles:
std::filebuf
par votre propre classe et redéfinir std::ofstream
ofstream::write()
, aux peaces inférieurs à 1024 et les transmettre au ofstream::write()
un par un ofstream::write()
pour éviter de diminuer les performances des fonctions virtuelles de std::ofstream
Je voudrais append aux réponses existantes que ce comportement de performance (tout le surcoût des appels / indirection de la méthode virtuelle) n’est généralement pas un problème si vous écrivez de gros blocs de données. Ce qui semble avoir été omis de la question et de ces réponses antérieures (bien que probablement implicitement comsockets) est que le code original écrivait un petit nombre d’octets à chaque fois. Juste pour clarifier pour les autres: si vous écrivez de gros blocs de données (~ kB +), il n’ya aucune raison de s’attendre à ce que la mise en mémoire tampon manuelle ait une différence de performance significative avec la mise en mémoire tampon de std::fstream
.