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

Major Roslyn Improvements to Memory and Running Time #25

Merged
merged 2 commits into from
Aug 14, 2020

Conversation

TravisOnGit
Copy link
Member

Roslyn Improvements Summary:

  • Memory consumption down from 75MB per unique expression to a flat 25MB plus 0.3MB~ per unique expression. This is a 250x improvement or 0.4% memory consumed.
  • Running time of first execution down from 2 seconds for each unique expression to a flat 2 seconds plus 15ms per unique expression. This is a 133x improvement or 0.75% running time.
  • All changes are under the covers. No changes required by applications using Forge.

Feature breakdown:

  • ParentScript

    • The parentScript that, upon initialization, gets Created, RunAsync, and added to the ScriptCache.
    • All Executed expressions are continued from the parentScript, avoiding additional compiles.
    • Before improvement - each unique expression would cost 25MB, take 2 seconds on first execution, then 0ms on further executions.
    • After improvement - only the parentScript costs 25MB and takes 2 seconds. Each unique expression costs < 0.5MB, takes 15ms on first execution, then 0ms on further executions.
  • ParentScriptTask

    • The parentScriptTask kicks off initializing the Roslyn parentScript asynchronously, allowing ExpressionExecutor to construct very quickly.
    • This initialization time is saved if the application takes time to initialize before the first WalkTree call.
    • Before improvement - 1800ms for first initialize.
    • After improvement - 2ms for first Initialize. 0ms for further initializes (new TreeWalkerSessions using same ScriptCache).
  • ParentScriptCode

    • Code that Forge uses to prime the parentScript on initialization.
    • Evaluating some code moves roughly 600ms from the first expression to asynchronously initializing the parentScript.
  • ScriptCache rework

    • ScriptCache now caches Scripts that come from the parentScript.ContinueWith. ScriptCache still caches one Script per unique Expression, but these no longer get compiled.
    • ScriptCache now caches the ParentScript as well on initialization.
  • MetadataReferenceResolver

    • This added ScriptOption causes Roslyn to not load missing references.
    • Before improvement - it costs about 75MB to initialize, then 0.5-1.5MB per unique expression.
    • After improvement - it costs about 25MB to initialize, then 0.05-0.5MB per unique expression.

…ession to a flat 25MB plus 0.3MB~ per unique expression. Running time of first execution down from 2 seconds for each unique expression to a flat 2 seconds plus 15ms per unique expression.
Copy link

@abhayketkar abhayketkar left a comment

Choose a reason for hiding this comment

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

:shipit:

if (forgeState == null) throw new ArgumentNullException("forgeState");
if (callbacks == null) throw new ArgumentNullException("callbacks");
if (token == null) throw new ArgumentNullException("token");

this.SessionId = sessionId;
this.JsonSchema = jsonSchema;
this.ForgeTree = forgeTree;
Copy link
Collaborator

@zhengmu zhengmu Aug 14, 2020

Choose a reason for hiding this comment

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

just to confirm, looks like you just switched line 140-152 with line 169-181. Besides these, only some comments are changed, right? #Closed

Copy link
Member Author

Choose a reason for hiding this comment

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

I swapped the constructors to put the ForgeTree one on top. Just because it is preferred to use that one. No functional changes.


In reply to: 470868342 [](ancestors = 470868342)

/// </summary>
private List<Type> dependencies;
public static string ParentScriptCode = "(1+1).ToString()";
Copy link
Collaborator

@zhengmu zhengmu Aug 14, 2020

Choose a reason for hiding this comment

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

"(1+1).ToString()" [](start = 48, length = 18)

nit: consider to add the comment why to add the NO-OP statement. #Closed

Copy link
Member Author

Choose a reason for hiding this comment

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

I've outlined the various feature improvements on the new fields/properties. I describe more about the parentScript below. I didn't do deep investigation into how or why adding code here works. I just noticed it in the testing. It didn't matter too much what the code I put here was. I seemed to get slightly better times if the logic here matched the first executed expression logic, such as doing arithmetic. But since the improvements were small and it entirely depends on the unique ForgeTree that each application uses, I don't think its worth any more investment to investigate and improve.


In reply to: 470868792 [](ancestors = 470868792)


/// <summary>
/// Roslyn script options.
/// The ScriptCache is used to cache and re-use Roslyn scripts.
/// Scripts include the parentScript, as well as all unique expressions that get Executed.
/// </summary>
Copy link
Collaborator

@zhengmu zhengmu Aug 14, 2020

Choose a reason for hiding this comment

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

[](start = 12, length = 10)

consider to add your comment in the code review description:
ScriptCache now caches Scripts that come from the parentScript.ContinueWith. ScriptCache still caches one Script per unique Expression, but these no longer get compiled. #Closed

Copy link
Member Author

Choose a reason for hiding this comment

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

Added more detail to ScriptCache description.


In reply to: 470870229 [](ancestors = 470870229)

@zhengmu
Copy link
Collaborator

zhengmu commented Aug 14, 2020

/// </summary>

actually, consider to add your code review comments here, starting with "Roslyn Improvements Summary:....". Those perf numbers are good reference/baseline for future improvement if needed. #Closed


Refers to: Forge.TreeWalker/src/ExpressionExecutor.cs:24 in 9ed42b2. [](commit_id = 9ed42b2, deletion_comment = False)

@TravisOnGit
Copy link
Member Author

/// </summary>

The history of this PR will not go away, so we can look back in the description to find the original notes if needed.
In general, I want the comments to reflect the current state of the code. I did include a brief note about the improvement numbers on parentScript, but I want to avoid adding too much historical info.


In reply to: 674277179 [](ancestors = 674277179)


Refers to: Forge.TreeWalker/src/ExpressionExecutor.cs:24 in 9ed42b2. [](commit_id = 9ed42b2, deletion_comment = False)

Copy link
Collaborator

@zhengmu zhengmu left a comment

Choose a reason for hiding this comment

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

:shipit:

@TravisOnGit TravisOnGit merged commit ff083e0 into master Aug 14, 2020
@TravisOnGit TravisOnGit deleted the feature/RoslynImprovements branch December 9, 2020 05:48
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

3 participants