I have always thought that WriteFile is more efficient than fwrite, because fwrite calls down to WriteFile internally, but the following test code show me that fwrite is faster
If properly setup, WriteFile()
can be more efficient than fwrite()
. WriteFile()
allows you to fine-tailor the conditions it uses to when performing the IO request you're issuing.
For example, you can bypass the intermediate buffered IO subsystem and pull directly from your data pointer as if it were the intermediate IO buffer, therefore removing a significant middle-man. The setup, however, is somewhat restrictive. Your data pointer must lay on a byte boundary equivalent to the sector size of the volume being written to. No such facility exists with fwrite()
for hopefully obvious reasons. Windows API enthusiasts (circa J. Richter and his brethren) are big fans of twiddling with such usages of WriteFile()
to squeeze every last drop out of their Windows program IO performance.
And if you're wondering why people aren't WriteFile()
love-children I can assure you many people are, but none of them are the least-bit interested in portable code. The ones that are (or just aren't that concerned about it (what was it Knuth said about premature optimization..?), choose standard facilities like fwrite()
.
If you're really interested in the MSVCRT-implementation of fwrite()
and how it does what it does, check out the source. It is shipped with every version of VC++ Standard or greater (perhaps not Express; I've never checked).
Using a tool like Process Monitor (procmon) from Sysinternals, you'll see that the call to fflush()
isn't doing the same thing as FlushFileBuffers(wfile)
(or the FILE_FLAG_WRITE_THROUGH
flag to CreateFile()
).
fwrite()
will write the data to a buffer until that buffer fills, which will cause it to send the data in the buffer to WriteFile()
call. When you call fflush()
all that happens is that the data currently in the buffer is passed to a call to WriteFile()
- fflush()
doesn't call FlushFileBuffers()
:
1:21:32.9391534 AM test.exe 6132 WriteFile C:\temp\file1.txt SUCCESS Offset: 0, Length: 24
1:21:32.9392200 AM test.exe 6132 WriteFile C:\temp\file1.txt SUCCESS Offset: 24, Length: 24
1:21:32.9392340 AM test.exe 6132 WriteFile C:\temp\file1.txt SUCCESS Offset: 48, Length: 24
1:21:32.9392436 AM test.exe 6132 WriteFile C:\temp\file1.txt SUCCESS Offset: 72, Length: 24
1:21:32.9392526 AM test.exe 6132 WriteFile C:\temp\file1.txt SUCCESS Offset: 96, Length: 24
1:21:32.9392623 AM test.exe 6132 WriteFile C:\temp\file1.txt SUCCESS Offset: 120, Length: 24
For comparison, here's an example of a trace from the fwrite()
loop without the fflush()
call:
1:27:28.5675034 AM test.exe 3140 WriteFile C:\temp\file1.txt SUCCESS Offset: 0, Length: 1,024
1:27:28.5676098 AM test.exe 3140 WriteFile C:\temp\file1.txt SUCCESS Offset: 1,024, Length: 1,024
1:27:28.5676399 AM test.exe 3140 WriteFile C:\temp\file1.txt SUCCESS Offset: 2,048, Length: 1,024
1:27:28.5676651 AM test.exe 3140 WriteFile C:\temp\file1.txt SUCCESS Offset: 3,072, Length: 1,024
And here's a snippet of the trace from the WriteFile()
loop (with the FILE_ATTRIBUTE_NORMAL
flag and explicit call to FlushFileBuffers()
- it just makes what's happening easier to see in the trace since the FlushFileBuffers()
call is shown in the trace instead of just showing as a second 4KB WriteFile()
call).
1:21:29.0068503 AM test.exe 6132 WriteFile C:\temp\file2.txt SUCCESS Offset: 0, Length: 24, Priority: Normal
1:21:29.0069197 AM test.exe 6132 FlushBuffersFile C:\temp\file2.txt SUCCESS
1:21:29.0069517 AM test.exe 6132 WriteFile C:\temp\file2.txt SUCCESS Offset: 0, Length: 4,096, I/O Flags: Non-cached, Paging I/O, Synchronous Paging I/O, Priority: Normal
1:21:29.0087574 AM test.exe 6132 WriteFile C:\temp\file2.txt SUCCESS Offset: 24, Length: 24
1:21:29.0087798 AM test.exe 6132 FlushBuffersFile C:\temp\file2.txt SUCCESS
1:21:29.0088087 AM test.exe 6132 WriteFile C:\temp\file2.txt SUCCESS Offset: 0, Length: 4,096, I/O Flags: Non-cached, Paging I/O, Synchronous Paging I/O, Priority: Normal
1:21:29.0102260 AM test.exe 6132 WriteFile C:\temp\file2.txt SUCCESS Offset: 48, Length: 24
1:21:29.0102428 AM test.exe 6132 FlushBuffersFile C:\temp\file2.txt SUCCESS
1:21:29.0102701 AM test.exe 6132 WriteFile C:\temp\file2.txt SUCCESS Offset: 0, Length: 4,096, I/O Flags: Non-cached, Paging I/O, Synchronous Paging I/O, Priority: Normal
1:21:29.0113444 AM test.exe 6132 WriteFile C:\temp\file2.txt SUCCESS Offset: 72, Length: 24
1:21:29.0113602 AM test.exe 6132 FlushBuffersFile C:\temp\file2.txt SUCCESS
1:21:29.0113848 AM test.exe 6132 WriteFile C:\temp\file2.txt SUCCESS Offset: 0, Length: 4,096, I/O Flags: Non-cached, Paging I/O, Synchronous Paging I/O, Priority: Normal
So the reason that your benchmark shows a severe disadvantage to the WriteFile()
loop is simply because you have about a thousand calls to FlushFileBuffers()
that aren't in the fwrite()
loop.