Jump to content
  • Advertisement
Sign in to follow this  
Numsgil

Reducing load times

This topic is 3916 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I tried doing a site and google search, but I'm not finding anything. I'm interested in techniques to reduce the time it takes to load (or reload) the assets for a game. Apparently it's not something anyone's talking about. About the only thing I found out for sure so far is that saving assets in a compressed format helps reduce load times, because the CPU can decompress information considerably faster than the hard drive can retrieve it. This got me thinking about a multithreaded setup. One thread loads things from disk as fast as it can, while the other handles decompressing the assets and loading them into the engine. It's all pretty academic for me, I don't have a game with long load times to try anything out on, but I'm still curious. What techniques or experiences have others had reducing load times?

Share this post


Link to post
Share on other sites
Advertisement
You won't find a whole lot on this topic, because most of it is beyond your control at the OS and hardware levels. As long as your data files are in a sane format and can be easily/sequentially read, there isn't much you can do. You want your data in as close to the engine's run-time format, if not exactly, as possible while maintaining easy edit ability. Having to do a lot of text parsing and conversion of data on the CPU can add up to a good bit of time as well.

Compression can speed things up, but depending on the type of compression and how you time/synch everything up, it could be slower. Using container files can help with speed as well, since they are seen by the OS as a single file, which is then cached sequentially as you're reading it. If you place your files in the container sequentially and the file is not too fragmented on the disk, your pull from disk time can drop a little.

Something to also consider, as you mentioned a threaded approach, is to asynchronously load your data. In places where you can, load your data bit by bit in the background before it is needed. You won't get anything amazing as far as speed, but the effect of remaining interactive while loading what you need is better than a slightly reduced load time where everything stops. You should be able to find a bit on this topic, as it's becoming quite popular, especially in console games.

Share this post


Link to post
Share on other sites
The multithreaded method is a real dead end since the loading thread will be doing nothing 99% of the time whilst the decompressing thread will be doing nothing 1% of the time. The best solution is to use asynchronous disk IO if it's available. It works like this:

1. Load first chunk of data, n=1
2. Start asynchronous load of chunk n+1 of data
3. Decompress chunk n of data
4. Wait for chunk n+1 of data to finish loading
5. ++n
6. Goto 2

The above is only efficient for large n, there's little to gain for small n - you will need to profile the loading times to determine if there's an improvement.

Of course, the load times do depend on where the data is coming from and how it's stored. The overhead for file access is in descending order: opening files, seeking, reading. Things get much worse when reading from CD/DVD - opening files and seeking are phenomenonly slow, reading is OK provided is a big sequential read. So, on CD/DVD, having lots of files is really bad, having a packed file that requires seeking is not quite as bad.

Given the above, I have developed a system that can really speed up CD/DVD load times at the cost of slightly larger data files. I have used this to speed up a PS2 load time from minutes to seconds.

The method I developed required three versions of a file IO class. All file IO was done using the class:

class FileIO
{
public:
static FileIO *Open (char *filename);
void Close ();
void Seek (int position, int origin);
void Read (void *dest, int num_bytes);

static void BeginFastRead (char *filename);
static void EndFastRead ();
};

The first version just mapped the first four functions to the OS file routines and the last two were empty. The second version again mapped the first four functions to the OS routines, but this time the read function also wrote the data that was read to an output file as specified by the BeginFastRead function. The final and production version had empty functions for the first three methods and the Read function read from the file specified in the call to BeginFastRead. This leads to fast CD/DVD speeds as the data is stored on the disk in a way the disk can get it really quickly, i.e. one seek and one long sequential read.

An example:

void LoadLevel (char *levelname)
{
FileIO::BeginFastRead (levelname + ".fast");
FileIO *level_file = FileIO::Open (levelname);
ReadLevel (level_file);
level_file->Close ();
for each (Texture in level data)
{
FileIO *texture_file = FileIO::Open (texture_name);
ReadTexture (texture_file);
texture_file->Close ();
}
for each (StaticObject in level data)
{
FileIO *object_file = FileIO::Open (object_name);
ReadTexture (object_file);
object_file->Close ();
}
// load other stuff
FileIO::EndFastRead ();
}

Skizz

Share this post


Link to post
Share on other sites
Quote:
Original post by Skizz

1. Load first chunk of data, n=1
2. Start asynchronous load of chunk n+1 of data
3. Decompress chunk n of data
4. Wait for chunk n+1 of data to finish loading
5. ++n
6. Goto 2

The above is only efficient for large n, there's little to gain for small n - you will need to profile the loading times to determine if there's an improvement.


Yeah, that seems more reasonable than dedicated threads. The CPU manipulation will probably be done way before the next data chunk is in memory. I'll keep it under my hat ;)

Quote:

The first version just mapped the first four functions to the OS file routines and the last two were empty. The second version again mapped the first four functions to the OS routines, but this time the read function also wrote the data that was read to an output file as specified by the BeginFastRead function. The final and production version had empty functions for the first three methods and the Read function read from the file specified in the call to BeginFastRead. This leads to fast CD/DVD speeds as the data is stored on the disk in a way the disk can get it really quickly, i.e. one seek and one long sequential read.


I'm not entirely understanding this. The second version wrote a new file? You mean like it wrote a whole level's worth of data to a single file for later opening as a pre-computation step?

-----------------------------------------
I've played with noise a little before. It seems to me that for textures, which seem to take the most disk space and time to load, you could use noise to "up" the resolution for some textures. Maybe save your brick texture or floor textures as 256x256 and then apply a simple noise to make them work as 512x512 or 1024x1024. For textures details that are inherantly static anyway, like dimples in a brick, this might give pretty good results with appreciable cutdown bulk.

This would also let you shift the resolution up for other textures (if the hardware allowed it). Make the decals that contain text on the walls of the spaceship readable.

The extreme of this, all procedural, doesn't seem to be any faster from what I've seen in the demo scene. I might try a hybrid approach out at some point and see how well it works.

Share this post


Link to post
Share on other sites
the games are a bit old but some of the concepts are interesting.

http://www.drizzle.com/~scottb/gdc/

It discusses the way dungeon siege was made to have no loading screens. Discusses the implications of this from file format and using memory mapped reads all the way up to game implications, viewpoint restrictions and teleportation timing.

Share this post


Link to post
Share on other sites
Quote:
Original post by Daishim
You won't find a whole lot on this topic, because most of it is beyond your control at the OS and hardware levels.


Considering that it's one of the more popular complaints gamers have about pretty much any game, I think it's weird that there isn't more attention paid to it.

If the assets you need to load are fixed, then yeah, you don't have alot of control. However, take the case with something like Civ3 or 4. If I'm playing and save the game, lose a battle, and reload, the game spends alot of time loading things back from disk. Orders of magnitude longer than it took to save. Some games are really quite good with load times, and others aren't.

If you designed your engine (not sure how it would work, but bear with me) around the idea that most of the time the assets you need are already somewhere in memory, you might be able to drastically reduce load times from dying or reloading. You'd need a way to 'tag' each asset in memory and from disk with a hash. When you want to load an asset from disk, maybe check what you already have in memory for the hash.

Of course, if most of your assets don't exist in any form outside of the video card, this might not be practical.

Share this post


Link to post
Share on other sites
Quote:
Original post by Satook
the games are a bit old but some of the concepts are interesting.

http://www.drizzle.com/~scottb/gdc/

It discusses the way dungeon siege was made to have no loading screens. Discusses the implications of this from file format and using memory mapped reads all the way up to game implications, viewpoint restrictions and teleportation timing.


Excellent link, thanks. I'm reading it over now

Share this post


Link to post
Share on other sites
Quote:
Original post by Numsgil
I'm not entirely understanding this. The second version wrote a new file? You mean like it wrote a whole level's worth of data to a single file for later opening as a pre-computation step?

Yes, it does indeed write a new file. The second version effectively compiles the level data from multiple randomly accessed files into a single sequential file. The third version doesn't require any of the source files, just the file created by step 2.

The sharp eyed amongst you will see the downsides to this system.

Skizz

Share this post


Link to post
Share on other sites
Quote:
Original post by Numsgil
However, take the case with something like Civ3 or 4. If I'm playing and save the game, lose a battle, and reload, the game spends alot of time loading things back from disk. Orders of magnitude longer than it took to save.
I'm not sure if this is the case in Civ3, but lots of games [and apps in general, for that matter] save progress or files by taking an in-game snapshot of the current progress, and shipping off the actual save process to another thread that just does it all in the background. This means that the "save time" that the user experiences is often times only the time required to take a snap-shot of what is going on at the time, and create a thread to actually do all the data-saving [you aren't actually waiting for the possible compression steps, all the expensive computations, or the actual disk writes]. This means saves can occur over longer periods of time, and only have to stall the action long enough to gather up the data.

Loading, on the other hand, has to read from the disk, and completely recreate the game before the player can interact with anything [even in the case where actual game resources, like textures or models, aren't reloaded], thus taking a long time in comparison to saving. Plenty of games won't load certain resources unless they actually have to [if a texture was used before, don't delete it only to load it again], but of course there are certain transitions that require loading these sorts of things. Exactly the reason lots of games load into a level that is already running a whole lot faster than loading into the level from a different level.

Oh, and an approach that I've often had some success with regarding this subject. Create a pair of threads, one reading into a buffer and storing how much of the buffer has been filled and the other processing it [stalling any time it hits the point where the other one is still reading data]. Set up your load functions to work in-sequence as much as possible, instead of loading a huge chunk and working on it all at once. It's not likely the best method of doing things, and you don't need to create a pair of threads for every file, instead just two threads for however many files [queue up the files you want opened, so you can process one file while another is loading into memory, so not to constantly spend your time seeking on the HD]. Tell the processor thread to give up it's time slice whenever it bumps into the reader thread [which will happen frequently if you're reading files that need little processing, and never if you're reading processor-heavy files]

Worked pretty well for me, cut my load times down lots, and allowed me to just flag files for opening, and go back to my game while they load in the background.

Share this post


Link to post
Share on other sites
Quote:
Original post by Numsgil
Quote:
Original post by Daishim
You won't find a whole lot on this topic, because most of it is beyond your control at the OS and hardware levels.


Considering that it's one of the more popular complaints gamers have about pretty much any game, I think it's weird that there isn't more attention paid to it.

It's because load time is considered to be the point where performance doesn't really matter. If something can be done at load time instead of in-game, it's moved out to load time. Optimisation of gameplay performance usually takes precedence over load-time performance.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!