web-dev-qa-db-fra.com

Pourquoi istream/ostream est-il lent?

À 50:40 sur http://channel9.msdn.com/Events/GoingNative/2013/Writing-Quick-Code-in-Cpp-Quickly Andrei Alexandrescu fait une blague sur le manque d'efficacité/de lenteur de la circulation.

Par le passé, j’avais un problème de lenteur avec ostream et d’écriture beaucoup plus rapide (réduction de plusieurs secondes lors de l’exécution de la boucle principale une fois), mais je n’ai jamais compris pourquoi ni examiné la question.

Qu'est-ce qui ralentit istream et ostream en C++? ou du moins lente par rapport à d’autres choses (comme fread/fget, fwrite) qui satisferaient également les besoins.

32
user34537

En fait, IOStreams n'a pas besoin d'être lent! Il s’agit toutefois de les mettre en œuvre de manière raisonnable. La plupart des bibliothèques C++ standard ne semblent pas accorder trop d’attention à la mise en oeuvre d’IOStreams. Il y a longtemps, quand mon CXXRT était toujours maintenu, il était aussi rapide que stdio - utilisé correctement!

Notez qu'il existe peu de pièges de performance pour les utilisateurs configurés avec IOStreams. Les instructions suivantes s'appliquent à toutes les implémentations d'IOStream, mais plus particulièrement à celles conçues pour être rapides:

  1. Lorsque vous utilisez std::cin, std::cout, etc., vous devez appeler std::sync_with_stdio(false)! Sans cet appel, toute utilisation des objets de flux standard est nécessaire pour la synchronisation avec les flux standard du C. Bien sûr, lorsque vous utilisez std::sync_with_stdio(false), on suppose que vous ne mélangez pas std::cin avec stdin, std::cout avec stdout, etc.
  2. Do not / utilise std::endl car il impose de nombreux vidages inutiles de tout tampon. De même, ne définissez pas std::ios_base::unitbuf et n'utilisez pas std::flush inutilement.
  3. Lors de la création de vos propres tampons de flux (OK, peu d'utilisateurs le font), assurez-vous qu'ils utilisent un tampon interne! Le traitement de caractères individuels implique plusieurs conditions et une fonction virtual qui le rend terriblement lent.
41
Dietmar Kühl

Cela peut peut-être donner une idée de ce à quoi vous faites face:

#include <stdio.h>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <fstream>
#include <time.h>
#include <string>
#include <algorithm>

unsigned count1(FILE *infile, char c) { 
    int ch;
    unsigned count = 0;

    while (EOF != (ch=getc(infile)))
        if (ch == c)
            ++count;
    return count;
}

unsigned int count2(FILE *infile, char c) { 
    static char buffer[8192];
    int size;
    unsigned int count = 0;

    while (0 < (size = fread(buffer, 1, sizeof(buffer), infile)))
        for (int i=0; i<size; i++)
            if (buffer[i] == c)
                ++count;
    return count;
}

unsigned count3(std::istream &infile, char c) {    
    return std::count(std::istreambuf_iterator<char>(infile), 
                    std::istreambuf_iterator<char>(), c);
}

unsigned count4(std::istream &infile, char c) {    
    return std::count(std::istream_iterator<char>(infile), 
                    std::istream_iterator<char>(), c);
}

unsigned int count5(std::istream &infile, char c) {
    static char buffer[8192];
    unsigned int count = 0;

    while (infile.read(buffer, sizeof(buffer)))
        count += std::count(buffer, buffer+infile.gcount(), c);
    count += std::count(buffer, buffer+infile.gcount(), c);
    return count;
}

unsigned count6(std::istream &infile, char c) {
    unsigned int count = 0;
    char ch;

    while (infile >> ch)
        if (ch == c)
            ++count;
    return count;
}

template <class F, class T>
void timer(F f, T &t, std::string const &title) { 
    unsigned count;
    clock_t start = clock();
    count = f(t, 'N');
    clock_t stop = clock();
    std::cout << std::left << std::setw(30) << title << "\tCount: " << count;
    std::cout << "\tTime: " << double(stop-start)/CLOCKS_PER_SEC << "\n";
}

int main() {
    char const *name = "equivs2.txt";

    FILE *infile=fopen(name, "r");

    timer(count1, infile, "ignore");

    rewind(infile);
    timer(count1, infile, "using getc");

    rewind(infile);
    timer(count2, infile, "using fread");

    fclose(infile);

    std::ifstream in2(name);
    timer(count3, in2, "ignore");

    in2.clear();
    in2.seekg(0);
    timer(count3, in2, "using streambuf iterators");

    in2.clear();
    in2.seekg(0);
    timer(count4, in2, "using stream iterators");

    in2.clear();
    in2.seekg(0);
    timer(count5, in2, "using istream::read");

    in2.clear();
    in2.seekg(0);
    timer(count6, in2, "using operator>>");

    return 0;
}

En exécutant ceci, j'obtiens des résultats comme celui-ci (avec MS VC++):

ignore                          Count: 1300     Time: 0.309
using getc                      Count: 1300     Time: 0.308
using fread                     Count: 1300     Time: 0.028
ignore                          Count: 1300     Time: 0.091
using streambuf iterators       Count: 1300     Time: 0.091
using stream iterators          Count: 1300     Time: 0.613
using istream::read             Count: 1300     Time: 0.028
using operator>>                Count: 1300     Time: 0.619

et ceci (avec MinGW):

ignore                          Count: 1300     Time: 0.052
using getc                      Count: 1300     Time: 0.044
using fread                     Count: 1300     Time: 0.036
ignore                          Count: 1300     Time: 0.068
using streambuf iterators       Count: 1300     Time: 0.068
using stream iterators          Count: 1300     Time: 0.131
using istream::read             Count: 1300     Time: 0.037
using operator>>                Count: 1300     Time: 0.121

Comme nous pouvons le constater dans les résultats, il n’est pas vraiment question de lenteur catégorique dans iostreams. Cela dépend en grande partie de la manière dont vous utilisez iostreams (et dans une moindre mesure FILE * également). Il y a aussi une assez grande variation entre ces implémentations.

Néanmoins, les versions les plus rapides avec chacune (fread et istream::read) sont essentiellement liées. Avec VC++, getc est un peu plus lent que istream::read et ou istreambuf_iterator.

En résumé: obtenir de bonnes performances de la part d’iostreams nécessite un peu plus de soin qu’avec FILE * - mais c’est certainement possible. Ils vous offrent également plus d'options: confort lorsque vous ne vous souciez pas trop de vitesse et performances directement en concurrence avec le meilleur des E/S de style C, avec un peu de travail supplémentaire.

9
Jerry Coffin

Bien que cette question soit assez ancienne, je suis étonné que personne n'ait mentionné la construction d'objet iostream.

Autrement dit, chaque fois que vous créez une STL iostream (et d’autres variantes de flux), si vous entrez dans le code, le constructeur appelle une fonction interne Init. operator new est appelé pour créer un nouvel objet locale. De même, il est détruit lors de la destruction.

C'est hideux, à mon humble avis. Et contribue certainement à la lenteur de la construction/destruction des objets, car la mémoire est allouée/désallouée à l'aide d'un verrou système, à un moment donné.

De plus, certains flux STL vous permettent de spécifier une allocator, alors pourquoi la locale est-elle créée PAS en utilisant l'allocateur spécifié?

En utilisant des flux dans un environnement multithread, vous pouvez également imaginer le goulot d'étranglement imposé en appelant operator new chaque fois qu'un nouvel objet de flux est construit.

Hideux désordre si vous me le demandez, comme je le découvre moi-même en ce moment!

0
dicksters

Sur un sujet similaire, STL indique: "Vous pouvez appeler setvbuf () pour activer la mise en mémoire tampon sur stdout."

https://connect.Microsoft.com/VisualStudio/feedback/details/642876/std-wcout-is-ten-tten-slower-than-wprintf-performance-bug-in-c-library

0
AndrewDover