Я должен регистрировать огромное количество данных в 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 (N 2) в размере buffer
. При увеличении размера вашего буфера ваша производительность будет быстро снижаться. Это в значительной степени противоположно тому, что вы хотите от своего буфера. (Edit: Вы упомянули в комментарии, что ваш буфер равен 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);
Это не основная проблема производительности вашего кода, но изменение этого улучшит его.
EDIT: Еще лучший альтернативный подход:
Если вы хотите полностью исключить 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
значение maximumLineSize
в разумное значение для вашей строки из 5 целых чисел, например 60. (10 байтов для каждого целого числа, включая знак плюс 5 байтов для запятых и новой строки, равно 55, поэтому 60 - это хороший круглый номер выше этого.)
egregious
.
Вы вычисляете длину всей строки каждый раз! Это означает, что всю и растущую строку нужно перетасовывать через процессор. Это примерно соответствует худшему сценарию! Вы намного лучше записываете строку в файл раз в то время. Кроме того, вы должны отслеживать последнюю позицию записи и добавлять строку прямо там:
size_t size = sprintf(rowInLog + rowInLogSize, "%d,%d,%d,%d,%d\n", a, b, c, d, e);
rowInLogSize += size;
strlen
, так как sprintf
выдает его в качестве возвращаемого значения.
strlen
иstrcat
звонков делать много повторяющихся работы , чтобы вычислить то , что вы уже знаете. Советы профессионалов: прочитайте инструкцию дляsprintf
.memset
-buffer[0] = '\0'
достаточно.