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

Updates in performance highlightings. #878

Merged
merged 11 commits into from
Nov 8, 2018
Merged

Conversation

krasnotsvetov
Copy link
Collaborator

@krasnotsvetov krasnotsvetov commented Nov 2, 2018

  1. Add context "Move to start", "Move to awake" and "Move outside the loop" if current operation in loop. (All context actions is available only in per frame methods and methods that are reachable from them)
    a. Currently not solution-wide (only for opened file)
    b. This operations can break semantics, so they are just only context actions, not quickfixes
  2. Add highlighting on gutter for each performance method
  3. Add possibility to disable highlightings

Copy link
Contributor

@kskrygan kskrygan left a comment

Choose a reason for hiding this comment

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

please correct some minor issues related to threading and locks assertions

public static class PerformanceCriticalCodeStageUtil
{
public static bool IsInvocationExpensive(IInvocationExpression invocationExpression)
{
Copy link
Contributor

Choose a reason for hiding this comment

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

this is a public API.
threading?
locks?
Please use Shell.Instance.Locks.assertBlaBlaBla for all those purposes

namespace JetBrains.ReSharper.Plugins.Unity.CSharp.Feature.Services.QuickFixes.MoveQuickFixes
{
public static class MonoBehaviourMoveUtil
{
Copy link
Contributor

Choose a reason for hiding this comment

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

seems it can be accessed from UI thread only. Please assert that.

@@ -1,10 +1,10 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
// Этот код создан программой.
Copy link
Contributor

Choose a reason for hiding this comment

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

please exclude this file from the merge

@@ -170,13 +170,15 @@ private void InitLanguageLevelSettings(IProject project, SettingsStorageMountPoi
// * Unity3dRider can set `TargetFrameworkVersion` to `v4.5` on non-Windows machines to fix
// an issue resolving System.Linq

var languageLevel = ReSharperSettingsCSharpLanguageLevel.Default;
var languageLevel = ReSharperSettingsCSharpLanguageLevel.CSharp40;
Copy link
Contributor

Choose a reason for hiding this comment

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

why is it so?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Out-of-date 183's fixes. Fixed.

[NotNull]
public static string GetUniqueName<T>([NotNull] T node, [NotNull] string baseName,
NamedElementKinds elementKind, Func<IDeclaredElement, bool> isConflictingElement = null) where T : ICSharpTreeNode
{
Copy link
Contributor

Choose a reason for hiding this comment

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

assertions

@krasnotsvetov krasnotsvetov force-pushed the 183-vk-performance-stage branch 3 times, most recently from 511d1c8 to dd2190b Compare November 8, 2018 12:52
@citizenmatt citizenmatt merged commit a68bba9 into 183 Nov 8, 2018
@citizenmatt citizenmatt deleted the 183-vk-performance-stage branch November 8, 2018 17:10
@krasnotsvetov krasnotsvetov restored the 183-vk-performance-stage branch November 8, 2018 17:10
Copy link

@TessenR TessenR left a comment

Choose a reason for hiding this comment

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

It looks like QFs assume they are moving code between methods of the same type.
The analyzer on the other hand seems to check methods from the same file.

Please add tests for code like this:

class C : MonoBehaviour
{
  public void Update()
  {
    var t = new MyType();
    t.Test();
  }
}

class MyType : MonoBehaviour
{
  public void Test()
  {
    GetComponent("test");
  }
}

Is there a reason why a call Stack is not sufficient for the performance inspector?
It looks like there's a tremendous amout of work done for keeping CurrentElement and CurrentDeclaration consistent, reassignments and caching those properties just to check if the element is already processed, maintaining a call graph to propagate costly invocation mark backwards. It also seems like there are still possible pitfalls like visiting method declaration with incorrect values in .Current* properties or missing processed mark for local functions.

@@ -41,7 +42,11 @@ protected override void Analyze(IInvocationExpression expression, ElementProblem
if (info.ResolveErrorType != ResolveErrorType.OK)
return;

var method = (info.DeclaredElement as IMethod).NotNull("info.DeclaredElement as IMethod != null");

var method = info.DeclaredElement as IMethod; // e.g. localfunction declared element, (when removing `}` and method below is parsed like local function)
Copy link

Choose a reason for hiding this comment

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

The comment looks strange to me.
IInvocationExpression can be just a local function invocation in a correctly parsed code.
The reference expression can also resolve to almost anything e.g. fields, properties, local variables, parameters etc if they are delegates.
E.g.

Action a = () => { };
a();

Why mention parsing recover strategy here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Out-of-date comment, I have removed it :)

@@ -182,9 +222,12 @@ private static HashSet<IMethodDeclaration> FindHotRootMethods([NotNull]ICSharpFi

break;
case IMethodDeclaration methodDeclaration:
// check that method is hot and add it to container
Copy link

Choose a reason for hiding this comment

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

It looks like the code above skips types nested in classlike declarations not inherited from MonoBehaviour which doesn't seem intended
e.g.

class C 
{
  class NotProcessed : MonoBehaviour { ... }
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It is not possible for Unity. MonoScript should be top-level class with same name as file name.

myMaxDepth = maxDepth;
}

public override void VisitMethodDeclaration(IMethodDeclaration methodDeclarationParam, HotMethodAnalyzerContext context)
{
context.MarkCurrentAsVisited();
Copy link

Choose a reason for hiding this comment

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

It looks like partial methods will have one random declaration processed (probably not even containing the actual code)


// find all declarations in current file
var declarations = declaredElement.GetDeclarationsIn(mySourceFile).Where(t => t.GetSourceFile() == mySourceFile);
var declaration = declaredElement.GetDeclarationsIn(mySourceFile).FirstOrDefault(t => t.GetSourceFile() == mySourceFile);
Copy link

Choose a reason for hiding this comment

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

find all declarations in current file
.FirstOrDefault(...);

It looks like the comment is outdated.

I wonder why only the first declaration is processed though. It's possible to have multiple declarations of the same method with partial types. E.g.

partial class P
{
  partial void M();
}

partial class P
{
  partial void M() => Console.WriteLine();
}

Current code will process one random possibly empty declaration from the code above.

Copy link

Choose a reason for hiding this comment

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

Regarding GetDeclarationsIn(mySourceFile) call which returns declarations from other files - it looks like a copy-paste mistake in the source code for CSharpMethod. I'd recommend fixing the root problem instead of hacking it multiple times here.

@@ -359,7 +412,10 @@ public override void VisitEqualityExpression(IEqualityExpression equalityExpress

public bool InteriorShouldBeProcessed(ITreeNode element, HotMethodAnalyzerContext context)
{
return !(element is ICSharpClosure);
if (element is ICSharpClosure closure)
Copy link

Choose a reason for hiding this comment

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

It looks like this code should process local functions when their invocations are found.
Is it intended to ignore costly calls within lambda / anonymous method / query expressions?
e.g.

void M()
{
  Action a = () => CostlyInvocation();
  a();
}

Copy link

Choose a reason for hiding this comment

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

I haven't seen any code that will mark local function invocations (or any closures, altough it seems that any othe forms of closures aren't processed) as visited as the MarkCurrentAsVisited method is only called within VisitMethodDeclaration. Therefore they'll probably be analyzed multiple times. While it won't cause stackoverflow exception due to the depth limit it can cause a huge performance hit especially in case of large recursive functions since they will be analyzed MaxDepth times for each call. Please check performance for code like this:

void M()
{
  Trigger(); // start inspecting the local function

  void Trigger()
  {
    Local(); // inspect the second local function
    Trigger(); // inspect Trigger and consequently Local again MaxDepth times
  }

  void Local()
  {
    // 1000+ lines of code here, inspect them all
    Local(); // inspect Local MaxDepth times recursively
  }
}

Will this analyze the Local source MaxDepth^2 times?

classDeclaration.AddClassMemberDeclaration(field);
}

var initialization = isVoid ? factory.CreateStatement("$1;", name, expression.Copy()) :
Copy link

Choose a reason for hiding this comment

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

.Copy()

var initialization = isVoid ? factory.CreateStatement("$1;", name, expression.Copy()) :
factory.CreateStatement("$0 = $1;", name, expression.Copy());

var body = methodDeclaration.EnsureStatementMemberBody();
Copy link

Choose a reason for hiding this comment

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

I wonder how this would/should work with code like this:

class MonoBehaviour
{
  public virtual extern void Start();
}

abstract class C : MonoBehaviour
{
  public abstract override void Start(); // move something to this method
}


var localVariableDeclaration =
LocalVariableDeclarationNavigator.GetByInitial(
expression.GetContainingParenthesizedExpression()?.Parent as IVariableInitializer);
Copy link

Choose a reason for hiding this comment

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

var localVariableDeclaration =
        LocalVariableDeclarationNavigator.GetByInitial(
          ExpressionInitializerNavigator.GetByValue(expression.GetContainingParenthesizedExpression()))

if (type.IsUnknown)
type = TypeFactory.CreateTypeByCLRName("System.Object", toMove.GetPsiModule());

string baseName = type.GetPresentableName(CSharpLanguage.Instance);
Copy link

Choose a reason for hiding this comment

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

.GetPresentableName() returns a string which should represent a human-readable type name in hints/warnings. It might not be a valid identifier e.g. it could return int[] or MyType<T>.
Does naming service handle such base names? Please, add tests for such scenarios.

}

return baseName;
}
Copy link

Choose a reason for hiding this comment

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

It looks like this method tries to emulate the following:

INamesCollection namesCollection;
namesCollection.Add(GetNameRootFromStringArgument(), EntryOptions { Emphasis = Good ... });
namesCollection.Add(expression, EntryOptions { ... }); // this will add roots based on the expression's type and text

INamesCollection already has the capability to suggest names based on the expression's type.
It also can suggest names based on the expression's code. It's ignored here altough I'm not sure if it's intended.

@citizenmatt citizenmatt deleted the 183-vk-performance-stage branch November 26, 2018 10:38
@citizenmatt citizenmatt added this to the Rider 2018.3 milestone Nov 26, 2018
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