[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

Re: [pygame] Load Graphics 30x faster than pygame.image.load



On 1/20/07, Kamilche <kamilche@xxxxxxxxxxxx> wrote:
Assume the string is read from disk, instead of doing a pygame image
load. The speed bump truly surprised me!

The problem with switching to that approach (loading your images as
encompressed strings off of the disk) is that disk is much much slower
than CPU. Todays great transfer rates for disks are like 83MB/s (if
you get faster than that, it's out of the disk cache) whil memory
buses are like 533MB/s, and the perf difference is much much worse
(like 5-15MB/s) if the disk head has to seek (which it often does)

It's hard to really see that effect in testing as well - if you ran on
an NT os (like winXP), then if you try and load the same file off of
disk it usually comes from a memory cache which grows as big as it
needs up to all your free memory. So if you time how long it is to
load the same image 100 times or something, you threw out the impact
of disk IO.

Additionally, trying to save your images uncompressed makes the disk
stuff much worse, cause compression makes them significantly smaller
on disk. And the disk is the slowest part.

To validate all this, I wrote a test script that takes an image, and
saves enough copies of itself to entirely fill the RAM on my machine
(so that it couldn't all be cached, and disk IO would have to happen)
both as the original compressed and as a flattened "tostring" version.
Then it times loading all that stuff. It also times loading the same
image over and over for comparison.

below are the results of the test on my machine:
-------------
FINAL RESULTS
-------------
cached .png file loaded at 1.0054 seconds per MB or 0.0312 seconds per file
cached flat file loaded at 0.0323 seconds per MB or 0.0081 seconds per file
flat files are 8.1x the size of the optimized .png
.png files loaded at 1.3262 seconds per MB or 0.0412 seconds per file
flat files loaded at 0.3432 seconds per MB or 0.0858 seconds per file

So on my box, it's more than twice as fast to load compressed .pngs
with image.load than uncompressed files with image.fromstring, even
though the cpu and memory work to load compressed files is about 30
times longer than just making the image from a string (image.load does
appear to be a real dog...)
I think the reason the flat file loading lost was because the
increased size of the disk IO (8x as large files) made the flat file
loading lose out (the disk load took 10x the time of making the image
from the string)

The other thing that is interesting about this test to me, is how much
faster the disk reads were with the larger flat files (it loaded 3x as
many bytes in the same time). So if you want your loading to go
faster, large files are better. So packing your data in some big
data.dat is good for loading time from disk (cause then the OS can
load and cache large blocks at a time)

I would imagine that the flat file stuff could be faster on many
computers, but there is no way I can see it being anywhere near 32x in
practice except if you are loading the same images over and over
again.

---
script and fle attached

Attachment: TestFile.png
Description: PNG image

import pygame
import os
import stat
import time
import shutil
import platform

# don't use a cpu time func if we can help it - will be bad!
platform_string = platform.platform(aliased = True, terse = True)
if 'Windows' in platform_string:
    timer_func = time.clock
else:
    timer_func = time.time


def GetFileSize(filepath):
    return os.stat(filepath)[stat.ST_SIZE]/(1024.0*1024.0)

###############################################################33
test_file_name = "TestFile.png"
test_dir = "TestDir/"
if not os.path.isdir(test_dir):
    os.mkdir(test_dir)

# some initial set up
image_format = "TestImage%06d.png"
original_image = pygame.image.load(test_file_name)
pngsize = GetFileSize(test_file_name)
desired_size = 512

# create a flattened version
flat_format = "TestFlat%06d.dat"
image_as_string = pygame.image.tostring(original_image, 'RGBA')
image_size = original_image.get_size()
first_flat_file_name = test_dir + flat_format % 0
first_flat_file = file(first_flat_file_name, "wb")
first_flat_file.write(image_as_string)
first_flat_file.close()
flatsize = GetFileSize(first_flat_file_name)

print "sleeping to let disk stuff settle..."
time.sleep(3)

times_reload_same_image = 1000
print "timing loading the same .png file %d times" % times_reload_same_image

start_time = timer_func()
for load_index in xrange(times_reload_same_image):
    image = pygame.image.load(test_dir + (image_format % 0))
    if (load_index % 100) == 99:
        print "  loaded %d" % (load_index + 1)
end_time = timer_func()
image_reload_time = end_time - start_time

print "sleeping to let disk stuff settle..."
time.sleep(3)

print "timing loading the same flat file %d times" % times_reload_same_image
start_time = timer_func()
for load_index in xrange(times_reload_same_image):
    flat_file = file(test_dir + flat_format % 0, "rb")
    flat_data = flat_file.read()
    flat_file.close()
    image = pygame.image.fromstring(flat_data, image_size, "RGBA")
    if (load_index % 100) == 99:
        print "  loaded %d" % (load_index + 1)
end_time = timer_func()
flat_reload_time = end_time - start_time

# set up enough disk data to exhaust the cache
num_image_copies = int(desired_size/pngsize)
print "Creating %d image copies..." % num_image_copies
for image_index in xrange(num_image_copies):
    shutil.copyfile(test_file_name, test_dir + (image_format % image_index))
    if (image_index % 100) == 99:
        print "  created %d" % (image_index + 1)
    
num_flat_copies = int(desired_size/flatsize)
print "Creating %d flat copies..." % num_flat_copies
for image_index in xrange(1, num_flat_copies):
    shutil.copyfile(first_flat_file_name, test_dir + flat_format % image_index)
    if (image_index % 100) == 99:
        print "  created %d" % (image_index + 1)

# now do the serious timings...
print "sleeping to let disk stuff settle..."
time.sleep(10)

print "timing loading %dMB of .png images" % desired_size
start_time = timer_func()
for image_index in xrange(num_image_copies):
    image = pygame.image.load(test_dir + (image_format % image_index))
    if (image_index % 100) == 99:
        print "  loaded %d" % (image_index + 1)
end_time = timer_func()
image_load_time = end_time - start_time

print "sleeping to let disk stuff settle..."
time.sleep(10)

print "timing loading %dMB of flat images" % desired_size
start_time = timer_func()
for image_index in xrange(num_flat_copies):
    flat_file = file(test_dir + flat_format % image_index, "rb")
    flat_data = flat_file.read()
    flat_file.close()
    image = pygame.image.fromstring(flat_data, image_size, "RGBA")
    if (image_index % 100) == 99:
        print "  loaded %d" % (image_index + 1)
end_time = timer_func()
flat_load_time = end_time - start_time

print "-------------"
print "FINAL RESULTS"
print "-------------"
print "cached .png file loaded at %.04f seconds per MB or %.04f seconds per file" % (image_reload_time/(pngsize*times_reload_same_image), image_reload_time/times_reload_same_image)
print "cached flat file loaded at %.04f seconds per MB or %.04f seconds per file" % (flat_reload_time/(flatsize*times_reload_same_image), flat_reload_time/times_reload_same_image)
print "flat files are %.1fx the size of the optimized .png" % (flatsize/pngsize)
print ".png files loaded at %.04f seconds per MB or %.04f seconds per file" % (image_load_time/(num_image_copies*pngsize), image_load_time/num_image_copies)
print "flat files loaded at %.04f seconds per MB or %.04f seconds per file" % (flat_load_time/(num_flat_copies*flatsize), flat_load_time/num_flat_copies)