When you read or write large files, valuable file system cache from other applications is being swapped out. This is not wanted if you know that your files are not going to be accessed again afterwards.
As a result, most people have experienced copying a large file where after all applications feel slow and unresponsive for a period of time until they have recached. Especially on servers that need low latency, this can be a really large problem.
Start a trend and use this wrapper to improve the all-over responsiveness of Windows
The only method to prevent swapping out cache is to open files with the
FILE_FLAG_NO_BUFFERING flag. This, however, requires disk I/O requests to have sizes divisible by sector size (512 to 4096 bytes), which would require large rewrites of most applications that rely on being able to request different sizes.
This project contains a drop-in wrapper that offers the
CloseHandle_NB() functions that take care of queuing and adjusting the file size when closing a file opened for writing.
Note that the
FILE_FLAG_SEQUENTIAL_SCAN flags would not solve the above mentioned problem; while data is committed immediately, a cached copy is still kept in memory.
To illustrate the problem, start your Task Manager (the one at Ctrl + Alt + Del) and watch the Cached value in the Physical Memory area. Before our experiments, we need to invalidate as much cache as possible. Unfortunately, this is not easy on Windows. While the famous sfc /purgecache command commits all cache, it does not invalidate it. Do the following:
- Disable the ReadyBoost, SuperFetch, Readydrive, and SearchIndexer services. Especially SuperFetch is important because it fills up the cache at boot.
- Restart the computer. Simply logging off and on does not invalidate the cache and is not enough.
You should now see something like the image at the top of the article - the cache value is 14 MB, which is low and good.
Now, load the io.sln Visual Studio 2008 project. You will see the following lines in demo.c:
_TCHAR large_file = _T("d:\\database.mdf");
large_file point to a file which is larger than your physical memory, leave the comment at the
#define NORMAL flag as it is, and run the program. It reads the file into memory in chunks. Observe that the Cached value does not increase while running.
Now, uncomment the
#define NORMAL flag and run it again. Observe that the Cached value increases. The same would happen if you had copied files on the command line or simply used the computer normally for a period of time.
Using the Code
#include unbuf.h and unbuf.c in your project and append
_UB to the function names of
Do this for file accesses that have a significant amount of data and that you know are not accessed later by the user or by the application. This wrapper has certain limitations:
- Files cannot be opened with both
GENERIC_WRITE at the same time.
lpOverlapped parameter to
CreateFile() is ignored.
- Seeking (
SetFilePointer()) is not yet supported.
Last but not least, it may not mimic the original functions perfectly in detail yet which could make it fail in certain applications. Please give it a good testing and let me know any compatibility issues or improvements that you may find. I will follow this forum closely the next weeks, to follow up.
- 12-Jan-2010: First release.