Systematic Gaming

September 1, 2008

Load Times: Reading Files

Filed under: file management, game programming — Tags: , — systematicgaming @ 1:23 am

Load times are a bane of console games, every game has load screens and nobody likes staring at them. Some games try to hide load times under cut-scenes or flashy effects. The elevator scenes in Mass Effect are a clever trick and are better than a “Loading” icon, but still get repetitive and players do feel like they’re standing still. Really the only solution is to make the load time as short as possible.

With console games content getting larger and larger and disc speeds not keeping pace, load times are aren’t getting any shorter. To get the shortest load times possible we need to handle file loading intelligently.

Hardware

First let’s take a look as the actual hardware we’re loading with to help us understand what takes so long, and why load times seem worse on consoles than on PCs.

So we clearly see the huge difference between the hard drive and optical media read rates. This is one reason PC games tend to load faster. I should mention that the ranges above for the optical drives is based of the published maximum rate – which is rarely reached in practice. The ranges are because optical media (and hard drives) read at different rates from different parts of the disc.

Also interesting is that BlueRay is slower than a common 12x DVD and is a constant linear velocity (CLV) drive – meaning the disc head moves at the same speed which is why it’s read rate will tend to vary less. Most disc drives are constant angular velocity (CAV) which means the disc turns at the same speed, so the head will actually read faster from the outside where the disc surface is moving faster.

Again we see a very large difference between optical media and hard drives. Almost 100 times worse! Since drives seek whenever we read a file notice how simply seeking to 5 different files will take almost 1 second on optical media. The large range in seek times is because the time to seek depends on how far the drive head needs to move, smaller seek distances means less time spent seeking.

One interesting way to look at load times on consoles is to compare how long is would take to fill all the memory from the disc.

This chart show the time to read 512MB (the lower range is the average read time, the upper range is the maximum rate). This chart gives you an picture of the best possible time to read all 512MB. Again you see that the hard drive wins hands down. If you take the Xbox 360 with it’s 12x DVD drive and approximate 512MB of memory you can see that this range isn’t that far off from many game’s load times.

Reading Files

To get a feel for what happens when we read a single file let’s look at a common way for people to load files in detail.

FILE *f = fopen("file", "rb");
fread( &numObjects, sizeof(int), 1, f );
fread( &extraDataSize, sizeof(int), 1, f );
fread( &myArray, sizeof(MyObjects), numObjects, f );
fread( &extraData, 1, extraDataSize, f );
fclose( f );

There are a number things not so nice about this sample, but lets look at how the disc and CPU coordinate to read this file.

I’ve left out what happens in open and closing because it’s hard to say – an OS like windows may have to open and read many directories to open the file, which would be very expensive, however another embedded OS may just return a handle without accessing the drive. So don’t assume open is a fast operation, it can be very expensive at times.

The main thing to notice is all those blue boxes are when the physical drive is actually active. During this time our CPU is idle, we’re just waiting for the disc to finish. Also, between reads the drive isn’t doing anything either. This means each separate file read is wasting CPU and drive resources. Clearly this is an inefficient way of working and we’re wasting a lot of time and resources doing this.

One improvement is reduce the number of file operations by loading the whole file at once and extract the data we want from the raw buffer, like so

FILE *f = fopen("file", "rb");

// get size of file
fseek( f, 0, POS_END );
long fileSize = ftell( f );

// read whole file
fseek( f, 0, POS_BEGIN );
int8_t *fileBuffer = new int8_t[ fileSize ];
fread( fileBuffer, 1, fileSize, f );

fclose(f);

// parse file
numObjects = (int)fileBuffer;
extraDataSize = (int)(fileBuffer + 4);
myArray = (MyObject *)(fileBuffer + 8);
extraData = (void *)(fileBuffer + 8 + numObjects * sizeof(MyObject) );

We’ve reduced the amount of stalls between the CPU and disc, but not eliminated them. We’ve also made our loading a bit messier, since we have to parse the data in a fairly ugly manner. We can improve upon this technique by using “load-and-go” data files.

Load-and-Go

Load-and-go data files (sometimes called memory dumped or image files) are file data that can be loaded and used right away, no parsing required. The idea is very simple, write the data file in a format that matches your in-memory data structures exactly. Then all you have to is point to the start of the file data. This means that you have practically zero runtime processing. However there are restrictions

  • Data must be in the proper format, such as correct alignment, padding and endian format. This means that you may need per-platform data files for multi-platform games.
  • Simple data structures with no or few pointers are best. Pointers will need to be fixed-up post-load, so using indices and offsets are usually a better choice than pointers.
  • Code and data must be in-sync or else data files may not match the game’s data structure layout. Adding a version to data files can help, and older versions may be converted, or at least a warning can be displayed.

Taking these restrictions into account, load-and-go data formats are key to efficient load times and should be used where possible.

One at a Time

So we’ve seen how we can reduce load times by minimizing the number of reads we make to a file, with one single read being ideal. But we still have stalls where the disc and CPU are waiting on each other, wasting a lot of time. Clearly we have to do better, and we can.

The next installment about load times we’ll look at asynchronous file loading to really speed things up.

Advertisements

Leave a Comment »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Blog at WordPress.com.

%d bloggers like this: