Re: Speed improvement using compile time generate proxies
"Andrés G. Aragoneses" <[email protected]> Mon, 01 Sep 2008 14:18:43 +0200
| Newsgroups | gmane.comp.windows.dotnet.nhibernate.devel |
|---|---|
| Message-ID | <[email protected]> |
+1
I've had similar performance issues and I would vote for this to be =
included in NH Core vNext, so let's start creating a JIRA issue for it, =
right?
Johan, wonderful research! Do you have rough benchmark numbers about the =
performance increase? A simple percentage number?
Regards,
Andr=E9s
Johan Kirsten wrote:
> Greetings
> =
> I recently had the opportunity to move a project onto NHibernate. The =
> project was using Gentle.NET. After moving the project onto NHibernate, =
> I noticed that the application had become slower. For those not familiar =
> with Gentle.NET, one of the biggest differences between Gentle.NET and =
> NHibernate is the use of runtime generated proxies. Gentle.NET does not =
> make use of runtime generated proxies, but rather method calls.
> =
> An example of an NHibernate objects would be:
> =
> * public class Invoice {
> private int _id;
> private string _reference;
> private Company _company;
> private IList<InvoiceItem> _invoiceItems;*
> ** =
> * public Invoice() {
> }*
> ** =
> * public int Id {
> get { return _id; }
> set { _id =3D value; }
> }*
> ** =
> * public virtual string Reference {
> get { return _reference; }
> set { _reference =3D value; }
> }*
> ** =
> * public virtual Company Company {
> get { return _company; }
> set { _company =3D value; }
> }*
> ** =
> * public virtual IList<InvoiceItem> InvoiceItems {
> get { return _invoiceItems; }
> set { _invoiceItems =3D value; }
> }
> }*
> ** =
> * public class Company {
> private int _id;
> private string _name;*
> ** =
> * public Company() {
> }*
> ** =
> * public int Id {
> get { return _id; }
> set { _id =3D value; }
> }*
> ** =
> * public string Name {
> get { return _name; }
> set { _name =3D value; }
> }
> }*
> =
> The equivalent for Gentle.NET would be:
> =
> * public class Invoice {
> private int _id;
> private string _reference;
> private int _companyKey;*
> ** =
> * public Invoice() {
> }*
> ** =
> * public int Id {
> get { return _id; }
> set { _id =3D value; }
> }*
> ** =
> * public virtual string Reference {
> get { return _reference; }
> set { _reference =3D value; }
> }*
> ** =
> * public int CompanyKey {
> get { return _companyKey; }
> set { _companyKey =3D value; }
> }*
> ** =
> * public Company GetCompany() {
> // Code that executes SQL and return company for current invoice
> }*
> ** =
> * public static IList<Invoice> GetList() {
> // Code that executes SQL and returns all invoices
> }
> }*
> ** =
> * public class Company {
> private int _id;
> private string _name;*
> ** =
> * public Company() {
> }*
> ** =
> * public int Id {
> get { return _id; }
> set { _id =3D value; }
> }*
> ** =
> * public string Name {
> get { return _name; }
> set { _name =3D value; }
> }*
> ** =
> * public IList<Invoice> GetInvoiceList() {
> // Code that executes SQL and returns invoices for current compa=
ny
> }*
> ** =
> * public static IList<Company> GetList() {
> // Code that executes SQL and returns all companies
> }
> }*
> =
> It is important to note that - in general and assuming lazy loading - =
> Gentle.NET and NHibernate will execute the same SQL at the same point in =
> code. For example the Gentle.NET code:
> =
> * IList invoiceList =3D Invoice.GetList();
> foreach (Invoice invoice in invoiceList) {
> Company company =3D invoice.GetCompany();*
> ** =
> * // Print invoice and company information
> }*
> =
> will request a list of invoices and the company for each invoice. =
> NHibernate will do the same:
> =
> * IList invoiceList =3D InvoiceDAO.GetList();
> foreach (Invoice invoice in invoiceList) {
> Company company =3D invoice.Company;*
> ** =
> * // Print invoice and company information
> }*
> =
> What I found was that the Gentle.NET code was faster. The only =
> difference was the runtime generated proxies.
> =
> The point I am trying to make is NHibernate takes a performance hit =
> because of runtime generated proxies. I investigated and realised that =
> the queries that were taking the longest were those retrieving lists of =
> complex objects (objects containing other objects). These were the =
> objects that used the most runtime generated proxies. I dived into the =
> NHibernate code to understand why this is happening. What I discovered =
> is that if you query a List of Invoices, NHibernate will compile a proxy =
> for each Company of each row.
> =
> To make it clear, for the first row in an Invoice results set, =
> NHibernate (using Castle's dynamic proxy) generates a Company proxy type =
> and then instantiates the proxy. For the second row, NHibernate again =
> generates a Company proxy type and then instantiates the proxy. =
> Therefore for a 100 invoices the same Company proxy type will be =
> generated 100 times. After investigation I confirmed that this holds =
> true for NH 1.2.1 and 2.0, although I suspect that NH 2.0 does not =
> suffer as much due to optimization in Castle's new dynamic proxy.
> =
> To confirm my suspisions I experimented with NH 1.2.1. I altered the =
> code of NHibernate.Proxy.CastleProxyFactory, which is responsible for =
> proxy generation. I had it store the type on its first pass and then to =
> use the stored type to instantiate each instance. I added the data member:
> =
> * private System.Type _proxyType;*
> =
> And altered the GetProxy method from:
> =
> * public INHibernateProxy GetProxy(object id, ISessionImplementor =
> session) {
> try {
> CastleLazyInitializer initializer =3D new =
> CastleLazyInitializer(_persistentClass, id, _getIdentifierMethod, =
> _setIdentifierMethod, session);*
> ** =
> * object generatedProxy =3D null;*
> ** =
> * if (IsClassProxy) {
> generatedProxy =3D =
> _proxyGenerator.CreateClassProxy(_persistentClass, _interfaces, =
> initializer, false);
> }
> else {
> generatedProxy =3D _proxyGenerator.CreateProxy(_interfaces, =
> initializer, new object());
> }*
> ** =
> * initializer._constructed =3D true;
> return (INHibernateProxy)generatedProxy;
> }
> catch (Exception e) {
> log.Error("Creating a proxy instance failed", e);
> throw new HibernateException("Creating a proxy instance =
> failed", e);
> }
> }*
> =
> to:
> =
> * public INHibernateProxy GetProxy(object id, ISessionImplementor =
> session) {
> try {
> CastleLazyInitializer initializer =3D new =
> CastleLazyInitializer(_persistentClass, id, _getIdentifierMethod, =
> _setIdentifierMethod, session);*
> ** =
> * object generatedProxy =3D null;*
> ** =
> * if (IsClassProxy) {
> if (_proxyType =3D=3D null)
> _proxyType =3D =
> _proxyGenerator.ProxyBuilder.CreateClassProxy(_persistentClass, =
> _interfaces);*
> ** =
> * generatedProxy =3D Activator.CreateInstance(_proxyType, =
> initializer);
> }
> else {
> if (_proxyType =3D=3D null)
> _proxyType =3D =
> _proxyGenerator.ProxyBuilder.CreateInterfaceProxy(_interfaces, =
> typeof(object));*
> ** =
> * generatedProxy =3D Activator.CreateInstance(_proxyType, new =
> object[] { initializer, new object() });
> }*
> ** =
> * initializer._constructed =3D true;
> return (INHibernateProxy)generatedProxy;
> }
> catch (Exception e) {
> log.Error("Creating a proxy instance failed", e);
> throw new HibernateException("Creating a proxy instance =
> failed", e);
> }
> }*
> =
> This provided a speed improvement, but this isn't a solution. I do not =
> want any delays at runtime due to proxy generation. I wanted to =
> translate all of the types at startup. But for a lot of classes this =
> would take too long and use too much memory. Then I found compile time =
> generated proxies and it fit perfectly. Check out =
> http://code.google.com/p/nhibernateproxygenerator/ by WC Pierce. I =
> downloaded the source code and altered it to compile multiple libraries =
> (my domain is spread over multiple libraries). The library it generated =
> contains a ProxyFactoryFactory which you can use by setting the =
> "proxyfactory.factory_class" property in your config file:
> =
> * <property =
> name=3D"proxyfactory.factory_class">StaticProxyFactoryFactory, =
> Library.Proxies</property>*
> =
> It worked amazingly well. The speed improved significantly. It does take =
> a while to compile the proxies. Especially if you have a lot of classes. =
> So I only plan to do this on deployment. I will continue to use runtime =
> generated proxies in my debug environment. This solution seems to work =
> very well. Please provide any thoughts and comments. I am especially =
> interested in hearing any comments from people that can point out any =
> serious downside to this approach. Are there specific reasons why =
> runtime generated proxies are prefered over compile time generated =
> proxies that I am not aware of?
> =
> Finally I want to state that I hope that the NHibernate community will =
> contemplate making compile time proxy generation a standard feature in =
> the next release of NHibernate.
> =
> Thanks
> =
> Johan Kirsten
> =
> =
> ------------------------------------------------------------------------
> =
> -------------------------------------------------------------------------
> This SF.Net email is sponsored by the Moblin Your Move Developer's challe=
nge
> Build the coolest Linux based applications with Moblin SDK & win great pr=
izes
> Grand prize is a trip for two to an Open Source event anywhere in the wor=
ld
> http://moblin-contest.org/redirect.php?banner_id=3D100&url=3D/
> =
> =
> ------------------------------------------------------------------------
> =
> _______________________________________________
> Nhibernate-development mailing list
> [email protected]
> https://lists.sourceforge.net/lists/listinfo/nhibernate-development
-------------------------------------------------------------------------
This SF.Net email is sponsored by the Moblin Your Move Developer's challenge
Build the coolest Linux based applications with Moblin SDK & win great priz=
es
Grand prize is a trip for two to an Open Source event anywhere in the world
http://moblin-contest.org/redirect.php?banner_id=3D100&url=3D/