Skip to content

Commit d29f1d4

Browse files
committed
Improve flatdata-generator speed by caching various computations, or making them lazy
1 parent e8e1601 commit d29f1d4

4 files changed

Lines changed: 72 additions & 33 deletions

File tree

flatdata-generator/flatdata/generator/generators/__init__.py

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ class BaseGenerator(metaclass=ABCMeta):
2929

3030
def __init__(self, template: str) -> None:
3131
self._template = template
32+
self._env: Environment | None = None
3233

3334
@abstractmethod
3435
def supported_nodes(self) -> list[type]:
@@ -49,30 +50,36 @@ def get_import_directives(self, tree: SyntaxTree) -> list[str]:
4950
"""Return language-specific import directives. Override in subclasses."""
5051
return []
5152

53+
def _get_environment(self, tree: SyntaxTree) -> Environment:
54+
if self._env is None:
55+
env = Environment(loader=PackageLoader('flatdata.generator', 'templates'), lstrip_blocks=True,
56+
trim_blocks=True, autoescape=False, extensions=[RaiseExtension])
57+
env.filters['is_archive'] = lambda n: isinstance(n, Archive)
58+
env.filters['is_instance'] = lambda n: isinstance(n, Instance)
59+
env.filters['is_raw_data'] = lambda n: isinstance(n, RawData)
60+
env.filters['is_archive_resource'] = lambda n: isinstance(
61+
n, ArchiveResource)
62+
env.filters['is_structure'] = lambda n: isinstance(n, Structure)
63+
env.filters['is_enumeration_reference'] = lambda n: isinstance(n, EnumerationReference)
64+
env.filters['is_enumeration'] = lambda n: isinstance(n, Enumeration)
65+
env.filters['is_constant'] = lambda n: isinstance(n, Constant)
66+
env.filters['is_namespace'] = lambda n: isinstance(n, Namespace)
67+
env.filters['is_resource'] = lambda n: isinstance(n, ResourceBase)
68+
env.filters['is_bound_resource'] = lambda n: isinstance(
69+
n, BoundResource)
70+
env.filters['is_vector'] = lambda n: isinstance(n, Vector)
71+
env.filters['is_multivector'] = lambda n: isinstance(n, Multivector)
72+
env.filters['is_multivector_index'] = lambda n: (isinstance(
73+
n, Structure) and "_builtin.multivector" in SyntaxTree.namespace_path(n))
74+
env.filters['namespaces'] = SyntaxTree.namespaces
75+
env.filters['not_auto_generated'] = lambda n: [ x for x in n if not x.auto_generated]
76+
self._populate_environment(env, tree)
77+
self._env = env
78+
return self._env
79+
5280
def render(self, tree: SyntaxTree) -> str:
5381
"""Generate the language implementation from the AST"""
54-
env = Environment(loader=PackageLoader('flatdata.generator', 'templates'), lstrip_blocks=True,
55-
trim_blocks=True, autoescape=False, extensions=[RaiseExtension])
56-
env.filters['is_archive'] = lambda n: isinstance(n, Archive)
57-
env.filters['is_instance'] = lambda n: isinstance(n, Instance)
58-
env.filters['is_raw_data'] = lambda n: isinstance(n, RawData)
59-
env.filters['is_archive_resource'] = lambda n: isinstance(
60-
n, ArchiveResource)
61-
env.filters['is_structure'] = lambda n: isinstance(n, Structure)
62-
env.filters['is_enumeration_reference'] = lambda n: isinstance(n, EnumerationReference)
63-
env.filters['is_enumeration'] = lambda n: isinstance(n, Enumeration)
64-
env.filters['is_constant'] = lambda n: isinstance(n, Constant)
65-
env.filters['is_namespace'] = lambda n: isinstance(n, Namespace)
66-
env.filters['is_resource'] = lambda n: isinstance(n, ResourceBase)
67-
env.filters['is_bound_resource'] = lambda n: isinstance(
68-
n, BoundResource)
69-
env.filters['is_vector'] = lambda n: isinstance(n, Vector)
70-
env.filters['is_multivector'] = lambda n: isinstance(n, Multivector)
71-
env.filters['is_multivector_index'] = lambda n: (isinstance(
72-
n, Structure) and "_builtin.multivector" in SyntaxTree.namespace_path(n))
73-
env.filters['namespaces'] = SyntaxTree.namespaces
74-
env.filters['not_auto_generated'] = lambda n: [ x for x in n if not x.auto_generated]
75-
self._populate_environment(env, tree)
82+
env = self._get_environment(tree)
7683
template = env.get_template(self._template)
7784

7885
flatdata_nodes = [n for n, _ in DfsTraversal(tree).dependency_order() if

flatdata-generator/flatdata/generator/tree/nodes/node.py

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,18 @@
1616
_T = TypeVar('_T', bound='Node')
1717

1818

19+
class _NodeNotFoundError(RuntimeError):
20+
"""Lazy error that defers expensive symbols() computation to str()."""
21+
22+
def __init__(self, path: str, tree: 'Node') -> None:
23+
self._path = path
24+
self._tree = tree
25+
26+
def __str__(self) -> str:
27+
return "Path '{path}' not found in tree. Options: {options}".format(
28+
path=self._path, options=tuple(self._tree.symbols()))
29+
30+
1931
class Node:
2032
"""
2133
Node of a Syntax Tree.
@@ -48,6 +60,7 @@ def __init__(self, name: str, properties: ParseResults | None = None) -> None:
4860
self._parent: Node | None = None
4961
self._source_file: str | None = None
5062
self._is_local: bool = True
63+
self._cached_path: str | None = None
5164

5265
@property
5366
def source_file(self) -> str | None:
@@ -110,9 +123,17 @@ def path(self) -> str:
110123
"""
111124
Returns nodes' path in a tree.
112125
"""
113-
if self._parent is None:
114-
return self.name
115-
return Node.jointwo(self._parent.path, self.name)
126+
if self._cached_path is None:
127+
if self._parent is None:
128+
self._cached_path = self.name
129+
else:
130+
self._cached_path = Node.jointwo(self._parent.path, self.name)
131+
return self._cached_path
132+
133+
def _invalidate_path_cache(self) -> None:
134+
self._cached_path = None
135+
for child in self._children.values():
136+
child._invalidate_path_cache()
116137

117138
def path_with(self, separator: str = '_') -> str:
118139
"""
@@ -142,6 +163,7 @@ def set_name(self, value: str) -> None:
142163
"Cannot rename the node, name {value} is already in use".format(value=value))
143164

144165
self._name = value
166+
self._invalidate_path_cache()
145167
if self.parent is not None:
146168
self.parent.reindex()
147169

@@ -157,14 +179,12 @@ def find(self, path: str) -> Node:
157179
try:
158180
target = self
159181
if target.name != keys[0]:
160-
raise RuntimeError("Path {path} not found in tree. Options: {options}".format(
161-
path=path, options=tuple(self.symbols())))
182+
raise _NodeNotFoundError(path, self)
162183

163184
for key in keys[1:]:
164185
target = target._children[key]
165186
except (KeyError, IndexError):
166-
raise RuntimeError("Path '{path}' not found in tree. Options: {options}".format(
167-
path=path, options=tuple(self.symbols())))
187+
raise _NodeNotFoundError(path, self)
168188
return target
169189

170190
def get(self, path: str, default: Node | None = None) -> Node | None:
@@ -251,6 +271,7 @@ def insert(self, *nodes: Node) -> Node:
251271

252272
self._children[node.name] = node
253273
node._parent = self
274+
node._invalidate_path_cache()
254275
return self
255276

256277
def erase(self, key: str) -> None:
@@ -300,6 +321,7 @@ def detach(self) -> Node:
300321
return self
301322
del self._parent._children[self.name]
302323
self._parent = None
324+
self._invalidate_path_cache()
303325
return self
304326

305327
def symbols(self, include_types: bool = False) -> set[str] | dict[str, type]:

flatdata-generator/flatdata/generator/tree/nodes/references.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class Reference(Node):
1414

1515
def __init__(self, name: str) -> None:
1616
super().__init__(name=Reference._referencify(name))
17+
self._cached_node: Node | None = None
1718

1819
@property
1920
def target(self) -> str:
@@ -23,11 +24,14 @@ def update_reference(self, new_value: str) -> None:
2324
assert new_value.endswith(self.target), \
2425
"References can only be updated during resolution for the same symbol: %s -> %s" % \
2526
(self.target, new_value)
27+
self._cached_node = None
2628
self.set_name(Reference._referencify(new_value))
2729

2830
@property
2931
def node(self) -> Node:
30-
return self.root.find(self.target)
32+
if self._cached_node is None:
33+
self._cached_node = self.root.find(self.target)
34+
return self._cached_node
3135

3236
@property
3337
def is_qualified(self) -> bool:

flatdata-generator/flatdata/generator/tree/syntax_tree.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from __future__ import annotations
66

77
from collections.abc import Iterator, Sequence
8+
from typing import TYPE_CHECKING
89

910
from flatdata.generator.tree.nodes.references import TypeReference
1011
from flatdata.generator.tree.nodes.trivial import Namespace
@@ -15,6 +16,9 @@
1516
from flatdata.generator.tree.nodes.root import Root
1617
from flatdata.generator.tree.importer import ImportInfo
1718

19+
if TYPE_CHECKING:
20+
from flatdata.generator.generators import BaseGenerator
21+
1822
class SyntaxTree:
1923
"""
2024
Flatdata Syntax Tree.
@@ -99,14 +103,16 @@ def _unique(sequence: list[Node]) -> list[Node]:
99103
nodes.extend(dependent_type)
100104
return _unique(nodes)
101105

106+
_schema_generator: 'BaseGenerator | None' = None
107+
102108
@staticmethod
103109
def schema(node: Node) -> str:
104110
from ..generators.flatdata import FlatdataGenerator
105-
generator = FlatdataGenerator()
111+
if SyntaxTree._schema_generator is None:
112+
SyntaxTree._schema_generator = FlatdataGenerator()
106113

107-
# extract subtree from syntax tree
108114
subtree = node.extract_subtree()
109-
return str(generator.render(SyntaxTree(subtree)))
115+
return str(SyntaxTree._schema_generator.render(SyntaxTree(subtree)))
110116

111117
@staticmethod
112118
def namespaces(node: Node) -> Iterator[Node]:

0 commit comments

Comments
 (0)