0

Tile rendering is now mostly parallel up to 4 procs.

The initial recursive call for tile generation will spawn up to 3 extra
processes to work on each quadrant. It's not perfect yet since some
quadrants may have more or less work to do, and only 4 total workers are
supported.

Also, it waits for all chunks are finished before it dives into the
tiles, to prevent it from using more resources than requested.
This commit is contained in:
Andrew Brown
2010-09-11 00:12:38 -04:00
parent 1e296e858a
commit f9783d7a20
2 changed files with 78 additions and 15 deletions

View File

@@ -35,8 +35,11 @@ def main():
# Translate chunks from diagonal coordinate system # Translate chunks from diagonal coordinate system
mincol, maxcol, minrow, maxrow, chunks = world.convert_coords(all_chunks) mincol, maxcol, minrow, maxrow, chunks = world.convert_coords(all_chunks)
print "processing chunks in background" print "Rendering chunks"
results = world.render_chunks_async(chunks, False, options.procs) results = world.render_chunks_async(chunks, False, options.procs)
for i, (col, row, filename) in enumerate(chunks):
results[col, row].wait()
print "{0}/{1} chunks rendered".format(i, len(chunks))
print "Writing out html file" print "Writing out html file"
if not os.path.exists(destdir): if not os.path.exists(destdir):
@@ -49,7 +52,7 @@ def main():
tiledir = os.path.join(destdir, "tiles") tiledir = os.path.join(destdir, "tiles")
if not os.path.exists(tiledir): if not os.path.exists(tiledir):
os.mkdir(tiledir) os.mkdir(tiledir)
world.generate_quadtree(results, mincol, maxcol, minrow, maxrow, tiledir) world.generate_quadtree(results, mincol, maxcol, minrow, maxrow, tiledir, options.procs)
print "DONE" print "DONE"

View File

@@ -328,7 +328,7 @@ def get_quadtree_depth(colstart, colend, rowstart, rowend):
return p return p
def generate_quadtree(chunkmap, colstart, colend, rowstart, rowend, prefix): def generate_quadtree(chunkmap, colstart, colend, rowstart, rowend, prefix, procs):
"""Base call for quadtree_recurse. This sets up the recursion and generates """Base call for quadtree_recurse. This sets up the recursion and generates
a quadtree given a chunkmap and the ranges. a quadtree given a chunkmap and the ranges.
@@ -347,9 +347,11 @@ def generate_quadtree(chunkmap, colstart, colend, rowstart, rowend, prefix):
#print " power is", p #print " power is", p
#print " new bounds: {0},{1} {2},{3}".format(colstart, colend, rowstart, rowend) #print " new bounds: {0},{1} {2},{3}".format(colstart, colend, rowstart, rowend)
quadtree_recurse(chunkmap, colstart, colend, rowstart, rowend, prefix, "base") # procs is -1 here since the main process always runs as well, only spawn
# procs-1 /new/ processes
quadtree_recurse(chunkmap, colstart, colend, rowstart, rowend, prefix, "base", procs-1)
def quadtree_recurse(chunkmap, colstart, colend, rowstart, rowend, prefix, quadrant): def quadtree_recurse(chunkmap, colstart, colend, rowstart, rowend, prefix, quadrant, procs):
"""Recursive method that generates a quadtree. """Recursive method that generates a quadtree.
A single call generates, saves, and returns an image with the range A single call generates, saves, and returns an image with the range
specified by colstart,colend,rowstart, and rowend. specified by colstart,colend,rowstart, and rowend.
@@ -478,18 +480,47 @@ def quadtree_recurse(chunkmap, colstart, colend, rowstart, rowend, prefix, quadr
hasher = hashlib.md5() hasher = hashlib.md5()
# Recurse to generate each quadrant of images # Recurse to generate each quadrant of images
quad0file, hash0 = quadtree_recurse(chunkmap, # Quadrent 1:
colstart, colmid, rowstart, rowmid, if procs > 0:
newprefix, "0") Procobj = ReturnableProcess
quad1file, hash1 = quadtree_recurse(chunkmap, procs -= 1
colmid, colend, rowstart, rowmid, else:
newprefix, "1") Procobj = FakeProcess
quad2file, hash2 = quadtree_recurse(chunkmap,
colstart, colmid, rowmid, rowend, quad0result = Procobj(target=quadtree_recurse,
newprefix, "2") args=(chunkmap, colstart, colmid, rowstart, rowmid, newprefix, "0", 0)
)
quad0result.start()
if procs > 0:
Procobj = ReturnableProcess
procs -= 1
else:
Procobj = FakeProcess
quad1result = Procobj(target=quadtree_recurse,
args=(chunkmap, colmid, colend, rowstart, rowmid, newprefix, "1", 0)
)
quad1result.start()
if procs > 0:
Procobj = ReturnableProcess
procs -= 1
else:
Procobj = FakeProcess
quad2result = Procobj(target=quadtree_recurse,
args=(chunkmap, colstart, colmid, rowmid, rowend, newprefix, "2", 0)
)
quad2result.start()
# 3rd quadrent always runs in this process, no need to spawn a new one
# since we're just going to turn around and wait for it.
quad3file, hash3 = quadtree_recurse(chunkmap, quad3file, hash3 = quadtree_recurse(chunkmap,
colmid, colend, rowmid, rowend, colmid, colend, rowmid, rowend,
newprefix, "3") newprefix, "3", 0)
quad0file, hash0 = quad0result.get()
quad1file, hash1 = quad1result.get()
quad2file, hash2 = quad2result.get()
#if dbg: #if dbg:
# print quad0file # print quad0file
@@ -569,3 +600,32 @@ def remove_tile(prefix, quadrent):
os.unlink(img) os.unlink(img)
if os.path.exists(hash): if os.path.exists(hash):
os.unlink(hash) os.unlink(hash)
class ReturnableProcess(multiprocessing.Process):
"""Like the standard multiprocessing.Process class, but the return value of
the target method is available by calling get()"""
def run(self):
results = self._target(*self._args, **self._kwargs)
self._respipe_in.send(results)
def get(self):
self.join()
return self._respipe_out.recv()
def start(self):
self._respipe_out, self._respipe_in = multiprocessing.Pipe()
multiprocessing.Process.start(self)
class FakeProcess(object):
"""Identical interface to the above class, but runs in the same thread.
Used to make the code simpler in quadtree_recurse
"""
def __init__(self, target, args=None, kwargs=None):
self._target = target
self._args = args if args else ()
self._kwargs = kwargs if kwargs else {}
def start(self):
return
def get(self):
return self._target(*self._args, **self._kwargs)