Необычный способ чтения файла в C ++: странная проблема с производительностью - PullRequest
9 голосов
/ 22 июля 2010

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

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

Чтение файла объемом 1,6 МБ происходит практически мгновенно.

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

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

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

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

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

Когда вы читаете файл с помощью istream_iterator, он выглядит следующим образом:

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

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

Я думал, что снижение производительности - это перемещение или вставка, но я пыталсясоздание целого вектора и вызов std :: copy, и это так же медленно.

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

Ответы [ 3 ]

6 голосов
/ 22 июля 2010

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

Ваш первый код прочитал неформатированные двоичные данные, потому что вы используете функцию-член "read".И не потому, что вы используете 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 ().

3 голосов
/ 22 июля 2010

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

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

1 голос
/ 22 июля 2010

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...