I’ve been thinking about what we can do about the remaining problems that
users have reported when trying to load large maps. When loading an image,
there are three things which take time: allocating the original BufferedImage,
decoding the pixel data, and converting the original BufferedImage into a
usable type.
I’ve done some tests to see if we can avoid the conversion step by telling
ImageIO to decode the pixel data directly into the type of BufferedImage we
want to use. That works, but is slower (!) than creating the first
BufferedImage (which will often be TYPE_CUSTOM) and doing the conversion.
I don’t see any way to shave time on this.
Image decoding is something where we can potentially save a lot of time. The
amount of time it takes to decode image data and create a BufferedImage from
it is about 10x more than it takes to create a BufferedImage from raw pixel
data written to disk. This means that if we read images in once using ImageIO
and then wrote them back to a disk cache as (compressed) raw pixel data, we
could load images about 10x faster on all loads after the first one. Since a
disk cache can be persistent, this would mean we could have such savings on
all runs of the module after the first. The tradeoff here is a bit of disk
space for better performance. (Two examples: the raw pixel data for non-piece
images in the new 1805 module is 39MB; for Case Blue, it’s 110MB.) I’m
guessing that disk space is not in short supply for anyone these days; anyway,
it should be much more plentiful than RAM or CPU cycles.
What I have kicking around in my head right now is that we could do the
following:
The first time a module is loaded, load each large image and write the raw
pixel data for each tile out to the image cache. This is something for which
we could show a progress dialog (and I think we could even be pretty accurate
about the progress). I did some timing, and found that for the 1805 module, it
took about 150s to tile all of the map and chart images and write them out to
disk; for Case Blue, it took 452s. This is a one-time cost, and since it’s
something which I think will have a huge impact on load time, I think most
users will find it worthwhile. Having all of the unscaled tiles available
from the disk cache would make it unnecessary to ever load the original image
again—so for modules with large maps, that’s a huge savings, both in load
time and in continued RAM usage. (And it would make the in-memory image cache
more flexible, since it wouldn’t contain anything larger than the tile size.)
Before I put time into the design work for this, I’d like to hear whether
anyone sees problems with this, has suggestions, etc.