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

WIP, Highlighting: hot methods and invocation of costly methods #816

Merged
merged 6 commits into from
Oct 2, 2018

Conversation

krasnotsvetov
Copy link
Collaborator

TODO

@akarpov89
Copy link
Contributor

Briefly looked at the files and couldn't find any highlighting tests. Please add.

@akarpov89
Copy link
Contributor

It's very cool to have such inspections 👍

Though I have concerns about current implementation. In ReSharper there is a daemon stage which also builds call graph and is cross-procedural: SynchronizationProblemAnalyzerStage. I think PerformanceCriticalCodeAnalysisStage should be implemented in a similar way. I'm even wondering if it would be possible to share some code between these stages, there are certainly common parts.

While current implementation of PerformanceCriticalCodeAnalysisStage rebuilds the call graph on every run, SynchronizationProblemAnalyzerStage is incremental. Take a look how it uses FileStructure.MembersToRehighlight to rebuild only some parts of the graph. Also note that during the CSharpErrorStage LockSectionProblemAnalyzer saves info about presence of lock statement. In case there are no locks stage exists. You can actually ask Ivan Serduk for more details, he is author of the SynchronizationProblemAnalyzerStage.

{
switch (descendantsEnumerator.Current)
{
// skip class declaration because all derived sym
Copy link
Contributor

Choose a reason for hiding this comment

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

unfinished comment?

break;
case IInvocationExpression invocationExpression:
// we should find 'StartCoroutine' method, because passed symbol will be hot too
var reference = (invocationExpression.InvokedExpression as IReferenceExpression)?.Reference;
Copy link
Contributor

Choose a reason for hiding this comment

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

there is an InvocationExpressionReference property available on IInvocationExpression

Copy link
Collaborator Author

@krasnotsvetov krasnotsvetov Oct 2, 2018

Choose a reason for hiding this comment

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

InvocationExpressionReference has invalid DocumentRange (-1,-1)
(invocationExpression.InvokedExpression as IReferenceExpression)?.Reference has valid range for document

if (reference == null)
break;

var info = reference.Resolve();
Copy link
Contributor

Choose a reason for hiding this comment

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

ResolveResultWithInfo can be deconstructed:
var (declaredElement, substitution, resolveErrorType) = reference.Resolve();


// 'StartCoroutine' has overload with string. We have already attached reference, so get symbol from
// reference
if (firstArgument.ConstantValue.IsString())
Copy link
Contributor

Choose a reason for hiding this comment

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

code under condition can be extracted into separate method

if (method == null)
break;

var declarations = method.GetDeclarationsIn(sourceFile).Where(t => t.GetSourceFile() == sourceFile);
Copy link
Contributor

Choose a reason for hiding this comment

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

GetDeclarationsIn(sourceFile) is called, Where seems to be redundant. But if type declaration is partial then method can be located in another source file.

private readonly IHighlightingConsumer myConsumer;
private readonly int myMaxDepth;

public HotMethodAnalyzer(IPsiSourceFile sourceFile, IHighlightingConsumer consumer, int maxDepth)
Copy link
Contributor

Choose a reason for hiding this comment

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

maxDepth could be a constant declared in HotMethodAnalyzer

#region CostlyMethodInvocationInspection

ISet<string> knownCostlyMethods = null;
if (containingType.GetClrName().Equals(KnownTypes.Component))
Copy link
Contributor

Choose a reason for hiding this comment

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

containingType.GetClrName() is called several types, but could be saved into local variable


IExpressionType expressionType = null;

if (leftOperand.ConstantValue(new UniversalContext(equalityExpressionParam)).IsNull())
Copy link
Contributor

Choose a reason for hiding this comment

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

there is a ConstantValue property, no need to call extension method

if (containingType == null)
return;

#region CostlyMethodInvocationInspection
Copy link
Contributor

Choose a reason for hiding this comment

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

Regions are generally bad. Use methods to structure code.

public int Depth { get; set; }

// Inverted call graph for costly reachable mark propagation after visit AST
public IReadOnlyDictionary<IDeclaredElement, HashSet<IDeclaredElement>> InvertedCallGraph
Copy link
Contributor

Choose a reason for hiding this comment

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

Creating a copy of the whole dictionary isn't very good in terms of memory consumption. Maybe return the original dictionary?


private static List<IMethodDeclaration> FindHotRootMethods([NotNull]ICSharpFile file,[NotNull] IPsiSourceFile sourceFile)
{
var result = new List<IMethodDeclaration>();
Copy link
Contributor

Choose a reason for hiding this comment

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

Often it's good to use LocaList<T> instead of List<T>, it doesn't allocate without need. Once you populated local list you can then invoke .ReadOnlyList() on it to get IReadOnlyList<T>.

var descendantsEnumerator = file.Descendants();
while (descendantsEnumerator.MoveNext())
{
var checkForInterrupt = InterruptableActivityCookie.GetCheck().NotNull("checkForInterrupt != null");
Copy link
Contributor

Choose a reason for hiding this comment

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

It's better to get check for interrupt in outer method and use it here and in HotMethodAnalyzer. Also it makes sense to call checkForInterrupt only in switch cases where we are doing some interesting work and not on every descendant node.

@krasnotsvetov krasnotsvetov merged commit 943f4b9 into 183 Oct 2, 2018
@krasnotsvetov krasnotsvetov deleted the 183-vk-performance-critical-code-analyzer branch October 2, 2018 15:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants