Необычный способ считать файл в C++: странная проблема производительности

Обычный способ считать файл в C++ является этим:

std::ifstream file("file.txt", std::ios::binary | std::ios::ate);
std::vector data(file.tellg());
file.seekg(0, std::ios::beg);
file.read(data.data(), data.size());

Чтение файла на 1,6 МБ почти мгновенно.

Но недавно, я обнаружил станд.:: istream_iterator и требуемый для попытки его для кодирования красивого короткого способа считать содержание файла. Как это:

std::vector data(std::istream_iterator(std::ifstream("file.txt", std::ios::binary)), std::istream_iterator());

Код является хорошим, но очень медленным. Это берет о 2/3 секундах для чтения того же файла на 1,6 МБ. Я понимаю, что это не может быть лучший способ считать файл, но почему это настолько медленно?

Чтение файла классическим способом идет как это (я говорю только о функции чтения):

  • istream содержит filebuf, который содержит блок данных из файла
  • вызовы функции чтения sgetn от filebuf, который копирует символы один за другим (никакой memcpy) с внутреннего буфера на буфер "данных"
  • когда данные в filebuf полностью считаны, filebuf читает следующий блок из файла

При чтении файла с помощью istream_iterator он идет как это:

  • векторные вызовы *итератор для получения следующего символа (это просто читает переменную), добавляют его в конец и увеличивают ее собственный размер
  • если выделенное место вектора полно (который происходит не так часто), перемещение выполняется
  • затем это звонит ++ итератор, который читает следующий символ из потока (оператор>> с символьным параметром, который, конечно, просто вызывает sbumpc функцию filebuf),
  • наконец это сравнивает итератор с конечным итератором, который сделан путем сравнения двух указателей

Я должен признать, что второй путь не очень эффективен, но это по крайней мере в 200 раз медленнее, чем первый путь, как это возможно?

Я думал, что уничтожитель производительности был перемещениями или вставкой, но я пытался создать весь вектор и назвать станд.:: копия, и это столь же медленно.

// also very slow:
std::vector data2(1730608);
std::copy(std::istream_iterator(std::ifstream("file.txt", std::ios::binary)), std::istream_iterator(), data2.begin());

9
задан Tomaka17 22 July 2010 в 06:25
поделиться

3 ответа

Вы должны сравнить яблоко с яблоком.

Ваш первый код считывает неформатированные двоичные данные, потому что вы используете функцию-член «чтение». И не потому, что вы, кстати, используете std :: ios_binary, подробнее см. http://stdcxx.apache.org/doc/stdlibug/30-4.html , а вкратце: «Эффект двоичного открытого режима часто неправильно понимают. Он не переводит устройства вставки и извлечения в двоичный режим и, следовательно, подавляет форматирование, которое они обычно выполняют. Двоичный ввод и вывод выполняется исключительно с помощью basic_istream <> :: read () и basic_ostream < > :: write () "

Итак, ваш второй код с istream_iterator читает форматированный текст. Это намного медленнее.

Если вы хотите читать неформатированные двоичные данные, используйте istreambuf_iterator:

#include <fstream>
#include <vector>
#include <iterator>

std::ifstream file( "file.txt", std::ios::binary);
std::vector<char> buffer((std::istreambuf_iterator<char>(file)),
                          std::istreambuf_iterator<char>());   

На моей платформе (VS2008) istream_iterator примерно в 100 раз медленнее, чем read (). istreambuf_iterator работает лучше, но все же в 10 раз медленнее, чем read ().

7
ответ дан 3 November 2019 в 00:57
поделиться

Только профилирование скажет вам, почему именно. Я предполагаю, что то, что вы видите, - это просто накладные расходы на все дополнительные вызовы функций, связанные со вторым методом. Вместо одного вызова для ввода всех данных вы выполняете 1,6 миллиона вызовов * ... или что-то в этом роде.

* Многие из них являются виртуальными, что означает два цикла ЦП на вызов. (Ткс Зан)

3
ответ дан 3 November 2019 в 00:57
поделиться

Подход итератора читает файл по одному символу за раз, в то время как file.read делает это за один удар.

Если операционная система/обработчики файла знают, что вы хотите прочитать большой объем данных, можно сделать много оптимизаций - возможно, прочитать весь файл за один оборот шпинделя диска, не копировать данные из буферов ОС в буферы приложений.

Когда вы выполняете побайтовую передачу, ОС не имеет понятия, что вы на самом деле хотите сделать, поэтому не может выполнить такую оптимизацию.

1
ответ дан 3 November 2019 в 00:57
поделиться
Другие вопросы по тегам:

Похожие вопросы: