Best Pattern for Implementing a visitor that only handles certain nodes in the tree #1704
Replies: 3 comments 1 reply
-
|
Basically, the slowness is from the py/cpp boundary crossing that's happening on every AST logic and not your dispatch code, and I'd say there's two ways to do this
def __call__(self, node):
if not isinstance(node, ps.Symbol):
return
handler = self.lookup_table.get(node.kind)
if handler:
handler(node)
c.getRoot().visit(v, visitStatements=False, visitExpressions=False)You'll notice this dramatically reduces the callbacks since statements and the expressions make up the vast majority of AST nodes: class ConditionalVisitor:
def visit_instance_recursive(self, body):
print(f"--- entering instance body: {body.definition.name} ---")
# ... handle params ...
for member in body:
if member.kind == ps.SymbolKind.Instance:
self.visit_instance_recursive(member.body)
elif member.kind == ps.SymbolKind.GenerateBlock:
self._walk_generate(member)
def _walk_generate(self, block):
for member in block:
if member.kind == ps.SymbolKind.Instance:
self.visit_instance_recursive(member.body)
elif member.kind == ps.SymbolKind.GenerateBlock:
self._walk_generate(member)
for inst in c.getRoot().topInstances:
v.visit_instance_recursive(inst.body)and this covers gen blocks and multi top-level instances, and it becomes obvious this manual approach will be the fastest since you can control which subtrees to enter. the lookup table pattern itself is super fine since dict.get() is essentially free compared to the cross-boundary overhead. |
Beta Was this translation helpful? Give feedback.
-
|
Maybe the Python visitor system could be reworked to use the dict-based solution instead. Like you pass it a dict of handlers at creation time and then the C++ would only call into Python for node types you care about. |
Beta Was this translation helpful? Give feedback.
-
|
@CheeksTheGeek thank you so much for the input! It makes sense to me that the will always be the fastest if I manage to implement the walk correctly for all of the symbols/statements/expressions I need to support in a given visitor. It seems to me that it leaves a lot of room for me to write small bugs that aren't always easy to notice. I think if I could control which node types trigger slang to cross the cpp/python boundary with the built in visitor then the performance would be good enough for many of my use cases and I could avoid the anxiety of worrying that I incorrectly walked the tree because the visiting logic is delegated out to slang. Modifying the call to visit to take the dictionary like this would make things better in my use cases (and possibly others as well). import pyslang as ps
...
lookup_table = {
ps.SymbolKind.InstanceBody: handle_instancebody_sym
}
...
c.getRoot().visit(v, lookup_table)The visitor could then walk the tree and if it encounters a node with one of the specified kinds it crosses the cpp/python boundary, otherwise it continues walking. The dict here could default to empty in which case the visitor behaves like it normally does now. There may also be further room for improvement by analyzing the kinds of nodes when the dictionary is non empty to check if all statements / expressions could be skipped over since there is already support for that as noted by @CheeksTheGeek. @MikePopoloski would this be a change you'd be open to if I started to look into it? |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Hello!
I am trying to figure out the best way to implement visitors using pyslang to handle sets of specific AST node kinds. Below is an example of what I have at the moment
In the example above I am only visiting instance body symbols and evaluating their parameters, but there are other cases in my project where I need to handle a larger set of node kinds, hence the lookup table pattern. I've found that this pattern is much cleaner for my codebase than a giant list of if statements testing for specific node kinds, but it is much slower. Surprisingly, I believe that the slowness of this implementation is not caused by my table lookup and dispatch, but rather by the fact that slang calls into this visitor for every node in the AST (and not just the instance body that I care about in this scenario).
I have also tried to achieve the same goal via a pattern that manually walks the tree like so:
This pattern is much faster than the first one listed, but I've found that it is difficult to ensure that all of the instance bodies actually get visited due to the structure of some symbols being different from others.
Would anyone recommend a better way to implement the visitor that is also scalable for times when I need to support a larger set of Symbol kinds? Thanks!
Beta Was this translation helpful? Give feedback.
All reactions