Skip to content
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

Merged
merged 11 commits into from
Nov 22, 2019

Conversation

saintentropy
Copy link
Contributor

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 to GetNodesFromElementIds() in the ElementBinder. This method calls GetCallsitesForNodes() 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

  • The code base is in a better state after this PR
  • Is documented according to the standards
  • The level of testing this PR includes is appropriate
  • User facing strings, if any, are extracted into *.resx files
  • All tests pass using the self-service CI.
  • Snapshot of UI changes, if any.
  • Changes to the API follow Semantic Versioning, and are documented in the API Changes document.

Reviewers

@mjkkirschner assign to whoever you want

FYIs

@Dewb @QilongTang

@saintentropy saintentropy changed the title DYN-2226: Graphs with large node quantities cause long delays when setting element bond Revit nodes to update WIP DYN-2226: Graphs with large node quantities cause long delays when setting element bond Revit nodes to update Oct 24, 2019
@saintentropy
Copy link
Contributor Author

As a side note, looking at the original call in D4R GetNodesFromElementIds() method, it seems like it could just use the CallsiteCache directly instead of calling GetCallsitesForNodes() first. I need to check what node types it filters but may be faster just to process all the Callsites.

/// <summary>
/// Map from a graph UI node to callsite identifiers.
/// </summary>
public Dictionary<Guid, List<string>> NodeToCallsiteIdentifiersMap { get; }
Copy link
Contributor

@QilongTang QilongTang Oct 24, 2019

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

@saintentropy saintentropy Oct 24, 2019

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.

@QilongTang
Copy link
Contributor

Can you also look at why the AppVeyor build fails?

@saintentropy
Copy link
Contributor Author

AppVeyor fixed :-) When can we use C# 7 features @QilongTang?

@QilongTang
Copy link
Contributor

@saintentropy When we move everything to Visual Studio 2019, currently only me in the team using that so I got the same pain :(

@mjkkirschner
Copy link
Member

@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?

@mjkkirschner
Copy link
Member

mjkkirschner commented Oct 25, 2019

I'm also curious if the elementBinding perf tests get faster?
The answer is not really - but they run in 10 already so...

22:57:16  |    Run |      ElementBindingLarge |    Base |     11.31 ms |   0.34 ms |   0.20 ms |
22:57:16  |        |                          |     New |     10.80 ms |   0.13 ms |   0.03 ms |
22:57:16  |        |                          |         |       -4.51% |   -61.27% |   -83.09% |PASS

seems we should have a longer running version of this test.

@saintentropy
Copy link
Contributor Author

@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.

}
else
{
NodeToCallsiteIdentifiersMap[topGraphNode.guid] = new List<string>() { callsiteID };
Copy link
Contributor

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 };

Copy link
Contributor Author

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>();
}
Copy link
Contributor

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?

Copy link
Contributor Author

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

@aparajit-pratap
Copy link
Contributor

@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?

@saintentropy
Copy link
Contributor Author

@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

@mjkkirschner
Copy link
Member

@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.

@saintentropy
Copy link
Contributor Author

@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

@mjkkirschner mjkkirschner changed the title WIP DYN-2226: Graphs with large node quantities cause long delays when setting element bond Revit nodes to update WIP DYN-2226: Graphs with large node quantities cause long delays when setting element bound Revit nodes to update Nov 4, 2019
{
var graph = stream[0].dependencyGraph;
if (graph != null)
graphNodes = graph.GraphList;
Copy link
Member

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?

Copy link
Contributor

@aparajit-pratap aparajit-pratap Nov 15, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. It's basically a node in the dependency graph held by the VM. During codegen phase the compiler generates graphnodes that represent each executable statement
  2. Graphnodes 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​
  3. 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;
Copy link
Member

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?

Copy link
Contributor Author

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

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes

@saintentropy saintentropy removed the WIP label Nov 8, 2019
@saintentropy saintentropy changed the title WIP DYN-2226: Graphs with large node quantities cause long delays when setting element bound Revit nodes to update DYN-2226: Graphs with large node quantities cause long delays when setting element bound Revit nodes to update Nov 8, 2019
@mjkkirschner mjkkirschner added this to the 2.5.0 milestone Nov 20, 2019
@aparajit-pratap aparajit-pratap added the LGTM Looks good to me label Nov 21, 2019
@aparajit-pratap aparajit-pratap merged commit 9115b98 into DynamoDS:master Nov 22, 2019
aparajit-pratap pushed a commit to aparajit-pratap/Dynamo that referenced this pull request Nov 22, 2019
…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
aparajit-pratap added a commit that referenced this pull request Nov 22, 2019
…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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
LGTM Looks good to me performance
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants