web-dev-qa-db-fra.com

mise en mémoire tampon std :: fstream vs mise en mémoire tampon manuelle (pourquoi gain 10x avec mise en mémoire tampon manuelle)?

J'ai testé deux configurations d'écriture:

1) Mise en mémoire tampon Fstream:

// 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<char*>(&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 fstream ne change rien par rapport à la situation normale (sans redéfinition d'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 superordinateur (architecture linux 64 bits, dernière version Intel Xeon 8-core, système de fichiers Lustre et ... des compilateurs bien configurés).benchmark (et je n’explique pas la raison de la "résonance" d’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é): enter image description here

50
Vincent

Ceci est essentiellement dû à la surcharge des appels de fonctions et à l'indirection. La méthode ofstream :: write () est héritée d'ostream. Cette fonction n'est pas insérée dans libstdc ++, qui est la première source de surcharge. Ensuite, ostream :: write () doit appeler rdbuf () -> sputn () pour écrire, qui est un appel de fonction virtuelle.

En plus de cela, libstdc ++ redirige sputn () vers une autre fonction virtuelle xsputn () qui ajoute un autre appel de fonction virtuelle.

Si vous mettez vous-même les caractères dans la mémoire tampon, vous pouvez éviter cette surcharge.

24
Vaughn Cato

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 diminution de la performance, comme nous le voyons sur la première image, mais cela ne permet pas de comprendre pourquoi la performance la plus élevée était lorsque la taille de la mémoire tampon manuelle était inférieure à 1024 octets.

Le problème est lié au coût élevé des appels système writev() et write()) et à la mise en œuvre interne de la classe interne std::filebuf 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 une machine Linux afin de copier un fichier de 10 Mo avec différentes tailles de mémoire 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, plus la vitesse d'écriture est faible et le temps passé par dd dans l'espace système. Ainsi, la vitesse de lecture/écriture diminue lorsque la taille de la mémoire tampon diminue.

Mais pourquoi la vitesse maximale était-elle lorsque la taille de la mémoire tampon manuelle était inférieure à 1024 octets dans les tests de mémoire tampon manuels du créateur de rubrique? Pourquoi c'était presque constant?

L'explication concerne la mise en œuvre std::ofstream, en particulier le std::basic_filebuf.

Par défaut, il utilise un tampon de 1024 octets (variable BUFSIZ). Ainsi, lorsque vous écrivez vos données en utilisant des pixels inférieurs à 1024, l'appel système writev() (et non write()) est appelé au moins une fois pour deux opérations ofstream::write() (les processions ont une taille de 1023 <1024 - le premier est écrit dans le tampon, et le second force l'écriture premier et deuxième). Sur cette base, nous pouvons conclure que la vitesse de ofstream::write() ne dépend pas de la taille de la mémoire tampon manuelle avant le pic (la fonction write() est appelée au moins deux fois rarement).

Lorsque vous essayez d'écrire simultanément un tampon supérieur ou égal à 1024 octets à l'aide de l'appel ofstream::write(), l'appel système writev() est appelé pour chaque ofstream::write. Ainsi, vous voyez que la vitesse augmente lorsque la mémoire tampon manuelle est supérieure à 1024 (après le pic).

De plus, si vous souhaitez définir un tampon std::ofstream supérieur à 1024 (par exemple, un tampon de 8192 octets) à l'aide de streambuf::pubsetbuf() et appeler ostream::write() pour écrire des données à l'aide de piles de taille 1024, vous serez surpris de savoir que la vitesse d'écriture sera la même que celle que vous utiliserez. utiliser 1024 tampon. C’est parce que implementation of std::basic_filebuf - la classe interne de std::ofstream - est hard codé pour forcer l’appel système writev() pour chaque appel ofstream::write() lorsque la mémoire tampon passée est supérieure ou égale à 1024 octets (voir basic_filebuf :: xsputn () code source). Il y a aussi un problème en suspens dans le bugzilla de GCC qui a été signalé à 2014-11-05 .

Donc, la solution de ce problème peut être faite en utilisant deux cas possibles:

  • remplacez std::filebuf par votre propre classe et redéfinissez std::ofstream
  • divise un tampon, qui doit être passé à la ofstream::write(), aux places inférieures à 1024 et les passe à la ofstream::write() un à un
  • ne transmettez pas de petites quantités de données à la ofstream::write() pour ne pas nuire aux performances des fonctions virtuelles de std::ofstream
3
nomad85

J'aimerais ajouter aux réponses existantes que ce comportement de performance (tout le temps système généré par les appels de méthode virtuelle/indirection) n'est généralement pas un problème si vous écrivez de grands blocs de données. Ce qui semble avoir été omis de la question et de ces réponses antérieures (bien que probablement implicitement compris) est que le code original écrivait un petit nombre d'octets à chaque fois. Juste pour clarifier pour les autres: si vous écrivez des blocs de données volumineux (~ kB +), il n'y a aucune raison de penser que la mise en mémoire tampon manuelle aura une différence de performances significative par rapport à l'utilisation de la mise en mémoire tampon de std::fstream.

0
wolf1oo