对大量小写入的fwrite效率

我有一个程序,使用fwrite保存了大于1GB的大文件它工作正常,但不幸的是,由于数据的性质,每个调用fwrite的数据只能写入1-4字节。 结果写入可能花费一个多小时,大部分时间看起来是由于系统调用的开销(或者至less在fwrite的库函数中)造成的。 我和fread有类似的问题。

有没有人知道任何现有的/库函数将缓冲这些写入和读取一个内联函数,或者这是另一个滚动你自己?

首先, fwrite()是一个库,而不是系统调用。 其次,它已经缓冲了数据。

您可能想要尝试增加缓冲区的大小。 这是通过使用setvbuf()来完成的。 在我的系统上,这只能帮助一点点,但是YMMV。

如果setvbuf()没有帮助,你可以做自己的缓冲,只有在你累积了足够的数据后才调用fwrite() 。 这涉及到更多的工作,但几乎肯定会加快写作速度,因为您自己的缓冲可以做得比fwrite()更轻量级。

编辑:如果有人告诉你,这是数量庞大的fwrite()调用是问题,要求看证据。 更好的是,做你自己的性能测试。 在我的电脑上,使用fwrite() 5亿次双字节写入需要11秒。 这相当于约90MB / s的吞吐量。

最后但并非最不重要的是,在我的测试中11秒和在您的问题中提到的1小时之间的巨大差异暗示了您的代码中还有其他事情正在导致性能很差的可能性。

你的问题不是fwrite()的缓冲区,而是使用少量数据调用库的总开销。 如果您只写入1MB的数据,则会进行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种方式写出数据:

  • 天真地叫fwrite
  • 使用缓冲区,并使用更大的缓冲区进行更少的fwrite调用
  • 天真地使用Win32 API
  • 使用缓冲区并使用更大的缓冲区对Win32进行更少的调用
  • 使用Win32,但双缓冲输出和使用异步写入

请检查我没有做任何有点愚蠢的任何上述。

该程序使用QueryPerformanceCounter对代码进行计时,并在文件关闭后结束计时,试图包含任何待处理的内部缓冲数据。

我的机器上的结果(旧的WinXP SP3盒): –

  • 自己写fwrite通常是最快的,虽然缓存版本有时可以打败它,如果你得到的大小和迭代恰到好处。
  • 天真的Win32显着慢
  • 缓冲Win32加倍速度,但它仍然很容易被fwrite击败
  • 异步写入并不比缓冲版本好得多。 也许有人可以检查我的代码,并确保我没有做一些愚蠢的事情,因为我从来没有真正使用过异步IO。

根据您的设置,您可能会得到不同的结果。

随意编辑和改进代码。

  #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 // asynchronousous 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); } 

首先也是最重要的是:小的fwrites()速度较慢,因为每个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)。

stdio中FILE *层的要点是它为你做缓冲。 这可以避免系统调用开销。 正如其他人所指出的那样,还有一个问题是图书馆电话费用相对较小。 另一件可能咬你的事情是同时在磁盘上写入很多不同的位置。 (磁盘旋转,头部需要8毫秒的时间才能到达正确的地方进行随机写入。)

如果你确定库调用的开销是问题,我建议使用向量来滚动你自己的平凡的缓冲区,并周期性地将向量清理到文件中。

如果问题是你有大量的写入散布在整个磁盘上,尝试使用setvbuf()来增加缓冲区大小。 如果可以,请尝试每个文件大约4MB的数字。

应该很容易推出自己的缓冲区。 幸运的是标准的c ++有你所要求的。 只要使用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,因为从标准的巫婆缓冲区不清楚它是(输入还是输出?)