SimpleInjector
SimpleInjector copied to clipboard
Reduce memory pressure
The use of a DI container comes with a certain increase of memory pressure. This happens because the container must be informed about all registrations and their metadata. This metadata will be kept into memory for the duration of the application and will increase almost constantly with the number of registrations in the application.
All DI containers have this cost and increased memory pressure compared to Pure DI is inevitable. With Simple Injector every registration causes some extra memory that will be kept allocated as long as the container instance is referenced (which usually is for the duration of the application).
For each registration Simple Injector will hold the following information in memory:
- The InstanceProducer (80 bytes by itself) with:
- The generated Expression tree (? bytes)
- Collection of KnownRelationships
- The Registration (25 bytes by itself) with:
- HashSet for KnownRelationsips
This can easily lead up to around 300 bytes per registration. This is memory that will keep being referenced until the Container instance goes out of scope (which typically means the application stops).
Simple Injector is already highly tuned but we might do even more to prevent memory pressure. We should investigate how we can reduce pressure as much as possible.
Currently, the overhead is somewhere between 380 and 400 bytes per unique registration.
Do note, however, that no user has ever complained about the memory use in Simple Injector, and its memory use will likely be similar to other DI Containers, although it would be nice to see a benchmark for this.
Here are some pointers for research:
- Clearing Expressions:
InstanceProducerinstances cache theExpressionthat is built by theirRegistration. This is crucial for build performance. Not caching would easily make callingVerify()one or two orders of magnitude slower, and disallows Simple injector to effectively reduce object graph size (as implemented in #91). But there might be a middle ground where this cache is cleared at some point, allowing memory to be freed. - Clearing KnownRelationships:
Registrationitself doesn't cache the builtExpression, but it does cacheKnownRelationshipinstances (which is weird, because it acts as an ExpressionFactory, while still caching parts. Changing this, however, is a big breaking change). This information could be cleared as well at some point, although that would only be useful when that (same) information is removed from theInstanceProducerinstances as well. - Smarter delegate reuse: As implemented in #91, Simple Injector reduces the size of compiled delegates by splitting up an
InstanceProducer's delegate into smaller delegates, where it analyzes the object graph in order to reuse parts of the graph that are identical (caused by dependencies that are injected multiple times). This analysis, however, is limited to the compilation of a delegate of a singleInstanceProducer. In case the same dependency is injected into multiple graphs (multiple root objects), there will be no reuse. Doing this analysis on the complete DI configuration could do a global optimization and severely reduce the amount of code that needs to be compiled, and thus reducing the total memory use. Downside, however, is that such analysis is complex, performance intensive, and would cause the full configuration to be build on the first resolve.
Question now becomes, if we want to clear state, how do we know when to do this? The only viable moment I can think of is at the end of a call to Verify(), because that's the moment that everything (known to Simple Injector) is built and compiled. This does mean, however, that not calling Verify() would cause Simple Injector to use much more memory (its current amount). This is obviously not a very attractive model, because Verify() will cause a considerable delay for really large applications (which is the type of applications that benefit from this memory reduction), while not calling Verify() speeds up startup, while causing much higher memory pressure.