-
Notifications
You must be signed in to change notification settings - Fork 926
Avoid memory allocation and improve performance, part 1 #522
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
Avoid memory allocation and improve performance, part 1 #522
Conversation
Thanks for starting this conversation. It looks like there are a few breaking changes (like making component disposable), which could be a problem. This PR is also quite large, it would be better to split up optimization’s into smaller PRs so we can discuss each change :) Otherwise as is it’s going to be a bit before we can work through this one. |
@dustinsoftware i think disposable component is the smallest problem :) |
1cc32fb
to
3b5e5c9
Compare
This is probably the largest pull request we've ever received :) Thanks for working on it. There's a lot of changes that change |
bbffdde
to
60d96bb
Compare
@Daniel15 Thanks you, splitted, after this PR I will create another PR |
@Daniel15, @dustinsoftware what about this PR? I have already split this request and prepared the |
When you’re ready, please open some smaller pull requests so that we can
review and merge those changes individually :)
…On Wed, Mar 28, 2018 at 12:08, Daniil Sokolyuk ***@***.***> wrote:
@Daniel15 <https://github.com/Daniel15>, @dustinsoftware
<https://github.com/dustinsoftware> what about this PR? I have already
split this request and prepared the
second part
<https://github.com/DaniilSokolyuk/React.NET/tree/avoid-memory-allocations-part2>
We are already using these changes and significantly reduced the hits in
LOH and FullGC
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#522 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AA5hFmwUqVYqIUKtOtV4BGc4NSQPnk2dks5ti9-OgaJpZM4S5xkt>
.
|
@dustinsoftware i am already split this PR, there are only 227 new lines here, and about 60% this lines is replace string.format to TextWriter.Write |
60d96bb
to
bdda3e6
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for submitting this PR! There are some good changes in here but we'll need to break this up into separate PR's even further before we can continue (if you don't have time to do that, let us know, and we can pick out some of these changes and test them individually when we have time).
From what I could tell, there are five separate improvements here:
- Use new component ID generation instead of Guid.NewGuid
- Using
StringBuilder
/TextWriter
instead ofstring.Format
to generate HTML - Cache component name validation
- Optionally skip component exists checks
- Update JSON.NET version
With each of these improvements it would be great to see how much these changes affected the benchmarks instead of testing all of them at once. It could be that some of these changes made a significant difference (such as using StringBuilder
) and others not so much (such as changing how IDs are generated).
Let me know if you'd like some more clarification or help moving this PR forward!
if (Environment.Configuration.ScriptNonceProvider != null) | ||
{ | ||
tag.Attributes.Add("nonce", Environment.Configuration.ScriptNonceProvider()); | ||
writer.Write(" nonce=\""); | ||
writer.Write(Environment.Configuration.ScriptNonceProvider()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see that we're manually constructing HTML tags in existing code.. as long as we're not passing user-generated data to these methods, this should be OK (although it would be nice to sanitize the data going into these attributes just in case)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yea, original tag builder has a similar code https://github.com/aspnet/Mvc/blob/feed0fea2c5280264ac000947b3fee542d7d9e6a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/TagBuilder.cs#L336
src/React.Core/React.Core.csproj
Outdated
@@ -37,7 +37,7 @@ | |||
<PackageReference Include="JavaScriptEngineSwitcher.Msie" Version="2.4.9" /> | |||
<PackageReference Include="JSPool" Version="3.0.1" /> | |||
<PackageReference Include="MsieJavaScriptEngine" Version="2.2.2" /> | |||
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" /> | |||
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is bumping Json.NET
required for this PR?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, split PR fail, bumping JSON.NET need in part 2 for use ArrayPool, fixed
@@ -114,31 +101,61 @@ public ReactComponent(IReactEnvironment environment, IReactSiteConfiguration con | |||
/// <param name="renderServerOnly">Only renders the common HTML mark up and not any React specific data attributes. Used for server-side only rendering.</param> | |||
/// <param name="exceptionHandler">A custom exception handler that will be called if a component throws during a render. Args: (Exception ex, string componentName, string containerId)</param> | |||
/// <returns>HTML</returns> | |||
public virtual string RenderHtml(bool renderContainerOnly = false, bool renderServerOnly = false, Action<Exception, string, string> exceptionHandler = null) | |||
public string RenderHtml(bool renderContainerOnly = false, bool renderServerOnly = false, Action<Exception, string, string> exceptionHandler = null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removing virtual
is a breaking change, can this be avoided?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we can return virtual but it does not make sense and can cause errors
because all methods and extensions uses overload with textwriter
src/React.Core/ReactComponent.cs
Outdated
{ | ||
var isValid = componentName.Split('.').All(segment => _identifierRegex.IsMatch(segment)); | ||
if (!isValid) | ||
//TODO: cache |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's remove any TODOs before merging this in
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
? string.Format("ReactDOMServer.renderToStaticMarkup({0})", GetComponentInitialiser()) | ||
: string.Format("ReactDOMServer.renderToString({0})", GetComponentInitialiser()); | ||
html = _environment.Execute<string>(reactRenderCommand); | ||
stringWriter.Write(renderServerOnly ? "ReactDOMServer.renderToStaticMarkup(" : "ReactDOMServer.renderToString("); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't look thread safe - multiple threads could call Write
at the same time to this shared instance. Keeping StringWriter
scoped to this method instead of using a static field would solve this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_sharedStringWriter is marked as ThreadStatic and this is unique for each thread. (corefx use similar strategy, https://github.com/dotnet/corefx/blob/master/src/Common/src/System/IO/StringBuilderCache.cs) , I am reuse evertything for reduce allocations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh ok, did not know about ‘ThreadStatic’ :)
@dustinsoftware, yes you right, but previous ID generating is ugly, we are allocate GUID, after allocate byte array and 1-3 string... my implementation used 1 string, |
{ | ||
EnsureComponentExists(); | ||
} | ||
|
||
var html = string.Empty; | ||
if (!renderContainerOnly) | ||
{ | ||
var stringWriter = _sharedStringWriter; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This variable declaration is unnecessary - you can just use _sharedStringWriter
here and below:
if (_sharedStringWriter == null)
_sharedStringWriter = new StringWriter(new StringBuilder(512));
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, the Write
method on StringWriter
just calls the wrapped StringBuilder
, so it would be better to just use StringBuilder
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- This variable is not unnecessary, because access to ThreadStatic variable is slow http://tips.x-tensive.com/2008/10/cost-of-threadstatic-attribute.html
- StringBuilder is not implement TextWriter, we cant use StringBuilder
{ | ||
_sharedStringWriter = | ||
stringWriter = | ||
new StringWriter(new StringBuilder(512)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This number seems unnecessarily large and might allocate more space than necessary for simple components, let's leave it unset unless there is a noticeable benefit to specifying a large value
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In part 2 i will remove all the StringWriter, StringBuilders and threadStatic and replace them with my own buffer based on ArrayPool, https://github.com/DaniilSokolyuk/React.NET/blob/c271afeb369875194a31c18bbbe6b76fac947d51/src/React.Core/ReactComponent.cs#L150
There may be consuming code that depends on the current ID format, so this isn't a good reason to change it. We unfortunately don't know how consumers are using this project and want to avoid surprises between version updates. |
Thanks for reviewing this, @dustinsoftware! |
@dustinsoftware , we can announce this change or publish major version |
That’s not the point I was trying to make... I am hesitant to merge in changes for minor improvements that are technically breaking changes. The discussion on this PR is getting too long, please open a new one for each individual change (such as ones the bulleted list above) and we can discuss them one at a time :) |
Hi @Daniel15, for more than a year we have been using this library, and recently noticed problems with allocations and object hits in Large Object Heap if the component is large or a large props is serialized
Goals
Branch with banchmarks here https://github.com/DaniilSokolyuk/React.NET/tree/bench
Note the difference between the nano seconds and microseconds
Before, Small objects
After, Small objects
Before, Large objects
After, Large objects