PDA

View Full Version : Very poor performance using Spring Web Framework



bjornwang
12-04-2007, 08:43 AM
In an earlier projects using the Spring Web Framework, we experienced extremely poor performance due to Spring trying to inject every single control used on our web pages.

Tracing the code using DotTrace showed the culprits:
- InjectDependenciesRecursive was called 1400+ times for some pages.
- InjectDependenciesRecursiveInternal was called 11000+ times.

That particular page had a large GridView containing many nested controls, all of which Spring attempted to inject. Needless to say, rendering of that page in particular was very slow.

Erich Eichinger came to our rescue and explained how it all works:
"By default Spring replaces the page's ControlCollection with it's own DI-aware collection - and this process is cascaded as additional controls are loaded and added to the collection."

Erich then recommended us to implement the ISupportsWebDependencyInjection interface to control whether dependency injection (DI) is performed on page controls (thus increasing performance).

Our solution was to let all pages inherit from an AbstractBasePage, which implemented the interface like this:


public abstract class AbstractBasePage : Page, ISupportsWebDependencyInjectionThis tells Spring that the page will handle DI of controls itself, thus overriding Spring's default mechanism for recursive DI of controls. Since our AbstractBasePage does not attempt to perform DI on controls, DI is effectively turned off for child controls. (Check out this page for info on how actually do DI yourself: [http://www.springframework.net/docs/1.1-RC2/sdk/2.0/html/topic12141.html].)

Question:
The solution described above works, but ties all our pages to Spring because we have to implement Springs interface. My question is whether better solutions have surfaced in newer versions of Spring? For example:

- Has DI of controls become faster in Spring RC2, so that this isn't a problem anymore?
- Can DI of controls be configured from the Spring XML files now (on/off, depth)?
- Are there other, more elegant solutions we can use?

Erich Eichinger
12-05-2007, 07:49 PM
Hi,


I - Has DI of controls become faster in Spring RC2, so that this isn't a problem anymore?There have been a couple of performance-related improvements to web DI. Maybe you want to give Spring.Web another chance and let me know your results. As you might remember I'm more than willing to help.

If you are really looking for performance I recommend not using GridView at all, since - from performance viewpoint - this is the worst control of all. Replacing GridViews with e.g. DataLists will make your application much more resource-friendly.


- Can DI of controls be configured from the Spring XML files now (on/off, depth)?
Control DI can be configured as described in the reference docs (http://www.springframework.net/doc-latest/reference/html/web.html#web-di-controls). Atm there is no way to control DI depth or turn it off aside from implementing ISupportsWebDependencyInjection yourself.

Of course I'm all ears for suggestions on how to configure/control DI for particular cases and would be glad to hear your opinion. I simply didn't have much time to think about a solution yet.


- Are there other, more elegant solutions we can use?
E.g. separate your pages by configuring 2 <httpHandler>s:


<httpHandlers>
<add verb="*" path="*_di.aspx" type="Spring.Web.Support.PageHandlerFactory, Spring.Web"/>
<add path="*.aspx" verb="*" type="System.Web.UI.PageHandlerFactory"/>
</httpHandlers>this will cause only pages with the "_di" postfix to take part in dependency injection.

It is too late to fix this for the upcoming 1.1 release. I recorded it to JIRA as SPRNET-794. (http://opensource.atlassian.com/projects/spring/browse/SPRNET-794)

hope this helps,
Erich

Erich Eichinger
12-06-2007, 03:52 PM
Hi Bjorn,

I just had another idea on how to better control DI on controls. See my post over here (http://forum.springframework.net/showthread.php?p=9921#post9921) and checkout solution variant 2 for more.

hope this helps,
Erich

ahbeng
01-04-2008, 01:08 AM
Hi guys sorry for this newbie question but Bjorn quoted Erich as saying "By default Spring replaces the page's ControlCollection with it's own DI-aware collection".

By looking at the config files, I initially thought Spring only injected objects I specify. So for other asp controls like GridView, Labels etc don't they just get processed as per normal?

Erich Eichinger
01-04-2008, 09:45 AM
Hi,

to enable DI for ASP.NET controls, we faced an important problem: Instantiation of controls happens outside the container. To cope with this, DI is done in 2 steps:

1) The ControlCollection of any control, that is added to the hierarchy, is dynamically replaced with a DI-aware ControlCollection

2) This ControlCollection instance checks each added control, if there is a corresponding object definition in the container. If one is found, the control will be configured. Otherwise just do step 1)


Normally this works without noticable delay but in the case of a GridView, that potentially creates hundreds of child controls, this may cause a remarkable performance degradation.

Finding an alternative, but still non-intrusive solution is on my plate and will definitely be part of an upcoming Spring.NET release. I'll ping back on this thread as soon as I commit something.

hope this helps,
Erich

sideout
02-29-2008, 04:59 AM
So if I wanted to turn off DI on a whole page could I just have my page implement ISupportsWebDependencyInjection and then comment out the calls to WebUtils.InjectDependenciesRecursive in Add, AddAt,
AddedControl from the implementation examples in http://www.springframework.net/docs/1.1-RC2/sdk/2.0/html/topic12141.html? Then for pages where I do want it I'd just not use the base page that implements it? This seems neater to me than using a naming convention for the page. I think the documentation could use some more examples on custom injection strategies or disabling rather than just showing the default implementation.

Could I also do something like this to specifically ignore GridViews as they have lots of nested controls? Is this sort of type checking slow?
if (!child is GridView)
WebUtils.InjectDependenciesRecursive...

Just to confirm, if I do manual object lookups in a Spring.NET app context I won't need to use the httphandler and won't have any performance hit on heavy nested control pages at all, correct?

Thanks.

Erich Eichinger
03-06-2008, 08:40 AM
Hi all,

I added a new <spring:Panel> server control, which has an attribute "suppressDependencyInjection". By wrapping the performance sensitive parts of your page within this panel, you can easily turn off DI:



<spring:Panel runat="server"
suppressDependencyInjection="true"
renderContainerTag="false">

.. put your heavy controls here - they won't be touched by DI

</spring:Panel>


Note: In contrast to <asp:Panel>, by default <spring:Panel> won't render a container tag (<div>, <span> etc.). You can modifiy this behaviour by setting the attribute "RenderContainerTag" accordingly.

I will update the documentation regarding this asap.



Is this sort of type checking slow?
if (!child is GridView)
WebUtils.InjectDependenciesRecursive...


Compared to what else is happening during a request, I don't think that such checks cause measurable performance degradation.

hope this helps,
Erich

sideout
03-07-2008, 12:28 AM
Thanks Erich. That looks very handy for pages where I want DI to work in general, but have areas of heavily nested controls.

I'm still trying to generate a base class that turns off DI for anyone implementing it just to make it easy to have it off by default without relying on page naming conventions (almost our entire app implements a Page derived base class so this would be a good point to turn DI off by default) and haven't been successful. My properties are still injected. Here's what I've created:


public class SpringNoDIPage : Page, ISupportsWebDependencyInjection
{
public SpringNoDIPage()
{
}

#region ISupportsWebDependencyInjection Members

private IApplicationContext _defaultApplicationContext;
public IApplicationContext DefaultApplicationContext
{
get { return _defaultApplicationContext; }
set { _defaultApplicationContext = value; }
}
#endregion

protected override void AddedControl(Control control, int index)
{
//Note that this is WebDependencyInjectionUtils and not WebUtils. The docs are out of date. //WebDependencyInjectionUtils.InjectDependenciesRecu rsive(_defaultApplicationContext, control);
base.AddedControl(control, index);
}
}


public partial class Test_Spring_SpringNoDI : SpringNoDIPage
{
private string _TestMessage;

public string TestMessage
{
get { return _TestMessage; }
set { _TestMessage = value; }
}

protected void Page_Load(object sender, EventArgs e)
{
Response.Write(String.Format("TestMessage Local Property: {0}<br>", TestMessage));
}
}


<object type="Test/Spring/SpringNoDI.aspx">
<property name="Title" value="Spring No DI Title"></property>
<property name="TestMessage" value="Hello From Spring No DI"></property>
</object>

Erich Eichinger
03-08-2008, 07:10 PM
Hi,

as long as you configure Spring's PageHandlerFactory in the <httpHandlers> section, your *page* will be populated with configured values.

But your implementation will successfully prevent any *control* on this page to get injected.

cheers,
Erich

sideout
03-09-2008, 03:52 AM
Excellent. I put a giant gridview on my page and confirmed my NoDI page is working as expected and disabling injection. My DI version of the page with a giant gridview has a 25 second extra delay. Thanks.

Erich Eichinger
03-09-2008, 10:10 AM
Hi,

to make DI work on selected controls on your NonDIPage, you can again wrap them within a <spring:Panel> and set "suppressDependencyInjection='false'".

-Erich

sideout
03-09-2008, 03:22 PM
Yes that's what I'll likely be doing. Thanks.

In case anyone is thinking of trying the "control is GridView" check, it turned out to not be so simple as the GridView may be buried in the Controls hierarchy. You'd actually have to make a clone of the whole hierarchy passed in to AddedControl removing any GridViews and passing your copy to WebDependencyInjectionUtils.InjectDependenciesRecu rsive and the original hierarchy to base.AddedControl. It doesn't seem worth the trouble so I'll be using the spring:Panel where needed.

Erich Eichinger
03-09-2008, 03:23 PM
Hi,

unfortunately using <spring:Panel> without Spring's PageHandlerFactory had a bug. This is now fixed and it will work with the next nightly build.

This means you can choose between "optimistic" and "pessimistic" DI in webapplications now.

cheers,
Erich

Erich Eichinger
06-08-2008, 08:02 PM
Hi all,

I'd just like to mention that I revisited Web DI performance and at least for net-2.0 you shouldn't notice much of a delay anymore. Unfortunately the technique I used isn't applicable for net-1.1, but I could still achieve a remarkable performance improvement there as well.

For testing it, I created 1000 DataGrids containing 100 rows with 3 colums resulting in 300000 controls. On my notebook this gives:

net 1.1 - no DI : 0.5s
net 1.1 - w/ DI : 14.5s
net 2.0 - no DI : 0.6s
net 2.0 - w/ DI : 1.4s

before the improvement it took ~35s.

enjoy!

-Erich

sideout
06-09-2008, 12:57 PM
Using a spring:panel or doing nothing special at all? What version of the framework?

I had just load tested an app with 1.1.1 making use of GridViews, etc using 1.1.1 and CPU usage spiked between 60%-100%. When I disabled DI using the override implementing ISupportsWebDependencyInjection it went down to <20% and very steady.

Erich Eichinger
06-09-2008, 01:23 PM
Hi,

sorry, forgot to mention that: You need to update to one of the nightly builds (http://www.springframework.net/downloads/nightly/) after May,17 (which in fact means Spring.NET-20080527-1037.zip atm). There's no need for panel and implementing ISupportsWebDependencyInjection then.

If you could rerun your tests, I'd be happy to hear your feedback!

cheers,
Erich

sideout
06-10-2008, 02:50 PM
It's too late for us to put a new version (and a nightly) in our app, but I'll try to sneak a just for test build into load testing if I can in a week or so. :)

Erich Eichinger
06-10-2008, 09:43 PM
hi,

that'd be great!

tx,
Erich

sideout
07-15-2008, 05:07 PM
I'm running load tests this week. I'm guessing your change has made it into the 1.1.2 release build and I can just test against that?

Erich Eichinger
07-15-2008, 05:21 PM
Hi,

Cool, thanks! No, unfortunately it didn't make it. 1.1.2 was released on 6th May. I committed the fix on the 17th (see my post above)

-Erich

sideout
07-15-2008, 08:29 PM
I downloaded the latest nightly 20080711 1.1.2.20192. Unfortunately the results from load testing weren't good. This build is only slightly better than 1.1.1 with regards to this performance problem. Here are some select values observed during steady state of the load test:

Spring 1.1.1 w/o ISupportsWebDependencyInjection

cpu 50-80
memory pages/sec - 12
post/s 30
get/s 98
events/s 110
req/s 110
% time GC - 0.6
% disk time - 4-10

---

Spring 1.1.1 w/ ISupportsWebDependencyInjection

cpu 12.8
post/s 34
get/s 102
events/s 110
req/s 110
% time GC - 0.3
% disk time - 2

---

Spring 1.1.2.20192 20080711 nightly w/ ISupportsWebDependencyInjection

cpu 13.7
post/s 33
get/s 97
events/s 110
% time GC - 0.3
% disk time - 2

---

Spring 1.1.2.20192 20080711 w/o ISupportsWebDependencyInjection

cpu 40-65
memory pages/sec - 15
post/s 30
get/s 98
events/s 110
req/s 110
% time GC - 0.3
% disk time - 4-10

---

w/ ISupportsWebDependencyInjection the CPU was relatively stable. Without it, CPU fluctuated through a wide range even hitting 100% at times. I noted the common band of values.

As far as the load test itself it was a 500 user test hitting the majority of the pages on our site with a fair amount of repeaters, gridviews, etc.

Erich Eichinger
07-15-2008, 08:33 PM
Hi,

Thanks for testing! Are you using net 2.0 or 1.1? The improvement only affects 2.0.

-Erich

sideout
07-15-2008, 09:09 PM
.Net 2.0 SP1
Windows Server 2003 SP2
IIS 6

Erich Eichinger
07-15-2008, 10:10 PM
Hi,

strange....

my unit tests show a significant difference. Please double check you are using the right Spring assemblies for your tests.

Do you think you can provide me a demo app reproducing the issue? Maybe the root cause of this pb is somewhere else? Try using e.g. dotTrace (http://www.jetbrains.com/profiler/)to find the performance hotspots.

Thanks a lot for your time!

cheers,
Erich

sideout
07-16-2008, 02:20 PM
Yes I had the right assemblies. I was using the debug assemblies in all cases.

Have you tested under heavy simultaneous load? It might be an issue that is not visible with just one user regardless of how deeply nested the control structure is, but for some reason manifests when multiple instances are run simultaneously.

The only difference in the builds I tested was the Spring version and ISupportsDependencyInjection. It may be something in particular I'm doing with Spring, but the performance difference is definitely just due to Spring.

The app is huge so I'd have to make some sort of test page that exhibits the same behavior and run it under a load test and I don't have the time to do this at this point. I realize I didn't give you a lot of information to go on and I'll see what I can do in a month or so to pinpoint more if I can. I just wanted to give you a heads up in case anything obvious came to mind.

Erich Eichinger
07-16-2008, 05:43 PM
Hi,

thanks a lot for your investigation. I'll have another look and check, if it is a threading issue.

Are you using lots of spring databinding in your datagrid cells? If you can afford another 10 minutes, please fire up dotTrace, run your loadtests and check the "hotspots" then - maybe this immediately leads us to the root cause of your issue.

cheers,
Erich

Erich Eichinger
07-21-2008, 11:48 AM
Hi Alec,

first of all: Thanks a lot for your time & help in nailing down the issue.

The likely solution in brief: Turn off logging for "Spring.Util.HttpContextSwitch"

While investigating the issue, I found that much of the time was consumed by accessing the HttpRequest.FilePath property. This is caused by setting the log level of the Spring.Util.HttpContextSwitch to >=DEBUG. Reducing the log level for this class solves at least much of the performance issue. Please post here, if this solved your issue.

I found some other, minor "suboptimal" things, namely the case-insensitive implementation of the Hashtable returned by CollectionsUtil.CreateCaseInsensitiveHashtable(). Spring.Core now contains a twice-as-fast Spring.Collections.CaseInsensitiveHashtable that is specifically designed for case insensitive string keys. Of course this is only measurable, if (really) lots of calls hit your hashtable. 3*10^7 key lookups on my notebook take

Duration w/o optimization: 00:00:11s
Duration w/ optimization : 00:00:05s

You can find the improvements it in the latest nightly builds.

cheers,
Erich

sideout
07-21-2008, 07:26 PM
Erich's latest changes fixed the performance issue now without doing the ISupportsWebDependencyInjection workaround. Great work!