Retina.NET Performance #1
Today I started the performance analysis of Retina.NET and mostly obtained the expected results, and a couple of surprises.
I will not surprise anyone if the results indicates that reflection is paying a relative high cost when retrieving an entity from storage. It was expected and the profiling shows it crystal clear.
The test consisted on retrieving a medium complexity entity called "User" from an MSSQL 2005 database. The test was run 15 times and each run consisted of saving a new entity and retrieving it 1000 times.
This "User" entity has 12 properties (mostly strings) and a child entity of type "Currency" that is configured to use lazy load. The entity also has several constrints and triggers configured. If you want to see the code of the "User" class you can open the "Test" project in the Retina.NET VS.NET solution and look for the "User.cs" file.
Using a profiler I obtained the results I wanted, and here I show a simplified execution tree and the most important discoveries:
Retina v1.0.0.6 Inclusive Exclusive InternalDataStoreBroker.Retrieve 100% 0.2% Entity.ResetIsDirty 9.5% 0.3% BaseEntityPersister.Retrieve 89.2% 0.7% IDbCommand.ExecuteReader 42.6% 0.1% Activator.CreateInstance 15.4% 0.3% BaseEntityPersister.RetrieveChildEntities 12.8% 3.9% EntityStorageDefn.PopulateEntity 20.2% 6% SqlDataReader.get_Item 47.1% 4.9% EntityMember.SetValue 40.3% 8.5% Reflection.FieldInfo.SetValue 91.5% 4.5%
The test was run 15 times on my dev machine (P4 2.0Gz, 1Gb Ram) and the average execution time for retrieving 1000 entities was 1564.60ms (1.54 for each entity), not too shaby at all for absolutely no tunning.
Let's go back to the execution tree. I marked in bold the entries that deserve more attention, so move on to explain a little more of what is going on:
- Of the total retrieve time 9.5% is spent in the "Entity.ResetIsDirty" method. Well, that's a surprise. I will have to evaluate carefully this to get rid of this time. Maybe the call to this method is totally innecesary because it only makes sure that all newly retrieved entitied are flagged as non-dirty.
- Inside the "BaseEntityPersister.Retrieve" method we see that 42.6% of the time is spent in executing the reader. Nothing to do here. The rest of the time is used in creating the new entity (Activator.CreateInstance), populating the entity (filling its properties from the reader) and retrieving child entities.
- The creation of the entity by using reflection is consuming 15.4% of the retrieve time. We can do better than that. I think that we can use some factoy pattern here so each entity is created faster and avoid reflection.
- The population of the entity eats 20.2% of the total retrieve time, and nearly half of that time is used by setting the read values on the entity properties. The rest of the time is used reading from the reader, so we can't do much in this area. As I mentioned before I will make some tests using delegates for setting/getting entity values as they are some orders of magnitude faster than reflection. Whidbey have some more tricks in the sleeve about this, but not rush things here.
Well, as can be seen there are plenty of things to do about performance in Retina.NET, but all in all the times measured not seems to be that bad. It feels pretty fast indeed....
I will keep you posted on the advances of Retina.NET in this department. As always, any comments are welcome.
Best regards,
Andrés G Vettori
MCSE/MCSD/MCT
Leader of the C# Community of the Microsoft Users Group Argentina
4 Comments
Comments have been disabled for this content.
David Hayden said
Before you totally rule out reflection, you could probably drastically improve performance by caching the type right after you do a CreateInstance. Then on subsequent re-creations of the type, you can grab the type from the cache as opposed to re-creating it using reflection each time.
I would love to see the new performance numbers after you do that.
Setting values on private members is also slower then setting values on properties. Thus, if Retina attributes are not set on private members, that will also increase performance.
My guess is that after implementing type caching and wisely choosing the location of attributes, the performance hit will basically be in the database access and every thing else will be "negligible".
Andrés G Vettori said
Lucky me, Retina.NET is already doing that, or my performance number would be a lot worse.
As you can see there is no reference to any GetMember, GetField or GetProperty in the call graph, and that is because all member instances are cached.
Please see the EntityStorageDefn/FieldDefn classes for more details.
Thanks for your comments,
Andrés.
Andrés G Vettori said
I'm using the CLR Profiler 2.0 (MS, free) and DevPartner Profiler Community Edition (also free from Compuware).
Excellent tools, I recommend both.
Andrés.
Travis said
"Setting values on private members is also slower then setting values on properties. "
I have run tests trying to prove this, but I am getting much better performace setting values to private members than public properties. What am I missing here?