Улучшение / оптимизация скорости записи файлов в C++

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

чтобы объяснить, моя цель-захватить поток данных, поступающих через gigabit Ethernet, и просто сохранить его в файл.

необработанные данные поступают со скоростью 10 мс / с, а затем сохраняются в буфер и впоследствии записываются в файл.

Ниже приведен соответствующий раздел кода:

    std::string path = "Stream/raw.dat";
    ofstream outFile(path, ios::out | ios::app| ios::binary);

    if(outFile.is_open())
        cout << "Yes" << endl;

    while(1)
    {
         rxSamples = rxStream->recv(&rxBuffer[0], rxBuffer.size(), metaData);
         switch(metaData.error_code)
         {

             //Irrelevant error checking...

             //Write data to a file
                std::copy(begin(rxBuffer), end(rxBuffer), std::ostream_iterator<complex<float>>(outFile));
         }
    } 

проблема, с которой я сталкиваюсь, что слишком долго записывать образцы в файл. Через секунду или около того устройство, отправляющее образцы, сообщает, что его буфер переполнен. После некоторого быстрого профилирования кода почти все время выполнения тратится на std::copy(...) (99.96% времени, чтобы быть точным). Если я удалю эту строку, я могу запустить программу в течение нескольких часов без каких-либо переполнений.

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

Если это полезно, я запускаю эту программу на Ubuntu x86_64. Любые предложения будут оценены.

2 ответов


Итак, основная проблема здесь заключается в том, что вы пытаетесь написать в том же потоке, что и получаете, что означает, что ваш recv() может быть вызван только после завершения копирования. Несколько замечаний:

  • переместить запись в другой поток. Речь идет о USRP, поэтому GNU Radio действительно может быть инструментом по вашему выбору - он по своей сути многопоточен.
  • ваш выходной итератор, вероятно, не является самым эффективным решением. Просто " write()" в файловый дескриптор может быть лучше, но это показатели производительности, которые до вас
  • если ваш жесткий диск / файловая система / OS / CPU не соответствуют ставкам, поступающим из USRP, даже если развязка получения от записи по потоку, то вы ничего не можете сделать-получить более быструю систему.
  • попробуйте записать на RAM-диск вместо

на самом деле, я не знаю, как вы придумали std::copy подход. The rx_samples_to_file пример, который поставляется с UHD это с простой записью, и вы определенно должны отдать предпочтение этому копированию; файловый ввод-вывод может, на хороших Осах, часто выполняться с одной копией меньше, и итерация по всем элементам, вероятно, очень медленная.


давайте немного посчитаем.

ваши образцы (по-видимому) типа std::complex<std::float>. Учитывая (типичный) 32-битный float, это означает, что каждый образец составляет 64 бита. При 10 мс / с это означает, что необработанные данные составляют около 80 мегабайт в секунду-это в пределах того, что вы можете ожидать от записи на жесткий диск рабочего стола (7200 об / мин), но довольно близко к пределу (который обычно составляет около 100-100 мегабайт в секунду или около того).

к сожалению, несмотря на std::ios::binary, ты на самом деле запись данных в текстовом формате (потому что std::ostream_iterator в основном делает stream << data;).

это не только теряет точность, но увеличивает размер данных, по крайней мере как правило. Точный размер увеличения зависит от данных-небольшое целое значение может фактически уменьшить количество данных, но для произвольного ввода увеличение размера, близкое к 2:1, довольно распространено. С увеличением 2:1 Ваши исходящие данные теперь составляют около 160 мегабайт / секунду, что быстрее, чем большинство жестких дисков ручка.

очевидной отправной точкой для улучшения было бы записать данные в двоичном формате:

uint32_t nItems = std::end(rxBuffer)-std::begin(rxBuffer);
outFile.write((char *)&nItems, sizeof(nItems));
outFile.write((char *)&rxBuffer[0], sizeof(rxBuffer));

на данный момент я использую sizeof(rxBuffer) в предположении, что это реальный массив. Если это на самом деле указатель или вектор, вам придется вычислить правильный размер (то, что вы хотите, это общее количество байтов, которые будут записаны).

Я бы также отметил, что в настоящее время ваш код имеет еще более серьезную проблему: поскольку он не имеет указан разделитель между элементами при записи данных, данные будут записываться без чего-либо, чтобы отделить один элемент от другого. Это означает, что если вы написали два значения (например) 1 и 0.2, что ты читать не будет 1 и 0.2, но одно значение 10.2. Добавление разделителей к текстовому выходу добавит еще больше накладных расходов (рисунок около 15% больше данных) к процессу, который уже терпит неудачу, потому что он генерирует слишком много данные.

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

следующим шагом после этого было бы спуститься к процедуре ввода-вывода файлов более низкого уровня. В зависимости от ситуации это может иметь или не иметь большого значения. В Windows можно указать FILE_FLAG_NO_BUFFERING при открытии файла с CreateFile. Это означает, что чтение и запись в этот файл будет в основном обходить кэш и перейти непосредственно к диску.

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