Почему 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), которые одинаково удовлетворяли бы потребности.
Ответы
Ответ 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-стиля, с небольшой дополнительной работой.
Ответ 3
По аналогичной теме STL говорит: "Вы можете вызвать setvbuf() для включения буферизации на stdout".
https://web.archive.org/web/20170329163751/https://connect.microsoft.com/VisualStudio/feedback/details/642876/std-wcout-is-ten-times-slower-than-wprintf-performance- ошибка-в-с-библиотеки
Ответ 4
Хотя этот вопрос довольно старый, я удивлен, что никто не упоминал о конструкции объекта iostream.
То есть, всякий раз, когда вы создаете iostream
STL (и другие варианты потока), если вы входите в код, конструктор вызывает внутреннюю функцию Init
. Там появляется operator new
для создания нового объекта locale
. И также уничтожается при уничтожении.
Это отвратительно, ИМХО. И, конечно же, способствует медленному построению объекта/разрушению, поскольку в какой-то момент память выделяется/освобождается с помощью системной блокировки.
Кроме того, некоторые потоки STL позволяют указать allocator
, поэтому почему locale
создана с использованием указанного распределителя?
Используя потоки в многопотоковой среде, вы также можете представить себе узкое место, навязываемое вызовом operator new
каждый раз при создании нового объекта потока.
Ужасный беспорядок, если вы спросите меня, как я сейчас выясняю!