Andru's WebLog

//Comments about technology and software architecture
>MySelf.Persist();

June 2005 - Posts

Retina.NET Performance #2

Continuing with the performance analysis of Retina.NET and Reflection, I have found some interesting results.

In the previous post we see that in order to retrieve an entity from storage we must basically perform three operations: execute the DataReader, create the instance of the class being retrieved and populate the created instance with the DataReader. This operations consume the 42.6%, 15.4% and 20.2% respectively.

In this new test I focused on trying to minimize the costs of using Reflection, and therefore implemented some new features in Retina.NET. The first is the use of an entity factory class to minimize the instance build time avoiding the Activator.CreateInstance call, and the second new feature is the use of an field accesor interface in order to minimize the PropertyInfo.GetValue / SetValue usage.

Using a profiler I obtained the below results, where the execution tree now shows the changes:

Retina v1.0.0.7  -   Pass #1

Inclusive

Exclusive

InternalDataStoreBroker.Retrieve

100%

0.2%

     Entity.ResetIsDirty                                                                       

7.3%

0.5%

     BaseEntityPersister.Retrieve                                

91.2%

0.7%

           IDbCommand.ExecuteReader                                   

47.3%

0.1%

           UserFactory.Build                                         

10.7%

0.6%

           BaseEntityPersister.RetrieveChildEntities                 

10.7%

5.2%

           EntityStorageDefn.PopulateEntity                          

20.6%

6.4%

                  SqlDataReader.get_Item                                                 

40.5%

4.1%

                  EntityMember.SetValue                                                

43.0%

8.3%

                       Reflection.FieldInfo.SetValue                                   

79.1%

1.5%

                       User.SetValue

12.6%

50%

 

The test was run 15 times on the same machine as before (P4 2.0Gz, 1Gb Ram) and the new average execution time for retrieving 1000 entities was 1402.12ms (1.40 for each entity, a 9% less than the previous run).

Let's analyze 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 7.3% is spent in the "Entity.ResetIsDirty" method (down from 9.5%). I have made some tunning on this method and it shows, but still I have the feeling that this is too high.
  • Inside the "BaseEntityPersister.Retrieve" method we see that 47.3% of the time is spent in executing the reader. It was 42.6% in the previous test and the higher number is better because it means that we are spending less time in other things.
  • The creation of the entity by using a factory class now is consuming 10.7% of the retrieve time. The use of the factory class eliminates all reflection usage and cannot be reduced any further.
  • The population of the entity now consumes 20.6% of the total retrieve time (similar to the previous 20.2%), and fecthing of data from the reader now consumes 40.5% of the entity population time (down from 47.1%) and that means that we are doing more work setting the entity values than before.
  • Of the total entity population time, the 43% now is used in setting the entity values (a little up from 40.3%), and from that the 79.1% still corresponds to the use of Reflection used to set the read-ony fields.

The use of Reflection for setting read-only members still bothers me, but I can't find a solution for this dilemma: performance or data integrity. So, for now I choose data integrity. I have made some calculations and reach the conclusion that if I eliminate Reflection totally for setting the entity PK I would save aprox. 100ms for every 1000 retrieve operations made, witch means that each entity would load in 1.30ms instead of 1.40ms (a 7% less).

I feel that a 7% less in loading time does not justify the changes needed to eliminate Reflection for those cases, and Retina.NET will continue to use it until I found a better solution. Again, Whidbey can help in that direction.

We can point that viewing those numbers you can estimate that the 60% of the entity retrieval time is spent in executing and fetching the reader, and the remaining 40% is the overhead of using objects. From that, the 10% is consumed by the creation of the class instance. For that remaining 30%, 20% is used for populating the entity and the 10% is used populating child entities.

If you still follow me, you can conclude with me that the cost of using entity objects is aproximately 20% over using raw data readers, and that is really a low amount of overhead given the benefits.

From that you can also conclude that Retina.NET exclusive overhead is very little, and close to zero. My numbers indicates that the performance cost of using Retina.NET instead of a custom made entity and persistent code is lower than 5%. I'm very happy.

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

Posted: Jun 27 2005, 06:46 PM by andresv | with no comments
Filed under: ,
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

Posted: Jun 16 2005, 06:36 PM by andresv | with 4 comment(s)
Filed under: ,
Retina.NET status

Almost 10 days ago I published the 1.0.0.6 version of Retina.NET and I'm very happy with the feedback received.

Some stats:

  • The GotDotNet workspace member count increased from 50 to 73 74.
  • Retina.NET sources 1.0.0.6 was downloaded 75 80 times and counting (the previous version was downloaded 129 times).
  • Retina.NET is now actively used on several projects (I'm updating the list).

I'm now working on version 1.0.0.7, and so far I have made the following changes:

  • Changed StringDataType to not allow null values. Added a empty constructor.
  • Changed NullableStringDataType to add some additional constructors (min/max len).
  • Fixed the "NonPersistent" property of the "FieldStorage" attribute.
  • Fixed ROWGUID column definition for MsSql DataStore.
  • Added the "IgnoreMember" property to "FieldStorage" attribute.
  • Modified class "ChildCollection" to better support for proxy classes (lazy load).
  • Added preliminary support for Entity WebForms.

I'm focusing on fixing those remaining bugs from the new features added to the previous version, and starting to tune things up for better performance. Some of the things I have in mind are:

  • Not using reflection for accesing entity persistent properties/fields. I will be using delegates for this as they are way faster than MemberInfo.SetValue(...). In Whidbey delegates are as fast as using a virtual call (using an interface, for instance), but they are other cool tricks involving the new DynamicMethod class.
  • Using a factory pattern for every class being instantiated using reflection (StringDataType e.g.).
  • Start making some profiling and perfomance testing.

Well, thats all for now. All comments are (as always) welcome.

Best regards,

Andrés G Vettori
MCSE/MCSD/MCT
Leader of the C# Community of the
Microsoft Users Group Argentina

Posted: Jun 15 2005, 04:08 PM by andresv | with no comments
Filed under: ,
Retina.NET 1.0.0.6 Published!

Well, last nite I published the new version of Retina.NET at GotDotNet.

This new version includes a lot of hard work an several new cool features in addition to the usual bug fixes.
I have updated the QuickStart document to include a overview of the new lazy load capabilities for child entities and collections, but there are a lot of other improvements made that I will be describing in future articles as time goes.

The two most important new features are:

  • The new Lazy load support using proxies.
  • Inheritance support.

but there are also important changes in this areas:

  • Criteria objects redone to support complex expressions and parsing (thanks Daniel Aguilera!)
  • Criteria objects can include filter conditions from child entities.
  • Generation and retrieving of Identity values in the storage.
  • Support for Null child entities.
  • Better support for NullableTypes.
  • New Oracle8 DataStore, tested with Oracle 8i.
  • The Oracle DataStore renamed to Oracle9, tested with Oracle 9i and 10g.
  • The MsSql DataStore was tested with SQL 2005.
  • Added more Unit Testing cases.
  • A lot of clean-up in the code and accesibility of classes and members.
  • Changed some Namespaces to better organize code.
  • Updated ADO.NET providers used to the last version (MySql, Postgres, etc).
  • A lot of other changes / fixes.

There are still a lot of work to do, but these are generally new features that I have in mind. This version then should be considered BETA quality and I expect that any remaining bug be eliminated so we can declare Retina.NET 1.0 final release soon, and then start working in the next version.

Please download the new bits/source and let me know how this new version is doing out there. Any comments are welcome.

Best regards,

Andrés.

Posted: Jun 06 2005, 11:18 AM by andresv | with 1 comment(s)
Filed under: ,
More Posts