A few tests have indicated that there is a performance benefit obtained when the context is cached and re-used in multiple queries. Profiles indicate that the performance difference when the context is cached is mostly from the call to InitializeContext,
which is slowed down in part by the call to LazyInternalConnection.Initialize.
Attached is a project which serves as a performance test of two scenarios: when we create a new context to serve each query request, and when we have one context per thread to serve all query requests for that thread. By running this test (fully ngen’ed machine,
power setting = high performance, test bits built for release-any cpu) I experienced what I was expecting in my personal computer using a modified version of EF that is not affected by the performance issue described in
• When a new context is created per request I was getting close to 1215 queries per second, stable.
• When the test used one context per thread I was getting close to 1515 queries per second, stable.
That’s 25% increased throughput just by caching the context. This gap can be reduced by making the context initialization more nimble. Step one is to lower the cost of the calls to LazyInternalConnection.Initialize. It is currently spending 8.3% of all CPU
=> DefaultModelCacheKeyFactory.Create (8.3%)
==> LazyInternalContext.get_ProviderName (8.3%)
====> LazyInternalConnection.get_ProviderName (8.3%)
=====> LazyInternalConnection.Initialize (8.0%)
=====> InternalConnection.get_ProviderName (0.3%)
In this case it appears like the lazy internal context initializes a connection only to get the provider name during initialization. My two suggestions for us to lower this cost are:
- Given a context’s metadata is cached, could we also cache the value returned by InternalConnection.get_ProviderName so the cost of InitializeContext is lower?
- Could we also lower the cost of LazyInternalConnection.Initialize?
A closer look at LazyInternalConnection.Initialize tells us that:
=> LocalDbConnectionFactory.CreateConnection (4.6%)
==> SqlConnectionFactory.CreateConnection (2.8%)
===> DbConectionStringBuilder.get_ConnectionString (1.3%)
=> InternalConnection.OnConnectionInitialized (3.0%)
==> DbConnectionStringBuilder.ToString (1.6%)
===> DbConectionStringBuilder.get_ConnectionString (1.5%)
==> InternalConnection.AddAppNameCookieToConnectionString (1.4%)
Pretty much all of the CPU used by LazyInternalConnection.Initialize is spent doing two things:
- Obtaining the connection string in order to create a connection.
- Once the connection is created, it gets the connection string again in order to append an App Name Cookie to it.
I think it’s reasonable to believe that the LazyInternalConnection.Initialize process can be sped up by making it faster to retrieve the connection string using a cache, and possibly by avoiding repeating the same string operations on the connection string
once we obtained it the first time. I think this cache wouldn’t add much of a memory footprint since typically applications use one single connection string.