Retina.NET Performance #1

Tags: ORM, Retina.NET

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

  • 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.

  • 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?

Comments have been disabled for this content.