-
Notifications
You must be signed in to change notification settings - Fork 2.1k
[Perf] ViewDataDictionary is copied and resized many times #878
Comments
@NickCraver please note your last suggestion would undermine the contract MVC upholds w.r.t. Therefore the solution to your last issue must involve copy-on-write semantics. This would be relatively straightforward since |
@dougbu ah true - this isn't a concern for us at all since the instances are 1:1 with their usages here, but I see how that's a definite blocker as-is. It would be good if we could at least narrow them quite a bit as well...not even get to this step. The multiple instances on the base could be eliminated just by not instantiating the hidden implementations in the I'm proposing changing this: public override void InitHelpers()
{
base.InitHelpers();
Ajax = new AjaxHelper<TModel>(ViewContext, this);
Html = new HtmlHelper<TModel>(ViewContext, this);
} To this: public override void InitHelpers()
{
Url = new UrlHelper(ViewContext.RequestContext);
Ajax = new AjaxHelper<TModel>(ViewContext, this);
Html = new HtmlHelper<TModel>(ViewContext, this);
} This takes us down from 5 to 3 copies with minimal changes. While the write-on-change semantics are the way to narrow it down as much as possible while keeping the contract, this would be a quick win in performance. Since they're |
@NickCraver bug got assigned to @pranavkm, we will get a PR out in the next couple of weeks. |
We are going to also look at current MVC after fixing this issue - https://aspnetwebstack.codeplex.com/workitem/2085 |
There's quite a few changes in the execution model in Mvc 6, which makes some of the analysis here not particularly applicable.
At this stage, something along the lines of the copy-On-write might be the most impactful change. |
instead of eagerly copying. Partial fix for #878
instead of eagerly copying. Partial fix for #878
We decided to leave the behavior discussed in #954 until we can see a strong reason to change it to the Mvc 5 behavior. That said, it would be incorrect to port this behavior back to Mvc 5. The plan would be to to investigate the impact suggested in #878 (comment) in Mvc 5 in addition to Copy-On-Write that we have in Mvc 6. The bug on CodePlex (https://aspnetwebstack.codeplex.com/workitem/2085) links back to this location so we can make use of the discussion available here. |
I ported over the CopyOnWriteDictionary changes to Mvc 5 and additionally was able to defer the creation of the two helpers on the base type that are discussed in #878 (comment). |
Thanks guys, looking forward to testing this soon as I'm able! |
@NickCraver what a great suggestion. The perf test was very happy with you :) |
When we spin up a child view, we're seeing many copies of the
ViewData
dictionary. Currently instead of 1 copy for the child view (we don't even want in many cases, but given whereModel
lives, that's a non-trivial change for the<T>
), we see 5 copies in total. This is in reference to Issue #870.4 of the 5 come from creations of
AjaxHelper<T>
andHtmlHelper<T>
both of which have aViewDataDictionary<T>
constructor called internally.In the non-generic
WebViewPage
there are 2 copies made:And in
WebViewPage<T>
there are 2 additional copies in theInitHelpers()
override:Also in
WebViewPage<T>
in theSetViewData()
override is another copy:Since
ViewDataDictionary<T>
performs a shallow copy in its baseViewDataDictionary
constructor here:We get a shallow copy of the dictionary. This has 2 issues for us:
T Model
when it comes down to it, times 4 here.Dictionary<string, object>
is resized every time it's actually used, this is because it's initialized at a capacity of 0:Number 2 means even for a few values (we typically have 2-4) we are eating a dictionary
.Resize()
5 or more times, per view. We have hundreds of views or more on many pages so this stings pretty hard.The way I see it, the best solution is probably to create the
Dictionary<string, object>
differently: in the pipeline the constructor would create a properly sized one via thenew Dictionary<string, object>(IDictionary<string, object> dictionary)
constructor, instead of inflating one from 0 size. Thenew
hiding on theAjax
andHtml
"overrides" inWebViewPage<T>
overWebViewPage
create an additional 2 copies...can these not simply reference theViewDataDictionary<T>
from theWebViewPage
rather than copying their own? I can't find a case where these should actually differ but I may certainly be missing something.Assuming they still need their own shallow copy, the top of
ViewDataDictionary
could still look like this:This takes care of the
Resize()
pain aspect in a fairly concise way, but not the 5x work and allocation issue. I'd propose thatHtmlHelper
andAjaxHelper
simply set the reference if theTModel
matches already, something like this:Doing this in both
AjaxHelper
andHtmlHelper
would again be fairly concise changes with hopefully no negative impact but still have a huge impact on allocations at our case.The text was updated successfully, but these errors were encountered: