|
Reading
data efficiently from Hard Disk and DVD units is vital for video games
and one of the more important problems to solve in the next generation
of games. While we are getting 20x performance in processing power and
memory size, we are only getting 4x performance improvement in data
devices (dvd for consoles).
I describe in this article how to efficiently read raw data from disk (hdd, dvd) oriented towards streaming files in a realtime
application (although the concepts are useful in other areas). The
platform used is Win32, but all the topics covered could be easily
ported to other platforms.
I
have included a project for Visual Studio 2005 with all the code
described here and all the framework to test the different techniques.
You can download it here. The machine where I have done the tests is a 3.2GHz Pentium 4. The devices used for testing are:
Windows
(and all the Operating Systems in general) uses part of the physical
memory (not being used by processes) for file caching (you can view how
much memory is being used for the File System Cache in the Task
Manager). To avoid Windows caching my test files I implemented a
function that flushes the cache reading big files before measuring
times. The tests were executed 10 times, lasting each one several
minutes.
So let’s start the travel… Objective: having a 100MB file in the CPU memory as fast as possible.
1. The Standard and Portable way
The
first option is using portable code from the standard C library. All we
know the benefits of portability. So we allocate a buffer and fread() the file.
FILE *fp = fopen(FileName, “rb”);
fread(&g_buffer[0], 1, FileSize, fp);
fclose(fp);
|
Stats
|
Min (MB/s)
|
Max (MB/s)
|
Average (MB/s)
|
|
HDD
|
47.847
|
48.828
|
48.527
|
|
DVD
|
2.381
|
2.386
|
2.383
|
2. The Win32 Native way
Trying to improve the native approach we go for the Win32 native file functions: CreateFile and ReadFile
HANDLE hFile = CreateFile(FileName, GENERIC_READ, 0, 0, OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, 0);
DWORD dwNumberOfBytesRead = 0;
ReadFile(hFile, &g_buffer[0], FileSize, &dwNumberOfBytesRead, 0);
CloseHandle(hFile);
|
Stats
|
Min (MB/s)
|
Max (MB/s)
|
Average (MB/s)
|
|
HDD
|
47.483
|
48.852
|
48.497
|
|
DVD
|
2.383
|
2.390
|
2.386
|
Nearly
the same performance. FILE_FLAG_SEQUENTIAL_SCAN is used to direct the
Cache Manager to access the file sequentially. It is recommended to use
it when reading large files with sequential access. I though that the
FILE_FLAG_SEQUENTIAL_SCAN hint would give a better improvement than
this but obviously the fread implementation in Win32 is doing a good
job.
3. File Memory Mapping
Our
next approach is trying memory mapped files where the system reads the
data from disk on demand using the same mechanism that is used in
Virtual Memory.
HANDLE hFile = CreateFile(FileName, GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, 0);
HANDLE hFileMapping = CreateFileMapping(hFile, 0, PAGE_READONLY, 0, FileSize, 0);
int iPos = 0;
const unsigned int BlockSize = 128 * 1024;
while(iPos < FileSize)
{
int iLeft = FileSize - iPos;
int iBytesToRead = iLeft > BlockSize ? BlockSize: iLeft;
void *rawBuffer = MapViewOfFile(hFileMapping, FILE_MAP_READ, 0, iPos, iBytesToRead);
memcpy(&g_buffer[iPos], rawBuffer, iBytesToRead);
UnmapViewOfFile(rawBuffer);
iPos += iBytesToRead;
}
CloseHandle(hFileMapping);
CloseHandle(hFile);
|
Stats
|
Min (MB/s)
|
Max (MB/s)
|
Average (MB/s)
|
|
HDD
|
45.830
|
48.828
|
48.190
|
|
DVD
|
2.524
|
2.528
|
2.526
|
We are getting a significant improvement when reading from the DVD. Reading from Hard Disk is nearly the same performance.
|