Early Binding vs Late Binding Performance (Revisited)
After having an interesting debate on the CRM Community forums about the performance of Early verses Late Bound entities my friend Guido Preite pointed me at a good blog post on this subject byJames Wood named 'CRM 2011 Early Binding vs Late Binding Performance'. I have always been an advocate of Early Bound types but it is true that the SDK still states in the 'Best Practices for Developing with Microsoft Dynamics CRM'
"…use of the Entity class results in slightly better performance than the early-bound entity types"
However it also states that that the disadvantages of using the late-bound Entity types is:
"…you cannot verify entity and attribute names at compile time"
I've seen many bugs introduced into code from the use of late bound types because typos can easily be introduced into the strings that are used to determine the entity and attribute logical names. Due to the productivity gains that come with Early Bound types I always recommend their use if the schema of your entities is known at compile time. There are times when this is not true or you are creating code that must run in a configurable way on many different entities or attributes in which case the late bound entity type is the only choice.
So what about performance?
- The SDK state that Late Bound types give 'slightly' better performance and states 'Serialization costs'as the reason.
- James' post states a 30% increase in speed for 200 Create operations, and <5% increased for 1500 operations.
So addressing each of these points in turn:
Although before CRM2011 there were serialization costs in using early bound types because they were serialised as part of the web service call, with CRM2011/2013 the early bound types just inherit from the Entity class and the early bound attribute properties simple set/get values from the underlying Attribute collection. The serialization when making SDK calls is effectively the same for both early and late. The main difference is due to the extra work that the OrganizationService Proxy has to do when converting the Early Bound type to the Entity type and then back against when it's received from the server. This is done using Reflection to first search for the Early bound classes and then by searching the classes for the one that matches the logical name received. This obviously will have a cost but it seems to be work that is done once per Service Channel and then cached to avoid any further cost.
James' tests are interesting but perhaps a bit misleading because the initial cost of this additional work is included in his overall speed calculations. This is probably why the overall percentage cost of early binding goes down as the number of records increases.
To remove this initial cost from the equation I adapted his code to introduce a warmup time. In tests I couldn't categorically show that either Late Bound or Early Bound had any performance difference once the OrganizationService was 'warmed up' with all reflection done and cached. In fact sometimes the test showed that early bound was quicker which leads me to believe that the main influencing factor is somewhere else like Database or Server performance. To make the results easier to interpret I have simply shown the average operation time after the warmup period. I also separated out the tests so that the Early Bound types were not compiled and picked in the Late Bound tests.
Each test was a warm up of creating 400 records and a run of creating 500 records.
Conclusions
Whilst it is true that using Early Bound classes incurs some cost of additional 'plumbing' – assuming that you are caching the WCF Service Channels (which the Microsoft.Xrm.Client.Services.OrganizationService does for you) because the difference in speed is so small (< there really is no reason not to use the Early Bound classes unless you have performance related issues and want to eliminate this as the cause.
If you are interesting, here is the code I used (based on James' code)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
| static void Main( string [] args) { int warmupCount = 400; int runCount = 500; CrmConnection connection = new CrmConnection( "Xrm" ); var service = new OrganizationService(connection); CreateAccounts( "Early Bound Test" , warmupCount, runCount, () => { Account a = new Account(); a.Name = "Test Early Vs Late" ; service.Create(a); }); CreateAccounts( "Late Bound Test" , warmupCount, runCount, () => { Entity e = new Entity( "account" ); e[ "name" ] = "Test Early Vs Late" ; service.Create(e); }); TidyUp(); Console.WriteLine( "Finished" ); Console.ReadKey(); } static void CreateAccounts(String name, int warmup, int runs, Action action) { Console.WriteLine( "\n" + name); // Warm Up for ( int i = 1; i <= warmup; i++) { if (i % 10 == 0) Console.Write( "\r{0:P0} Warmup " , ((( decimal )i / warmup) )); action(); } // Run Test double runningTotal = 0; Stopwatch stopwatch = new Stopwatch(); for ( int i = 1; i <= runs; i++) { stopwatch.Reset(); stopwatch.Start(); action(); stopwatch.Stop(); runningTotal += stopwatch.ElapsedMilliseconds; double runningAverage = runningTotal / i; if (i % 10 == 0) Console.Write( "\r{0:P0} {1:N1}ms " , ((( decimal )i / runs)), runningAverage); } } |
No comments:
Post a Comment