Эффективный способ регистрации файлов

0

Я должен регистрировать огромное количество данных в 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 часа. Есть ли другие фундаментальные недостатки?

  • 0
    Подумайте о том, как работают строки. Все ваши strlen и strcat звонков делать много повторяющихся работы , чтобы вычислить то , что вы уже знаете. Советы профессионалов: прочитайте инструкцию для sprintf .
  • 2
    Кроме того, нет необходимости в memset - buffer[0] = '\0' достаточно.
Показать ещё 4 комментария
Теги:
visual-studio-2008
flush
disk-io

2 ответа

3
Лучший ответ

Ваш ответ, который я подозреваю, прямо здесь:

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 - это хороший круглый номер выше этого.)

  • 1
    +1 Хороший ответ и за использование слова egregious .
1

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

size_t size = sprintf(rowInLog + rowInLogSize, "%d,%d,%d,%d,%d\n", a, b, c, d, e);
rowInLogSize += size;
  • 0
    Вам даже не нужен strlen , так как sprintf выдает его в качестве возвращаемого значения.
  • 0
    @JoeZ: спасибо за указание на это: я немного лучше знаком с IOStreams, чем со stdio ...

Ещё вопросы

Сообщество Overcoder
Наверх
Меню