Быстрое чтение и запись файлов в C ++ - PullRequest
1 голос
/ 23 февраля 2020

Я пытаюсь прочитать и записать в мой SSD несколько мегабайт данных, хранящихся в файлах, состоящих из 8 чисел, преобразованных в строки на строку. Поиск кода C ++ и реализация некоторых ответов здесь для чтения и записи файлов позволили мне получить этот код для чтения файла:

std::stringstream file;
std::fstream stream;
stream.open("file.txt", std::fstream::in);
file << stream.rdbuf();
stream.close(); 

И этот код для записи файлов:

stream.write(file.str().data(), file.tellg());

Проблема в том, что этот код очень медленный по сравнению со скоростью моего SSD. Мой SSD имеет скорость чтения 2400 MB/s и скорость записи 1800 MB/s. Но моя программа имеет скорость чтения всего 180.6 MB/s и скорость записи 25.11 MB/s.

Поскольку некоторые спрашивают, как я измеряю скорость, я получаю std::chrono::steady_clock::time_point, используя std::chrono::steady_clock::now(), а затем делаю std::chrono::duration_cast. Используя тот же самый файл размером 5,6 МБ и разделив размер файла на измеренное время, я получаю мегабайты в секунду.

Как повысить скорость чтения и записи в файлы, используя только стандартные C ++ и STL?

Ответы [ 4 ]

1 голос
/ 23 февраля 2020

Вы можете попытаться скопировать весь файл сразу и посмотреть, улучшает ли это скорость:

#include <algorithm>
#include <fstream>
#include <iterator>

int main() {
    std::ifstream is("infile");
    std::ofstream os("outfile");

    std::copy(std::istreambuf_iterator<char>(is), std::istreambuf_iterator<char>{},
              std::ostreambuf_iterator<char>(os));

    // or simply: os << is.rdbuf()
}
1 голос
/ 23 февраля 2020

Я сделал для вас краткую оценку.

Я написал тестовую программу, которая сначала создает тестовый файл.

Затем я сделал несколько методов улучшения:

  1. Я включаю все оптимизации компилятора
  2. Для строки я использую resize, чтобы избежать перераспределений
  3. Чтение из потока значительно улучшено за счет установки большего входного буфера

Пожалуйста, посмотрите и проверьте, можете ли вы реализовать одну из моих идей для вашего решения


Редактировать

Урезать тестовую программу до чистого чтения:

#include <string>
#include <iterator>
#include <iostream>
#include <fstream>
#include <chrono>
#include <algorithm>

constexpr size_t NumberOfExpectedBytes = 80'000'000;
constexpr size_t SizeOfIOStreamBuffer = 1'000'000;
static char ioBuffer[SizeOfIOStreamBuffer];

const std::string fileName{ "r:\\log.txt" };

void writeTestFile() {
    if (std::ofstream ofs(fileName); ofs) {
        for (size_t i = 0; i < 2'000'000; ++i)
            ofs << "text,text,text,text,text,text," << i << "\n";
    }
}


int main() {

    //writeTestFile();

    // Make string with big buffer
    std::string completeFile{};
    completeFile.resize(NumberOfExpectedBytes);

    if (std::ifstream ifs(fileName); ifs) {

        // Increase buffer size for buffered input
        ifs.rdbuf()->pubsetbuf(ioBuffer, SizeOfIOStreamBuffer);

        // Time measurement start
        auto start = std::chrono::system_clock::now();

        // Read complete file
        std::copy(std::istreambuf_iterator<char>(ifs), {}, completeFile.begin());

        // Time measurement evaluation
        auto end = std::chrono::system_clock::now();
        auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
        // How long did it take?
        std::cout << "Elapsed time:       " << elapsed.count() << " ms\n";
    }
    else std::cerr << "\n*** Error.  Could not open source file\n";

    return 0;
}

С этим я достигаю 123,2 МБ / с

1 голос
/ 23 февраля 2020

В вашем примере медленная часть, скорее всего, повторные вызовы getline(). Хотя это в некоторой степени зависит от реализации, обычно вызов getline в конечном итоге сводится к вызову ОС для извлечения следующей строки текста из открытого файла. Вызовы ОС стоят дорого, и их следует избегать в тесных циклах.

Рассмотрим реализацию getline, которая требует ~ 1 мс служебной информации. Если вы называете это 1000 раз, каждое чтение ~ 80 символов, вы получаете полную секунду служебной информации. С другой стороны, если вы вызываете его один раз и читаете 80 000 символов, вы удалили 999 мс служебной информации, и функция, скорее всего, вернется почти мгновенно.

(Это также одна из причин, по которой игры и тому подобное реализуют пользовательское управление памятью, а не просто malloc и new повсеместно.)

Для чтения: Прочитать весь файл сразу, если он уместится в памяти.

См .: Как мне прочитать весь файл в std :: string в C ++?

В частности, см. slurp ответ внизу. (И принимайте близко к сердцу комментарий об использовании std::vector вместо char[] массива.)

Если он не все умещается в памяти, управляйте им большими кусками.

Для записи: создайте свой вывод в stringstream или аналогичном буфере, а затем запишите его за один шаг или большими кусками, чтобы минимизировать количество обращений ОС.

0 голосов
/ 24 февраля 2020

Похоже, вы выводите отформатированные числа в файл. Уже есть два узких места: форматирование чисел в удобочитаемую форму и файловый ввод / вывод.

Наилучшая производительность, которую вы можете достичь, - это поддерживать поток данных. Запуск и остановка требует накладных расходов.

Я рекомендую двойную буферизацию с двумя или более потоками.

Один поток форматирует данные в один или несколько буферов. Другой поток записывает буферы в файл. Вам нужно будет настроить размер и количество буферов, чтобы обеспечить непрерывность передачи данных. Когда один поток завершает буфер, поток начинает обрабатывать другой буфер. Например, можно записать поток записи, использующий fstream.write() для записи всего буфера.

Двойная буферизация с потоками также может быть адаптирована для чтения. Один поток считывает данные из файла в один или несколько буферов, а другой поток форматирует данные (из буферов) во внутренний формат.

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