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/