Я сталкивался с некоторыми проблемами при записи в файл, а именно с невозможностью писать достаточно быстро.
Чтобы объяснить, моя цель состоит в том, чтобы захватить поток данных, поступающих через гигабитный 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. Мы ценим любые предложения.
Таким образом, основная проблема заключается в том, что вы пытаетесь писать в том же потоке, что и вы, что означает, что ваш recv () может быть вызван снова только после завершения копирования. Несколько замечаний:
На самом деле, я не знаю, как вы придумали std::copy
подход. Пример rx_samples_to_file, который поставляется с UHD делает это с простой записью, и вы должны определенно одобрить это по сравнению с копированием; Файловый ввод / вывод в хороших ОС часто выполняется с одной копией меньше, и перебор всех элементов, вероятно, очень медленный.
Давайте сделаем немного математики.
Ваши образцы (по-видимому) типа std::complex<std::float>
, Учитывая (типичное) 32-битное значение с плавающей запятой, это означает, что каждая выборка имеет размер 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% больше данных) к процессу, который уже дает сбой, потому что он генерирует слишком много данных.
Запись в двоичном формате означает, что каждое число с плавающей запятой будет занимать ровно 4 байта, поэтому разделители не нужны для правильного считывания данных обратно.
Следующим шагом после этого будет переход к низкоуровневой процедуре файлового ввода-вывода. В зависимости от ситуации, это может иметь или не иметь большого значения. В Windows вы можете указать FILE_FLAG_NO_BUFFERING
когда вы открываете файл с CreateFile
, Это означает, что чтение и запись в этот файл будут в основном обходить кеш и переходить непосредственно на диск.
В вашем случае это, вероятно, победа — при 10 мс / с вы, вероятно, собираетесь использовать пространство кеша довольно долго, прежде чем перечитывать те же данные. В таком случае, если вы поместите данные в кеш, вы практически ничего не получите, но вам придется потратить некоторые данные на то, чтобы скопировать данные в кеш, а затем несколько позже скопировать их на диск. Хуже того, он может загрязнить кеш всеми этими данными, поэтому он больше не хранит другие данные, которые с большей вероятностью выиграют от кеширования.