Sort queue of blocks to visit by blend file and on-disk order

This gives a small speedup to dependency tracing.
This commit is contained in:
Sybren A. Stüvel 2018-03-07 17:14:35 +01:00
parent 09a0866c14
commit cc20b0bfd5
2 changed files with 37 additions and 6 deletions

View File

@ -121,7 +121,7 @@ class BlendFile:
fileobj.close() fileobj.close()
raise exceptions.BlendFileError("File is not a blend file", path) raise exceptions.BlendFileError("File is not a blend file", path)
self.blocks = [] self.blocks = [] # BlendFileBlocks, in disk order.
self.code_index = collections.defaultdict(list) self.code_index = collections.defaultdict(list)
self.structs = [] self.structs = []
self.sdna_index_from_id = {} self.sdna_index_from_id = {}
@ -333,6 +333,7 @@ class BlendFile:
raise exceptions.SegmentationFault('address does not exist', address) from None raise exceptions.SegmentationFault('address does not exist', address) from None
@functools.total_ordering
class BlendFileBlock: class BlendFileBlock:
""" """
Instance of a struct. Instance of a struct.
@ -405,6 +406,14 @@ class BlendFileBlock:
self.addr_old == other.addr_old and self.addr_old == other.addr_old and
self.bfile.filepath == other.bfile.filepath) self.bfile.filepath == other.bfile.filepath)
def __lt__(self, other: 'BlendFileBlock') -> bool:
"""Order blocks by file path and offset within that file."""
if not isinstance(other, BlendFileBlock):
raise NotImplemented()
my_key = self.bfile.filepath, self.file_offset
other_key = other.bfile.filepath, other.file_offset
return my_key < other_key
def __bool__(self) -> bool: def __bool__(self) -> bool:
"""Data blocks are always True.""" """Data blocks are always True."""
return True return True

View File

@ -35,17 +35,21 @@ class _BlockIterator:
limit_to: typing.Set[blendfile.BlendFileBlock] = frozenset(), limit_to: typing.Set[blendfile.BlendFileBlock] = frozenset(),
) -> typing.Iterator[blendfile.BlendFileBlock]: ) -> typing.Iterator[blendfile.BlendFileBlock]:
"""Expand blocks with dependencies from other libraries.""" """Expand blocks with dependencies from other libraries."""
if limit_to:
self._queue_named_blocks(bfile, limit_to)
else:
self._queue_all_blocks(bfile)
blocks_per_lib = yield from self._visit_blocks(bfile, limit_to)
yield from self._visit_linked_blocks(blocks_per_lib)
def _visit_blocks(self, bfile, limit_to):
bpath = bfile.filepath.absolute().resolve() bpath = bfile.filepath.absolute().resolve()
root_dir = bpathlib.BlendPath(bpath.parent) root_dir = bpathlib.BlendPath(bpath.parent)
# Mapping from library path to data blocks to expand. # Mapping from library path to data blocks to expand.
blocks_per_lib = collections.defaultdict(set) blocks_per_lib = collections.defaultdict(set)
if limit_to:
self._queue_named_blocks(bfile, limit_to)
else:
self._queue_all_blocks(bfile)
while self.to_visit: while self.to_visit:
block = self.to_visit.popleft() block = self.to_visit.popleft()
assert isinstance(block, blendfile.BlendFileBlock) assert isinstance(block, blendfile.BlendFileBlock)
@ -75,6 +79,9 @@ class _BlockIterator:
self.blocks_yielded.add((bpath, block.addr_old)) self.blocks_yielded.add((bpath, block.addr_old))
yield block yield block
return blocks_per_lib
def _visit_linked_blocks(self, blocks_per_lib):
# We've gone through all the blocks in this file, now open the libraries # We've gone through all the blocks in this file, now open the libraries
# and iterate over the blocks referred there. # and iterate over the blocks referred there.
for lib_bpath, idblocks in blocks_per_lib.items(): for lib_bpath, idblocks in blocks_per_lib.items():
@ -95,6 +102,7 @@ class _BlockIterator:
# to do with them anyway. # to do with them anyway.
self.to_visit.extend(block for block in bfile.blocks self.to_visit.extend(block for block in bfile.blocks
if block.code != b'DATA') if block.code != b'DATA')
self._sort_queue()
def _queue_named_blocks(self, def _queue_named_blocks(self,
bfile: blendfile.BlendFile, bfile: blendfile.BlendFile,
@ -106,6 +114,7 @@ class _BlockIterator:
The queued blocks are loaded from the actual blend file, and The queued blocks are loaded from the actual blend file, and
selected by name. selected by name.
""" """
for to_find in limit_to: for to_find in limit_to:
assert to_find.code == b'ID' assert to_find.code == b'ID'
name_to_find = to_find[b'name'] name_to_find = to_find[b'name']
@ -116,9 +125,22 @@ class _BlockIterator:
if block.id_name == name_to_find: if block.id_name == name_to_find:
log.debug('Queueing %r from file %s', block, bfile.filepath) log.debug('Queueing %r from file %s', block, bfile.filepath)
self.to_visit.append(block) self.to_visit.append(block)
self._sort_queue()
def _queue_dependencies(self, block: blendfile.BlendFileBlock): def _queue_dependencies(self, block: blendfile.BlendFileBlock):
self.to_visit.extend(expanders.expand_block(block)) self.to_visit.extend(expanders.expand_block(block))
self._sort_queue()
def _sort_queue(self):
"""Sort the queued blocks by file and by offset.
This allows us to go through the blend files sequentially.
"""
def sort_key(block: blendfile.BlendFileBlock):
return block.bfile.filepath, block.file_offset
self.to_visit = collections.deque(sorted(self.to_visit, key=sort_key))
def iter_blocks(bfile: blendfile.BlendFile) -> typing.Iterator[blendfile.BlendFileBlock]: def iter_blocks(bfile: blendfile.BlendFile) -> typing.Iterator[blendfile.BlendFileBlock]: