эффективность fwrite для огромного количества небольших записей

У меня есть программа, которая сохраняет много больших файлов> 1 ГБ, используя fwrite Он работает нормально, но, к сожалению, из-за характера данных каждый вызов fwrite пишет только 1-4 байта. в результате запись может занять более часа, причем большая часть этого времени, по-видимому, связана с издержками системного вызова (или, по крайней мере, в библиотечной функции fwrite). У меня похожая проблема с fread,

Кто-нибудь знает о каких-либо существующих функциях / библиотеках, которые будут буферизовать эти записи и чтения с помощью встроенной функции, или это другой ролик самостоятельно?

6

Решение

Прежде всего, fwrite() это библиотека, а не системный вызов. Во-вторых, он уже буферизует данные.

Возможно, вы захотите поэкспериментировать с увеличением размера буфера. Это делается с помощью setvbuf(). В моей системе это только немного помогает, но YMMV.

Если setvbuf() не помогает, вы можете сделать свою собственную буферизацию и только вызвать fwrite() как только вы накопили достаточно данных. Это требует больше работы, но почти наверняка ускорит процесс записи, поскольку ваша собственная буферизация может быть значительно упрощена. fwrite()«S.

редактировать: Если кто-то скажет вам, что это просто число fwrite() звонки, которые являются проблемой, требуют, чтобы увидеть доказательства. А еще лучше, сделайте свои собственные тесты производительности. На моем компьютере 500 000 000 двухбайтовых записей с использованием fwrite() займет 11 секунд. Это соответствует пропускной способности около 90 МБ / с.

И последнее, но не менее важное: огромное расхождение между 11 секундами в моем тесте и одним часом, указанным в вашем вопросе, указывает на возможность того, что в вашем коде происходит что-то еще, что приводит к очень низкой производительности.

9

Другие решения

ваша проблема не в буферизации для fwrite(), но общие накладные расходы на выполнение библиотечного вызова с небольшими объемами данных. если вы пишете только 1 МБ данных, вы делаете 250000 вызовов функций. вам лучше попытаться собрать ваши данные в памяти, а затем записать на диск с помощью одного вызова fwrite(),

ОБНОВИТЬЕсли вам нужны доказательства:

$ dd if=/dev/zero of=/dev/null count=50000000 bs=2
50000000+0 records in
50000000+0 records out
100000000 bytes (100 MB) copied, 55.3583 s, 1.8 MB/s
$ dd if=/dev/zero of=/dev/null count=50 bs=2000000
50+0 records in
50+0 records out
100000000 bytes (100 MB) copied, 0.0122651 s, 8.2 GB/s
5

ОК, это было интересно. Я думал, что напишу какой-то реальный код, чтобы увидеть, какова была скорость. И вот оно. Скомпилировано с использованием C ++ DevStudio 2010 Express. Здесь довольно много кода. Это раз 5 способов записи данных:

  • Наивно называя fwrite
  • Использование буфера и меньшее количество вызовов для записи с использованием больших буферов
  • Использование Win32 API наивно
  • Использование буфера и меньшее количество обращений к Win32 с использованием больших буферов
  • Использование Win32, но двойная буферизация вывода и использование асинхронной записи

Пожалуйста, проверьте, что я не сделал что-то немного глупо с любым из вышеперечисленного.

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

Результаты на моей машине (старая коробка WinXP SP3): —

  • fwrite сам по себе, как правило, самый быстрый, хотя буферизованная версия может побить его, если вы правильно выберете размер и итерации.
  • Наивный Win32 значительно медленнее
  • Буферизованный Win32 удваивает скорость, но он все еще легко побежден fwrite
  • Асинхронные записи не были значительно лучше, чем буферизованная версия. Возможно, кто-то может проверить мой код и убедиться, что я не сделал глупости, потому что я никогда раньше не использовал асинхронный ввод-вывод.

Вы можете получить разные результаты в зависимости от ваших настроек.

Не стесняйтесь редактировать и улучшать код.

    #define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <memory.h>
#include <Windows.h>

const int
// how many times fwrite/my_fwrite is called
c_iterations = 10000000,
// the size of the buffer used by my_fwrite
c_buffer_size = 100000;

char
buffer1 [c_buffer_size],
buffer2 [c_buffer_size],
*current_buffer = buffer1;

int
write_ptr = 0;

__int64
write_offset = 0;

OVERLAPPED
overlapped = {0};

// write to a buffer, when buffer full, write the buffer to the file using fwrite
void my_fwrite (void *ptr, int size, int count, FILE *fp)
{
const int
c = size * count;

if (write_ptr + c > c_buffer_size)
{
fwrite (buffer1, write_ptr, 1, fp);
write_ptr = 0;
}

memcpy (&buffer1 [write_ptr], ptr, c);
write_ptr += c;
}

// write to a buffer, when buffer full, write the buffer to the file using Win32 WriteFile
void my_fwrite (void *ptr, int size, int count, HANDLE fp)
{
const int
c = size * count;

if (write_ptr + c > c_buffer_size)
{
DWORD
written;

WriteFile (fp, buffer1, write_ptr, &written, 0);
write_ptr = 0;
}

memcpy (&buffer1 [write_ptr], ptr, c);
write_ptr += c;
}

// write to a double buffer, when buffer full, write the buffer to the file using
// asynchronous WriteFile (waiting for previous write to complete)
void my_fwrite (void *ptr, int size, int count, HANDLE fp, HANDLE wait)
{
const int
c = size * count;

if (write_ptr + c > c_buffer_size)
{
WaitForSingleObject (wait, INFINITE);

overlapped.Offset = write_offset & 0xffffffff;
overlapped.OffsetHigh = write_offset >> 32;
overlapped.hEvent = wait;

WriteFile (fp, current_buffer, write_ptr, 0, &overlapped);
write_offset += write_ptr;
write_ptr = 0;
current_buffer = current_buffer == buffer1 ? buffer2 : buffer1;
}

memcpy (current_buffer + write_ptr, ptr, c);
write_ptr += c;
}

int main ()
{
// do lots of little writes
FILE
*f1 = fopen ("f1.bin", "wb");

LARGE_INTEGER
f1_start,
f1_end;

QueryPerformanceCounter (&f1_start);

for (int i = 0 ; i < c_iterations ; ++i)
{
fwrite (&i, sizeof i, 1, f1);
}

fclose (f1);

QueryPerformanceCounter (&f1_end);

// do a few big writes
FILE
*f2 = fopen ("f2.bin", "wb");

LARGE_INTEGER
f2_start,
f2_end;

QueryPerformanceCounter (&f2_start);

for (int i = 0 ; i < c_iterations ; ++i)
{
my_fwrite (&i, sizeof i, 1, f2);
}

if (write_ptr)
{
fwrite (buffer1, write_ptr, 1, f2);
write_ptr = 0;
}

fclose (f2);

QueryPerformanceCounter (&f2_end);

// use Win32 API, without buffer
HANDLE
f3 = CreateFile (TEXT ("f3.bin"), GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);

LARGE_INTEGER
f3_start,
f3_end;

QueryPerformanceCounter (&f3_start);

for (int i = 0 ; i < c_iterations ; ++i)
{
DWORD
written;

WriteFile (f3, &i, sizeof i, &written, 0);
}

CloseHandle (f3);

QueryPerformanceCounter (&f3_end);

// use Win32 API, with buffer
HANDLE
f4 = CreateFile (TEXT ("f4.bin"), GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_FLAG_WRITE_THROUGH, 0);

LARGE_INTEGER
f4_start,
f4_end;

QueryPerformanceCounter (&f4_start);

for (int i = 0 ; i < c_iterations ; ++i)
{
my_fwrite (&i, sizeof i, 1, f4);
}

if (write_ptr)
{
DWORD
written;

WriteFile (f4, buffer1, write_ptr, &written, 0);
write_ptr = 0;
}

CloseHandle (f4);

QueryPerformanceCounter (&f4_end);

// use Win32 API, with double buffering
HANDLE
f5 = CreateFile (TEXT ("f5.bin"), GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_FLAG_OVERLAPPED | FILE_FLAG_WRITE_THROUGH, 0),
wait = CreateEvent (0, false, true, 0);

LARGE_INTEGER
f5_start,
f5_end;

QueryPerformanceCounter (&f5_start);

for (int i = 0 ; i < c_iterations ; ++i)
{
my_fwrite (&i, sizeof i, 1, f5, wait);
}

if (write_ptr)
{
WaitForSingleObject (wait, INFINITE);

overlapped.Offset = write_offset & 0xffffffff;
overlapped.OffsetHigh = write_offset >> 32;
overlapped.hEvent = wait;

WriteFile (f5, current_buffer, write_ptr, 0, &overlapped);
WaitForSingleObject (wait, INFINITE);
write_ptr = 0;
}

CloseHandle (f5);

QueryPerformanceCounter (&f5_end);

CloseHandle (wait);

LARGE_INTEGER
freq;

QueryPerformanceFrequency (&freq);

printf ("  fwrites without buffering = %dms\n", (1000 * (f1_end.QuadPart - f1_start.QuadPart)) / freq.QuadPart);
printf ("     fwrites with buffering = %dms\n", (1000 * (f2_end.QuadPart - f2_start.QuadPart)) / freq.QuadPart);
printf ("    Win32 without buffering = %dms\n", (1000 * (f3_end.QuadPart - f3_start.QuadPart)) / freq.QuadPart);
printf ("       Win32 with buffering = %dms\n", (1000 * (f4_end.QuadPart - f4_start.QuadPart)) / freq.QuadPart);
printf ("Win32 with double buffering = %dms\n", (1000 * (f5_end.QuadPart - f5_start.QuadPart)) / freq.QuadPart);
}
2

В первую очередь: маленькие буквы () являются медленнее, потому что каждый fwrite должен проверить правильность своих параметров, сделать эквивалент flockfile (), возможно fflush (), добавить данные, вернуть успех: эти накладные расходы складываются — не так много, как крошечные вызовы для записи (2) ), но это все же заметно.

Доказательство:

#include <stdio.h>
#include <stdlib.h>

static void w(const void *buf, size_t nbytes)
{
size_t n;
if(!nbytes)
return;
n = fwrite(buf, 1, nbytes, stdout);
if(n >= nbytes)
return;
if(!n) {
perror("stdout");
exit(111);
}
w(buf+n, nbytes-n);
}

/* Usage: time $0 <$bigfile >/dev/null */
int main(int argc, char *argv[])
{
char buf[32*1024];
size_t sz;

sz = atoi(argv[1]);
if(sz > sizeof(buf))
return 111;
if(sz == 0)
sz = sizeof(buf);
for(;;) {
size_t r = fread(buf, 1, sz, stdin);
if(r < 1)
break;
w(buf, r);
}
return 0;
}

При этом вы можете сделать то, что предложили многие комментаторы, то есть добавить собственную буферизацию перед fwrite: это очень тривиальный код, но вы должны проверить, действительно ли он приносит вам какую-то пользу.

Если вы не хотите накатывать свои собственные, вы можете использовать, например, интерфейс буфера в skalibs, но вы, вероятно, займет больше времени, чтобы прочитать документы, чем написать это самостоятельно (imho).

0

Смысл слоя FILE * в stdio заключается в том, что он выполняет буферизацию для вас. Это избавляет вас от системных вызовов. Как отметили другие, одна проблема, которая все еще может быть проблемой, — это издержки библиотечного вызова, которые значительно меньше. Другая вещь, которая может вас укусить, — это одновременная запись во множество разных мест на диске. (Диски вращаются, и голова берет приблизительное расстояние 8 мс, чтобы добраться до нужного места для случайной записи.)

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

Если проблема в том, что у вас много записей, разбросанных по всему диску, попробуйте увеличить размеры буфера с помощью setvbuf (). Попробуйте число около 4 МБ на файл, если можете.

-1

Должно быть легко свернуть свой собственный буфер. но, к счастью, стандарт с ++ имеет то, что вы просите.
Просто используйте std :: ofstream:

//open and init
char mybuffer [1024];
std::ofstream filestr("yourfile");
filestr.rdbuf()->pubsetbuf(mybuffer,1024);
// write your data
filestr.write(data,datasize);

Отредактировано: ошибка, использование ofstream, а не fstream, так как из стандартного буфера ведьмы не ясно (вход или выход?)

-1
По вопросам рекламы [email protected]