私は1GBを超える多くの大きなファイルを保存するプログラムを持っていfwrite
ますfwrite
. その結果、書き込みには 1 時間以上かかる場合があります。この時間のほとんどは、syscall のオーバーヘッド (または少なくとも fwrite のライブラリ関数) によるものと思われます。にも同様の問題がありfread
ます。
これらの書き込みと読み取りをインライン関数でバッファリングする既存の/ライブラリ関数を知っている人はいますか?それとも、これはあなた自身の別のロールですか?
私は1GBを超える多くの大きなファイルを保存するプログラムを持っていfwrite
ますfwrite
. その結果、書き込みには 1 時間以上かかる場合があります。この時間のほとんどは、syscall のオーバーヘッド (または少なくとも fwrite のライブラリ関数) によるものと思われます。にも同様の問題がありfread
ます。
これらの書き込みと読み取りをインライン関数でバッファリングする既存の/ライブラリ関数を知っている人はいますか?それとも、これはあなた自身の別のロールですか?
わかりました、まあ、それは面白かったです。実際のコードを書いて速度を確認しようと思いました。そして、ここにあります。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);
}
First and foremost: small fwrites() are slower, because each fwrite has to test the validity of its parameters, do the equivalent of flockfile(), possibly fflush(), append the data, return success: this overhead adds up -- not so much as tiny calls to write(2), but it's still noticeable.
Proof:
#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;
}
That being said, you could do what many commenters suggested, ie add your own buffering before fwrite: it's very trivial code, but you should test if it really gives you any benefit.
If you don't want to roll your own, you can use eg the buffer interface in skalibs, but you'll probably take longer to read the docs than to write it yourself (imho).
stdio の FILE * レイヤーのポイントは、バッファリングを行うことです。これにより、システム コールのオーバーヘッドを節約できます。他の人が指摘したように、まだ問題になる可能性があるのは、かなり小さいライブラリ呼び出しのオーバーヘッドです。もう 1 つの問題は、ディスク上のさまざまな場所に同時に書き込むことです。(ディスクが回転し、ヘッドがランダム書き込みの適切な場所に到達するまでにおよそ 8 ミリ秒かかります。)
ライブラリ呼び出しのオーバーヘッドが問題であると判断した場合は、ベクターを使用して独自の単純なバッファリングを展開し、定期的にベクターをファイルにフラッシュすることをお勧めします。
大量の書き込みがディスク全体に分散していることが問題である場合は、setvbuf() を使用してバッファ サイズを大きくしてみてください。可能であれば、1 ファイルあたり約 4MB の数値を試してください。