У меня есть программа, которая сохраняет много больших файлов> 1 ГБ, используя fwrite
Он работает нормально, но, к сожалению, из-за характера данных каждый вызов fwrite
пишет только 1-4 байта. в результате запись может занять более часа, причем большая часть этого времени, по-видимому, связана с издержками системного вызова (или, по крайней мере, в библиотечной функции fwrite). У меня похожая проблема с fread
,
Кто-нибудь знает о каких-либо существующих функциях / библиотеках, которые будут буферизовать эти записи и чтения с помощью встроенной функции, или это другой ролик самостоятельно?
Прежде всего, fwrite()
это библиотека, а не системный вызов. Во-вторых, он уже буферизует данные.
Возможно, вы захотите поэкспериментировать с увеличением размера буфера. Это делается с помощью setvbuf()
. В моей системе это только немного помогает, но YMMV.
Если setvbuf()
не помогает, вы можете сделать свою собственную буферизацию и только вызвать fwrite()
как только вы накопили достаточно данных. Это требует больше работы, но почти наверняка ускорит процесс записи, поскольку ваша собственная буферизация может быть значительно упрощена. fwrite()
«S.
редактировать: Если кто-то скажет вам, что это просто число fwrite()
звонки, которые являются проблемой, требуют, чтобы увидеть доказательства. А еще лучше, сделайте свои собственные тесты производительности. На моем компьютере 500 000 000 двухбайтовых записей с использованием fwrite()
займет 11 секунд. Это соответствует пропускной способности около 90 МБ / с.
И последнее, но не менее важное: огромное расхождение между 11 секундами в моем тесте и одним часом, указанным в вашем вопросе, указывает на возможность того, что в вашем коде происходит что-то еще, что приводит к очень низкой производительности.
ваша проблема не в буферизации для 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
ОК, это было интересно. Я думал, что напишу какой-то реальный код, чтобы увидеть, какова была скорость. И вот оно. Скомпилировано с использованием C ++ DevStudio 2010 Express. Здесь довольно много кода. Это раз 5 способов записи данных:
Пожалуйста, проверьте, что я не сделал что-то немного глупо с любым из вышеперечисленного.
Программа использует QueryPerformanceCounter для синхронизации кода и заканчивает синхронизацию после закрытия файла, чтобы попытаться включить любые ожидающие данные из внутренней буферизации.
Результаты на моей машине (старая коробка WinXP SP3): —
Вы можете получить разные результаты в зависимости от ваших настроек.
Не стесняйтесь редактировать и улучшать код.
#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);
}
В первую очередь: маленькие буквы () являются медленнее, потому что каждый 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).
Смысл слоя FILE * в stdio заключается в том, что он выполняет буферизацию для вас. Это избавляет вас от системных вызовов. Как отметили другие, одна проблема, которая все еще может быть проблемой, — это издержки библиотечного вызова, которые значительно меньше. Другая вещь, которая может вас укусить, — это одновременная запись во множество разных мест на диске. (Диски вращаются, и голова берет приблизительное расстояние 8 мс, чтобы добраться до нужного места для случайной записи.)
Если вы решите, что проблема связана с затратами на библиотечные вызовы, я бы порекомендовал свернуть свою собственную тривиальную буферизацию с использованием вектора и периодически сбрасывать вектор в файлы.
Если проблема в том, что у вас много записей, разбросанных по всему диску, попробуйте увеличить размеры буфера с помощью setvbuf (). Попробуйте число около 4 МБ на файл, если можете.
Должно быть легко свернуть свой собственный буфер. но, к счастью, стандарт с ++ имеет то, что вы просите.
Просто используйте 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, так как из стандартного буфера ведьмы не ясно (вход или выход?)