-
Notifications
You must be signed in to change notification settings - Fork 621
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Spike] perform static cyclic dependency detection before each graph run #11940
base: master
Are you sure you want to change the base?
Conversation
// Detect cyclic dependencies in graph | ||
var cyclicNodes = new List<NodeModel>(); | ||
|
||
foreach (var node in ModifiedNodes) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm, so this appears that it will start multiple DFS searches from each modified node - on opening a new graph all nodes will be modified so this has the possibility of recomputing the same information multiple times -and could have a performance like O(n(n+v))
if I've got that right.
can't each invocation of DFS share the visited data structure? If one DFS has already computed this subtree of the graph whats the reason to recompute it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mjkkirschner, you're right. I made a mistake earlier. I was initializing the visited
and recursion
lists for each modified node. I have now moved their initialization outside the for
loop so it's done only once. That way if a node is already visited it will be skipped. The overall complexity of this algo is O(v+e). The reason why I'm running this on all nodes as there could be disconnected portions of the graph and I am trying to collect all nodes involved in a cycle and report warnings on those.
The good thing is this will run on all nodes only the first time a graph is opened and executed but subsequently, it will only run on the modified nodes for each delta execution.
/// <param name="recursionTracker"></param> | ||
/// <param name="cyclicNodes">list of all nodes participating in a cycle</param> | ||
/// <returns>true if graph is cyclic, false otherwise</returns> | ||
private bool IsGraphCyclic(NodeModel node, HashSet<NodeModel> visitTracker, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is there no other similar method already implemented that we could reuse (optionally enhance)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a better algorithm used called StronglyConnectedComponent
but it is used at a lower, graphnode
level in the engine. We could probably use it for this too at the dynamo nodemodel
level but we'll need to generalize (templatize) it to work for any graph vertex, which will be tricky as getting the adjacent nodes for each type is very different. It would be easier to rewrite it to make it work specifically with NodeModel
type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My first goal here was to establish that this idea could work in general, we can always refine the exact algo later and make it more efficient, which at the moment, I can think of the stronglyconnectedcomponent
algo.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok
Is this supposed to detect all cycles or just the first one ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this will detect only one cycle for a given node but the outer method call will record at most one cycle for all nodes and report warnings on all of them.
if(warning.ID == WarningID.InvalidStaticCyclicDependency && | ||
node.GUID == warning.GraphNodeGuid) | ||
{ | ||
node.ClearErrorsAndWarnings(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So these warnings are from a previous execution when the graph was in a cyclic state?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, if a cyclic warning is present for this node from a previous execution and the graph is found to be acyclic for the node in the current execution, the warning will be cleared for that node.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ClearErrorsAndWarnings
will clear everything (all previous persistent warnings or errors)
Do you think there might be a case where older (persistent) errors should not be cleared ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it will clear everything but I'm reducing the surface area of it clearing warnings only when the node in question had a cyclic warning before, but you're right, this will still clear any and all other warnings that the node previously had as well. However, I can't think of a scenario where that would do any harm as I'm relying on the fact that the same warnings should reappear if the node is re-executed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Most of the internal (not created by API users) are indeed mostly regenerated when re executing. We do have example of non execution related warnings (like the Gizmo warning)
I guess the issue would be if API users create warnings that do not get recreated by re execution
Not a big issue though...warning infrastructure does not support it yet
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have not looked at the cycle detection algorithm too much (because we said we're going to look into stronglyconnectedcomponent later on)
Asides from that, LGTM
{ | ||
#if DEBUG | ||
|
||
Console.WriteLine("Warning:{0}", message); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can use System.Diagnostics.Debug.Writeline
I think to avoid the conditional compilation if you want - I think that call does not end up logging in Release builds.
@aparajit-pratap can we run the codegen cycle detection only for cbns ? |
@pinzart90 codegen is performed on the entire AST that is compiled for the entire graph. At the moment I don't think there is a way to detect which AST's correspond to CBNs vs. those that don't although that might be something that is possible to implement. I can make this a follow-up task if we wish to proceed with this spike at some stage. Thanks for the suggestion. |
Purpose
This adds the capability to perform a static cyclic dependency detection at the Dynamo graph level before each graph run (delta execution). If a cycle is detected, other than reporting node warnings on the nodes that are participating in the cycle, it prevents the graph from executing in the first place and getting into a warning state thus saving on some execution cycles (pun unintended).
Pros
Cons
Note:
![image](https://user-images.githubusercontent.com/5710686/130870878-9eb4ed58-a59c-4ab5-abdd-e34aebd904cc.png)
We cannot remove the existing cyclic dependency checks from codegen as they are still required to detect cycles in DS code in CBNs. In the absence of this check, CBN code cycles if present will not be reported to users. An example of such a code is the following where there is a clear cycle in the code. This cannot be detected by the graph level check implemented in this spike.
Profiling Results:
Note: The profiling assumes we can do away with the codegen check for cyclic dependency but as mentioned, we cannot do so yet.
Profiling was carried out on a D4R/GD graph for graph execution only. It uses the following packages.
![image](https://user-images.githubusercontent.com/5710686/130817464-ab618ebe-4a9d-4ae9-a5bd-a4b5c6aa10db.png)
Before:
![image](https://user-images.githubusercontent.com/5710686/130723363-127cf37e-a942-443d-9214-110e0e97a630.png)
Notice that just the cyclic dependency check at codegen takes nearly 1800ms or about 11.3% of the total time. This is saved after this new approach.
After:
![image](https://user-images.githubusercontent.com/5710686/130723413-9c23e0ba-b3c8-44a8-807a-d55903b71b7c.png)
Savings of about 1300 ms or about 8% improvement overall.
TODO:
Dynamo.Tests.DelayedGraphExecutionTests.DelayedExecutionNodeToCodeTest
. It is still a mystery as no circular dependency is found on running the test graph in Dynamo, however, there is one found when running the test. This needs to be investigated further.Declarations
Check these if you believe they are true
*.resx
filesFYIs
@saintentropy