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

collection performance #12974

Open
wants to merge 34 commits into
base: master
Choose a base branch
from
Open

collection performance #12974

wants to merge 34 commits into from

Conversation

pinzart90
Copy link
Contributor

@pinzart90 pinzart90 commented Jun 7, 2022

Purpose

Improve performance when opening graphs
https://jira.autodesk.com/browse/DYN-5005

Changes:
Performance improvement when dealing with iterations

Declarations

Check these if you believe they are true

  • The codebase 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.
  • This PR modifies some build requirements and the readme is updated

Release Notes

(FILL ME IN) Brief description of the fix / enhancement. Mandatory section

Reviewers

(FILL ME IN) Reviewer 1 (If possible, assign the Reviewer for the PR)

(FILL ME IN, optional) Any additional notes to reviewers or testers.

FYIs

(FILL ME IN, Optional) Names of anyone else you wish to be notified of

@@ -2478,8 +2495,7 @@ private void LoadAnnotation(ExtraAnnotationViewInfo annotationViewInfo)
continue;

// NOTE: Some nodes may be annotations and not be found here
var nodeModel = Nodes.FirstOrDefault(node => node.GUID == guidValue);
if (nodeModel == null)
if (!nodeMap.TryGetValue(guidValue, out NodeModel nodeModel))
Copy link
Member

Choose a reason for hiding this comment

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

👍

@@ -480,18 +480,14 @@ public double EndDotSize

public NodeViewModel Nodevm
{
get
{
return workspaceViewModel.Nodes?.FirstOrDefault(x => x.NodeLogic.GUID == model.Start.Owner.GUID);
Copy link
Member

Choose a reason for hiding this comment

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

yikes

@pinzart90 pinzart90 marked this pull request as ready for review June 23, 2022 17:22
Copy link
Contributor

@aparajit-pratap aparajit-pratap left a comment

Choose a reason for hiding this comment

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

Curious if all the locking and unlocking for each and every smart collection operation has any performance impact? It looks like in most cases, it may be overkill to make it thread safe so aggressively if that's the case, especially if such cross-threading scenarios will be rare.

@pinzart90
Copy link
Contributor Author

pinzart90 commented Jun 28, 2022

Curious if all the locking and unlocking for each and every smart collection operation has any performance impact? It looks like in most cases, it may be overkill to make it thread safe so aggressively if that's the case, especially if such cross-threading scenarios will be rare.

The SmartObservableCollection is not meant to be a replacement for all Lists/dictionaries or for highly sensitive areas.
It should mostly be used for WPF bindings (and optionally when dealing with multiple threads).
As an example:
Scheduler thread: Model.smartObservableItems => changed when running a graph
UI thread: View => BindTo Model.smartObservableItems

SmartObservableCollection is already executing a lot of instructions on every collection operation (ex. property change notifications, collection change notifications).
In the scenario where there are only concurrent reads, adding locks to the mix should not impact performance in a noticeable way.
In the scenario where there are concurrent reads and writes, obviously one of the operations will wait for the other with some performance penalty.
I can run performance tests to confirm.

/// Returns true if a value of type ValueAs is found at key in the ConcurrentDictionary
/// Returns false otherwise. The output value 'valueAs' will be null in this case.
/// </summary>
public static bool TryGetValueAs<Key, Value, ValueAs>(this ConcurrentDictionary<Key, Value> dictionary, Key key, out ValueAs valueAs) where ValueAs : class
Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry, I'm confused, why did you need to change where ValueAs : Value to where ValueAs : class?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

the constraint where ValueAs : Value does not support nullable (at least not in the currently used c# language version)
To support nullable I had to switch to the class constraint (to be able to return null and not default). This way we do lose the base type constraint...but it is not so important in my opinion

@@ -2469,6 +2469,8 @@ private void LoadAnnotation(ExtraAnnotationViewInfo annotationViewInfo)

var text = annotationViewInfo.Title;

Dictionary<Guid, NodeModel> nodeMap = Nodes.ToDictionary(x => x.GUID);
Copy link
Member

Choose a reason for hiding this comment

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

Could this be done once before all the Annotations were loaded? Is this just to be sure that new nodes were not loaded between some annotations being loaded?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

Copy link
Member

@mjkkirschner mjkkirschner left a comment

Choose a reason for hiding this comment

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

so there are some fundamental changes to the core data structures that are used to bind the view models to the views in this PR.

What type of performance improvement did you notice, and can you characterize where those improvements came from?

I think it is also worth discussing why the smart collection type should be thread safe, I guess because the other collections used locks beforehand and we do not want to break any existing callers?

@@ -186,6 +186,11 @@ internal void OnRequestPortContextMenu(ShowHideFlags flag, PortViewModel viewMod

#region Properties and Fields

/// <summary>
/// A Dictionary that maps NodeModels (by GUID) to their corresponding ViewModels
Copy link
Member

Choose a reason for hiding this comment

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

this comment seems incorrect - below you add ConnectorViewModels to this same map, is this a map of all viewModelTypes?

annotationViewModel.Dispose();
}
Annotations.Clear();
}

void Model_NodesCleared()
{
lock (Nodes)
Copy link
Member

Choose a reason for hiding this comment

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

lock is removed because this is a concurrent dictionary?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

lock has been reintroduced as it was

private void subscribeNodeEvents(NodeViewModel nodeViewModel)
{
nodeViewModel.SnapInputEvent += nodeViewModel_SnapInputEvent;
nodeViewModel.NodeLogic.Modified += OnNodeModified;
Copy link
Member

Choose a reason for hiding this comment

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

possibility of nodeLogic being null?

Errors.Remove(nodeViewModel.ErrorBubble);
//unsub the events we attached below in NodeAdded.
unsubscribeNodeEvents(nodeViewModel);

Nodes.Remove(nodeViewModel);
Copy link
Member

Choose a reason for hiding this comment

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

it would be nice if these other collections all stayed in sync automatically with the viewmodelmap or vice versa.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Collections will now sync somewhat automatically ( except when calling Clear on the collections).

src/DynamoUtilities/SmartObservableCollection.cs Outdated Show resolved Hide resolved
@pinzart90
Copy link
Contributor Author

Idea for more improvement is to DelayGraphExecution when calling DynamoModel::GetConnectorsToAddAndDelete. At this time...when hooking up a new node to an already connected node, Dynamo creates and schedules 2 UpdateGraphAsyncTasks.

@mjkkirschner
Copy link
Member

mjkkirschner commented Dec 1, 2022

Idea for more improvement is to DelayGraphExecution when calling DynamoModel::GetConnectorsToAddAndDelete. At this time...when hooking up a new node to an already connected node, Dynamo creates and schedules 2 UpdateGraphAsyncTasks.

see this issue for something related, it might be a slightly different case
https://jira.autodesk.com/browse/DYN-5128

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants