-
Notifications
You must be signed in to change notification settings - Fork 636
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
DYN-2226: Graphs with large node quantities cause long delays when setting element bound Revit nodes to update #10083
Conversation
… Callsite construction
…interaction with instruction stream data
…d and used correctly
As a side note, looking at the original call in D4R |
src/Engine/ProtoCore/RuntimeData.cs
Outdated
/// <summary> | ||
/// Map from a graph UI node to callsite identifiers. | ||
/// </summary> | ||
public Dictionary<Guid, List<string>> NodeToCallsiteIdentifiersMap { get; } |
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.
Seems internal
or private
now, can we expose this later? Also looking at CallSiteToNodeMap
above, maybe Dictionary<Guid, List<Guid>>
would be more consistent.
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.
Access modifier can be set more conservative but I would argue the other runtime dictionaries should be as well.
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 purposefully did not use Dictionary<Guid, List<Guid>>
as the primary lookup key for the CallsiteCache
dictionary was via the Callsite Identifier string. Using the Callsite.CallsiteID
Guid would require another series of ForEach loops or the creation of another map to the Callsite
objects which did not seem advisable. CallsiteCache
is the critical map here and is used in many places. The NodeToCallsite
is only used in the ReconcileTraceDataAndNotify()
which we should also look at for its inefficiency.
Can you also look at why the AppVeyor build fails? |
AppVeyor fixed :-) When can we use C# 7 features @QilongTang? |
@saintentropy When we move everything to Visual Studio 2019, currently only me in the team using that so I got the same pain :( |
@saintentropy it is likely a good idea to run the RTF tests over this change after it is merged - perhaps @AndyDu1985 or @ZiyunShang can help there? |
I'm also curious if the elementBinding perf tests get faster?
seems we should have a longer running version of this test. |
@mjkkirschner I don't think the ElementBindingLarge test is testing Revit Element binding though its more like testing the code path I updated. ElementBindingLarge is more about the trace mechanism. We should run it on the pre/post thread local storage changes. |
src/Engine/ProtoCore/RuntimeData.cs
Outdated
} | ||
else | ||
{ | ||
NodeToCallsiteIdentifiersMap[topGraphNode.guid] = new List<string>() { callsiteID }; |
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.
Why can't we simply declare a NodeToCallsiteMap
as:
Dictionary<Guid, List<CallSite>> NodeToCallsiteMap;
Then we can simply say:
NodeToCallsiteMap[topGraphNode.guid] = new List<CallSite>() { csInstance };
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 we can do that instead.... I wasn't sure the implications of having two maps that have Callsite Values. Seems ok since the CallsiteCache is never modified other than add
else | ||
{ | ||
nodeMap[nodeGuid] = new List<CallSite>(); | ||
} |
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.
Then if we have the NodeToCallsiteMap
, we won't need to do any of this either; we could simply return that map; or am I missing something?
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... this function still might need to filter the map based on the incoming dynamo graph node Guids but it could be simpler. Back in the D4R they could simply use the map
@saintentropy is this specific slowdown you're observing in DYN-2226 due to document-change events occurring in Revit caused by specific Revit UI interactions (Revit transactions) or caused by D4R nodes that cause the Revit document to change? |
@aparajit-pratap I haven't tested but could be due to both in the graph was in automatic mode. Our specific case was due to a graph running in D4R |
@saintentropy I actually believe I checked with Nate that the graph was in manual mode. He should have the full stack trace from the profiling session. |
@mjkkirschner I mean you can also see this action cause by Revit UI interaction if D4R is open and is in automatic mode. Our Repo case was a single run of the graph caused by D4R |
{ | ||
var graph = stream[0].dependencyGraph; | ||
if (graph != null) | ||
graphNodes = graph.GraphList; |
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.
@aparajit-pratap can you give us a summary of what a GraphNode
is in the context of the VM - and then is there any reason not to rename this to something less confusing as soon as possible?
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.
- It's basically a node in the dependency graph held by the VM. During codegen phase the compiler generates
graphnode
s that represent each executable statement Graphnode
s contain information about dependencies between statements and runtime properties instructing VM which instruction to execute next - they have a property that points to start and end of an instruction block- Every time a graph node executes it jumps to its target label as per dependency property stored in it - the “dep” instruction is responsible for finding which graph node to execute next given what
was currently modified
@@ -120,6 +125,15 @@ public CallSite GetCallSite(int classScope, string methodName, Executable execut | |||
|
|||
CallsiteCache[callsiteID] = csInstance; | |||
CallSiteToNodeMap[csInstance.CallSiteID] = topGraphNode.guid; | |||
List<CallSite> callsites; |
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.
@saintentropy did you notice that the params and comment for this method are completely wrong?
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 Which method are you meaning? GetCallsite
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
…tting element bound Revit nodes to update (DynamoDS#10083) * Add Map for Node guids to Callsite identifier strings * Load RuntimeData map for Node guids to Callsite identifier strings on Callsite construction * look up matching callsites directly for a node Guid with maps versus interaction with instruction stream data * Add test to validate map for node to callsite identifier is intialized and used correctly * Remove c#7 inline out variable declaration * remove more inline declaration * Remove graphnode check * Change map from callsiteIdentifier to callsite object * Update test strings * make new property internal * add code comments
…und nodes to update (#10160) * DYN-2226: Graphs with large node quantities cause long delays when setting element bound Revit nodes to update (#10083) * Add Map for Node guids to Callsite identifier strings * Load RuntimeData map for Node guids to Callsite identifier strings on Callsite construction * look up matching callsites directly for a node Guid with maps versus interaction with instruction stream data * Add test to validate map for node to callsite identifier is intialized and used correctly * Remove c#7 inline out variable declaration * remove more inline declaration * Remove graphnode check * Change map from callsiteIdentifier to callsite object * Update test strings * make new property internal * add code comments
Purpose
The purpose of this PR is to address a slow down observed in Dynamo graphs with large node quantities when Dynamo4Revit is responding to Document Change events in Revit. The specific flaw begins when the event handler
RevitServicesUpdater_ElementsUpdated()
in D4R responds to the Document Change events. It filters for transactions originating from Dynamo then passes the associated Element Id's toGetNodesFromElementIds()
in theElementBinder
. This method callsGetCallsitesForNodes()
in DynamoCore to find the associated Callsites for the nodes in the Graph. This lookup can be extremely slow due to a series of nested loops. This PR address the slowdown by processing the lookup with a pre-populated dictionary mapping the node Guids to Callsite Identifiers as the existing Callsite Cache dictionary. The runtime difference for our sample graph with 456 nodes was 23sec to ~20ms.Declarations
Check these if you believe they are true
*.resx
filesReviewers
@mjkkirschner assign to whoever you want
FYIs
@Dewb @QilongTang