Perf issues in RC1
Opened this issue · 4 comments
The primary thing that's going wrong here is that too much memory is being allocated. It's not anything you've done wrong in your app to cause this, but you have shown us a gap in our perf testing.
The problematic code is cases like this:
<a model="@Model" title="@Model.Headline">@Model.Headline</a>
When there's dynamic content inside of an attribute value like title
, we need to evaluate/ToString()
and then HTML encode the content, and then shove it into a buffer.
What Razor is doing wrong is encoding it character-by-character. Combined with our buffering system, this is very inefficient - each character gets ToString()
-ed again and then stuck back in the buffer. So instead of one string allocation that can't avoid and a single entry in a list, we end up with N string allocations and N entries in a list. Not good.
Again, this isn't something you've done wrong, it's out bug and a gap in our perf testing. Where we're testing cases like this, the amount of dynamic content is small.
Here's an allocation profile for 300 requests (after doing a warmup) on RC1:
Ouch! That's 2.72mb of allocations per request. At 200 rps, that's 544mb/s of memory the GC has to clean up.
Detailed view of the same for string
:
Detailed view of the same of object[]
(backing store of our buffer):
We unfortunately haven't fixed this issue yet for RC2. I'll update with numbers once I have em.
Wow! Thanks for the details 👍
Nice to know there is a reason for the numbers I'm seeing. If you need any other samples let me know!
Here's an update - with a few changes:
1). Don't encode char-by-char until we're writing to the final buffer
2). Removed a few unnecessary ViewBuffer[]
instances per request
3). Tune sizes of ViewBuffer[]
to avoid wastage
4). Avoid buffers/stringbuilders and reuse where possible
The profile will look a little different by nature because we've made a host of tweaks since RC1.
I can get about 350 rps with this now on my box
We'll continue to work on improving this until we release, I've got to clean up what I've been working on to check it in. I've already identified a few additional things from these data.
Sweet - thats def an improvement! That is a big drop in Object[] allocations. There are a lot of TagHelper related items there - does Razor use those for rendering just about everything or is that mostly from the few TagHelpers used in the example?
The taghelper related items are used every time a ProcessAsync
method is called for a taghelper. We're actively working on driving these down: aspnet/Razor#692