Я должен записать огромное количество данных в файл CSV с каждой строкой, имеющей 5 элементов. Я использовал большой буфер для хранения строк, а затем очистить его в один выстрел, используя fwrite(...)
когда он заполнится и повторите до необходимости. Ниже приведен фрагмент функции регистрации:
void logInFile(int a, int b, int c, int d, int e)
{
sprintf(rowInLog,"%d,%d,%d,%d,%d\n",a,b,c,d,e);
int bytesInRow = strlen(rowInLog);
if(bytesInRow + bytesUsedInBuffer <= sizeOfBuffer)
{
strcat(buffer, rowInLog);
bytesUsedInBuffer += bytesInRow;
}
else
{
printf("flushing file to disk\n");
fwrite(buffer, bytesUsedInBuffer, 1, fp);
memset(buffer, 0, sizeOfBuffer);
bytesUsedInBuffer = 0;
strcat(buffer, rowInLog);
bytesUsedInBuffer += bytesInRow;
}
}
Но это делает выполнение намного медленным, и это не из-за сброса, потому что сообщение «сброс файла на диск» не выводится на экран. Без какого-либо вызова этой функции регистрации вся программа выполняется за считанные минуты, но при этом она не завершилась даже за 2 часа. Есть ли какой-то другой фундаментальный недостаток?
Ваш ответ, я подозреваю, прямо здесь:
if(bytesInRow + bytesUsedInBuffer <= sizeOfBuffer)
{
strcat(buffer, rowInLog); // <--- riiiight here.
bytesUsedInBuffer += bytesInRow;
}
strcat()
функция будет сканировать весь buffer
чтобы найти конец, когда ты это называешь. Если buffer
это большой и в основном полный, это может быть довольно медленно. Поведение примерно O (N2) в размере buffer
, Ваша производительность будет быстро снижаться по мере увеличения размера буфера. Это в значительной степени противоположно тому, что вы хотите от вашего буфера. (Изменить: Вы упомянули в комментарии, что ваш буфер 1 ГБ. Я ожидаю, что приведенный выше код будет очень, очень медленно, как этот буфер заполняет.)
Тем не менее, вы уже знаете именно так где конец, и сколько байт для копирования. Так что сделайте это вместо этого:
if(bytesInRow + bytesUsedInBuffer <= sizeOfBuffer)
{
memcpy(buffer + bytesUsedInBuffer, rowInLog, bytesInRow + 1);
bytesUsedInBuffer += bytesInRow;
}
Обратите внимание, что у меня было memcpy
скопируйте один дополнительный байт, чтобы он поместил терминатор NUL в буфер, на случай, если у вас есть какие-либо другие функции strXXX, которые работают на buffer
, Если вы этого не сделаете, вы можете безопасно удалить + 1
выше.
Похожая, менее вопиющая ошибка происходит в вашем else
пункт. Вы можете заменить это также memcpy
:
printf("flushing file to disk\n");
fwrite(buffer, bytesUsedInBuffer, 1, fp);
memcpy(buffer, rowInLog, bytesInRow + 1);
bytesUsedInBuffer = bytesInRow;
Вы также можете сэкономить немного времени, комбинируя эти операторы:
sprintf(rowInLog,"%d,%d,%d,%d,%d\n",a,b,c,d,e);
int bytesInRow = strlen(rowInLog);
sprintf
возвращает длину выходной строки, так что вы можете просто сказать:
int bytesInRow = sprintf(rowInLog,"%d,%d,%d,%d,%d\n",a,b,c,d,e);
Это не было главной проблемой производительности в вашем коде, но изменение, которое улучшит это далее.
РЕДАКТИРОВАТЬ: Еще лучший альтернативный подход:
Если вы хотите устранить memcpy()
полностью, рассмотрим этот альтернативный подход:
bytesUsedInBuffer += snprintf( buffer + bytesUsedInBuffer, maximumLineSize,
"%d,%d,%d,%d,%d\n", a,b,c,d,e );
if (bytesUsedInBuffer >= sizeOfBuffer - maximumLineSize )
{
fwrite(buffer, bytesUsedInBuffer, 1, fp);
bytesUsedInBuffer = 0;
}
Задавать maximumLineSize
до разумного значения для вашей строки из 5 целых чисел, например 60. (10 байтов для каждого целого числа, включая знак плюс 5 байтов для запятых и новой строки — 55, поэтому 60 — это хорошее круглое число выше этого).
Вы каждый раз вычисляете длину всей строки! Это означает, что вся и растущая строка должна быть перетасована через процессор. Это грубо говоря наихудший сценарий! Вы много лучше время от времени записывать строку в файл. Кроме того, вы должны отслеживать последнюю позицию записи и добавлять строку прямо здесь:
size_t size = sprintf(rowInLog + rowInLogSize, "%d,%d,%d,%d,%d\n", a, b, c, d, e);
rowInLogSize += size;