Speed improvement using compile time generate proxies
"Johan Kirsten" <[email protected]> Mon, 1 Sep 2008 01:42:16 +0200
| Newsgroups | gmane.comp.windows.dotnet.nhibernate.devel |
|---|---|
| Message-ID | <[email protected]> |
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 = value; }
}
public virtual string Reference {
get { return _reference; }
set { _reference = value; }
}
public virtual Company Company {
get { return _company; }
set { _company = value; }
}
public virtual IList<InvoiceItem> InvoiceItems {
get { return _invoiceItems; }
set { _invoiceItems = value; }
}
}
public class Company {
private int _id;
private string _name;
public Company() {
}
public int Id {
get { return _id; }
set { _id = value; }
}
public string Name {
get { return _name; }
set { _name = 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 = value; }
}
public virtual string Reference {
get { return _reference; }
set { _reference = value; }
}
public int CompanyKey {
get { return _companyKey; }
set { _companyKey = 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 = value; }
}
public string Name {
get { return _name; }
set { _name = value; }
}
public IList<Invoice> GetInvoiceList() {
// Code that executes SQL and returns invoices for current company
}
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 = Invoice.GetList();
foreach (Invoice invoice in invoiceList) {
Company company = 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 = InvoiceDAO.GetList();
foreach (Invoice invoice in invoiceList) {
Company company = 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 = new CastleLazyInitializer(_persistentClass, id, _getIdentifierMethod, _setIdentifierMethod, session);
object generatedProxy = null;
if (IsClassProxy) {
generatedProxy = _proxyGenerator.CreateClassProxy(_persistentClass, _interfaces, initializer, false);
}
else {
generatedProxy = _proxyGenerator.CreateProxy(_interfaces, initializer, new object());
}
initializer._constructed = 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 = new CastleLazyInitializer(_persistentClass, id, _getIdentifierMethod, _setIdentifierMethod, session);
object generatedProxy = null;
if (IsClassProxy) {
if (_proxyType == null)
_proxyType = _proxyGenerator.ProxyBuilder.CreateClassProxy(_persistentClass, _interfaces);
generatedProxy = Activator.CreateInstance(_proxyType, initializer);
}
else {
if (_proxyType == null)
_proxyType = _proxyGenerator.ProxyBuilder.CreateInterfaceProxy(_interfaces, typeof(object));
generatedProxy = Activator.CreateInstance(_proxyType, new object[] { initializer, new object() });
}
initializer._constructed = 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="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 challenge
Build the coolest Linux based applications with Moblin SDK & win great prizes
Grand prize is a trip for two to an Open Source event anywhere in the world
http://moblin-contest.org/redirect.php?banner_id=100&url=/
_______________________________________________
Nhibernate-development mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/nhibernate-development