Подтвердить что ты не робот

Почему istream/ostream slow

В 50:40 http://channel9.msdn.com/Events/GoingNative/2013/Writing-Quick-Code-in-Cpp-Quickly Андрей Александреску делает анекдот о том, как неэффективен/медленный istream.

У меня была проблема в прошлом, когда ostream был медленным, а fwrite был значительно быстрее (сокращая много секунд при запуске основного цикла один раз), но я никогда не понимал, почему и не заглядывал в него.

Что делает istream и ostream медленным в С++? или, по крайней мере, медленнее по сравнению с другими вещами (например, fread/fget, fwrite), которые одинаково удовлетворяли бы потребности.

4b9b3361

Ответ 1

Собственно, IOStreams не должны быть медленными! Речь идет о том, чтобы сделать их разумным способом сделать их быстрыми. Большинство стандартных библиотек С++, похоже, не уделяют слишком много внимания внедрению IOStreams. Давно, когда мой CXXRT все еще поддерживался, он был примерно таким же быстрым, как stdio - при правильном использовании!

Обратите внимание, что есть несколько ловушек производительности для пользователей, выложенных с помощью IOStreams. Следующие рекомендации относятся ко всем реализациям IOStream, но особенно к тем, которые настроены быстро:

  • При использовании std::cin, std::cout и т.д. вам нужно позвонить std::sync_with_stdio(false)! Без этого вызова для синхронизации с стандартными потоками C требуется любое использование стандартных потоков. Конечно, при использовании std::sync_with_stdio(false) предполагается, что вы не смешиваете std::cin с stdin, std::cout с помощью stdout и т.д.
  • Используйте не std::endl, поскольку он требует много ненужных сбросов любого буфера. Аналогично, не устанавливайте std::ios_base::unitbuf или используйте std::flush без необходимости.
  • При создании собственных буферов потока (в порядке, немногие пользователи), убедитесь, что они используют внутренний буфер! Обработка отдельных символов перескакивает через несколько условий и функцию virtual, которая делает ее ужасно медленной.

Ответ 2

Возможно, это может дать некоторое представление о том, с чем вы имеете дело:

#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;
}

Запустив это, я получаю такие результаты (с MS VС++):

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

и это (с 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

Как мы можем видеть в результатах, на самом деле вопрос о том, что iostreams категорически невелик. Скорее, многое зависит от того, как вы используете iostreams (и в меньшей степени FILE *). Там также довольно существенное изменение только между ними для реализаций.

Тем не менее, самые быстрые версии с каждым (fread и istream::read) по существу связаны. С VС++ getc довольно немного медленнее, чем istream::read или или istreambuf_iterator.

В нижней строке: получение хорошей производительности от iostreams требует немного большей осторожности, чем с FILE *, но это, безусловно, возможно. Они также дают вам больше возможностей: удобство, когда вам все равно не нравится скорость, а производительность напрямую конкурирует с лучшими, которые вы можете получить от ввода-вывода C-стиля, с небольшой дополнительной работой.

Ответ 4

Хотя этот вопрос довольно старый, я удивлен, что никто не упоминал о конструкции объекта iostream.

То есть, всякий раз, когда вы создаете iostream STL (и другие варианты потока), если вы входите в код, конструктор вызывает внутреннюю функцию Init. Там появляется operator new для создания нового объекта locale. И также уничтожается при уничтожении.

Это отвратительно, ИМХО. И, конечно же, способствует медленному построению объекта/разрушению, поскольку в какой-то момент память выделяется/освобождается с помощью системной блокировки.

Кроме того, некоторые потоки STL позволяют указать allocator, поэтому почему locale создана с использованием указанного распределителя?

Используя потоки в многопотоковой среде, вы также можете представить себе узкое место, навязываемое вызовом operator new каждый раз при создании нового объекта потока.

Ужасный беспорядок, если вы спросите меня, как я сейчас выясняю!