I just published the latest BlueMilk v0.8 to Nuget with quite a bit of performance optimization, some additional StructureMap functionality added in, and the ability to handle every kind of service registration that a basic MVC Core application will throw at an IoC container.
BlueMilk is the current codename for a new IoC tool that partially originated in Jasper. You can read about the goals and motivation for the project in Introducing BlueMilk: StructureMap’s Replacement & Jasper’s Special Sauce.
One of my colleagues made the observation yesterday that while being a Star Wars nerd and getting the reference, the name “BlueMilk” is off putting and we probably need to change that (plus it feels awkward to say out loud to me). Other than Marten and BlueMilk, all the projects in the JasperFx organization are named after other little towns or landmarks around my hometown. Once upon a time, I parked some of the code that’s not part of BlueMilk in another repository named “Lamar” that fits that naming scheme, plus my wife is doing a master’s program at Lamar University, I have a former coworker who played baseball there, and it’s also the name of a Texas revolutionary hero. If nobody has a better idea, I’ll probably rename BlueMilk to “Lamar.” My other idea was “Corsair,” but that’s really just too cool a name for yet another IoC tool.
Usage in MVC Core
I blogged last week that BlueMilk is Ready for Early Adopters. I was wrong of course, but thank you to Mark Warpool and others for actually trying to use it and giving me some important feedback. The big problems with 0.7 were that the code generation model that BlueMilk uses internally can’t handle internal types and didn’t allow for using generic types as constructor parameters. Wouldn’t you know it, ASP.Net Core MVC has quite a bit of both of those usages in its service registrations and BlueMilk was falling flat on its face in an MVC Core application (correction, “works on my box,” fails on a couple registrations in AppVeyor even though it’s the same version of the .Net SDK. Still saying it’s good to go at least until someone proves it’s a no go).
After a couple fixes (one big and one small), there’s now a test in BlueMilk that successfully builds every possible service registration from a basic MVC Core application. For those of you following along at home, I had to revert to using the dynamic Expression compiled to a Func<> trick to slide around the nonpublic types just to call non-public constructors.
A caveat here, it’s just not terribly likely that your IoC tool of choice is the performance bottleneck in your system.
First off, Maksim Volkau has built some seriously cool stuff, and BlueMilk got quite a bit of the performance boost I’m talking about here from using his ImTools library (both Marten and StructureMap use FastExpressionCompiler as well).
One of my coworkers asked how BlueMilk compared to StructureMap in terms of performance, so I threw together some benchmarks where I was able to show BlueMilk being over 5X faster than StructureMap over a range of potential usages. I made the mistake of tweeting about that yesterday, and Eric Smith asked me how BlueMilk compared to the build in DI container inside of ASP.Net Core. After adding a comparison to the built in container to my BenchmarkDotNet metrics, I could see that BlueMilk lagged a bit (~30% slower over all). Several optimizations later, I can now say that BlueMilk is (barely) faster than the built in DI container and closing in on an order of magnitude faster than the latest StructureMap.
Using a barebones MVC Core application with logging added in as well, I built a series of metrics that just loops through the registered types and builds each possible type. It’s a lazy way of building up metrics, but it gave me a mix of registrations by type, by lambda, pre-built objects, and some deeper object graphs. It’s probably a bit bogus because this isn’t the way that an application is going to use the IoC tool at runtime and may weight more heavily on usages that don’t actually happen.
That being said, here’s the overall metrics on just creating every possible registered type in that minimal MVC Core application:
BenchmarkDotNet=v0.10.12, OS=macOS 10.12.6 (16G1212) [Darwin 16.7.0] Intel Core i7-6920HQ CPU 2.90GHz (Skylake), 1 CPU, 8 logical cores and 4 physical cores .NET Core SDK=2.1.4 [Host] : .NET Core 2.0.5 (Framework 220.127.116.11), 64bit RyuJIT DefaultJob : .NET Core 2.0.5 (Framework 18.104.22.168), 64bit RyuJIT Method | ProviderName | Mean | Error | StdDev | --------- |------------- |----------:|----------:|----------:| AllTypes | AspNetCore | 73.98 us | 1.444 us | 1.976 us | AllTypes | BlueMilk | 70.92 us | 1.408 us | 2.392 us | AllTypes | StructureMap | 646.28 us | 12.856 us | 27.398 us |
Getting much more specific, here are some finer grained metrics with an explanation of the different measurements below:
BenchmarkDotNet=v0.10.12, OS=macOS 10.12.6 (16G1212) [Darwin 16.7.0] Intel Core i7-6920HQ CPU 2.90GHz (Skylake), 1 CPU, 8 logical cores and 4 physical cores .NET Core SDK=2.1.4 [Host] : .NET Core 2.0.5 (Framework 22.214.171.124), 64bit RyuJIT DefaultJob : .NET Core 2.0.5 (Framework 126.96.36.199), 64bit RyuJIT Method | ProviderName | Mean | Error | StdDev | Median | ------------ |------------- |-------------:|-------------:|--------------:|-------------:| CreateScope | AspNetCore | 429.0 ns | 8.288 ns | 7.347 ns | 428.2 ns | Lambdas | AspNetCore | 1,784.4 ns | 25.886 ns | 22.948 ns | 1,777.8 ns | Internals | AspNetCore | 914.2 ns | 17.575 ns | 15.580 ns | 912.6 ns | Objects | AspNetCore | 810.2 ns | 7.723 ns | 6.449 ns | 808.7 ns | Singletons | AspNetCore | 18,428.6 ns | 203.784 ns | 159.101 ns | 18,441.3 ns | Scope | AspNetCore | 556.7 ns | 7.823 ns | 7.317 ns | 555.9 ns | Transients | AspNetCore | 41,882.1 ns | 391.872 ns | 327.231 ns | 41,787.8 ns | CreateScope | BlueMilk | 110.8 ns | 2.205 ns | 2.944 ns | 111.4 ns | Lambdas | BlueMilk | 2,138.1 ns | 27.465 ns | 25.691 ns | 2,140.5 ns | Internals | BlueMilk | 332.2 ns | 3.926 ns | 3.278 ns | 331.4 ns | Objects | BlueMilk | 586.9 ns | 17.605 ns | 51.633 ns | 575.4 ns | Singletons | BlueMilk | 9,852.8 ns | 196.721 ns | 548.380 ns | 9,780.1 ns | Scope | BlueMilk | 330.8 ns | 5.781 ns | 4.828 ns | 332.1 ns | Transients | BlueMilk | 54,439.2 ns | 1,083.872 ns | 2,967.082 ns | 53,801.7 ns | CreateScope | StructureMap | 16,781.0 ns | 334.284 ns | 948.307 ns | 16,584.2 ns | Lambdas | StructureMap | 12,329.5 ns | 244.697 ns | 686.155 ns | 12,121.9 ns | Internals | StructureMap | 10,585.0 ns | 209.617 ns | 393.712 ns | 10,519.9 ns | Objects | StructureMap | 17,739.9 ns | 430.679 ns | 560.005 ns | 17,606.7 ns | Singletons | StructureMap | 162,029.0 ns | 3,191.513 ns | 6,148.961 ns | 161,590.8 ns | Scope | StructureMap | 5,830.1 ns | 158.896 ns | 463.507 ns | 5,700.8 ns | Transients | StructureMap | 451,798.1 ns | 8,988.047 ns | 21,707.143 ns | 448,860.3 ns |
The metrics named in the first column are:
- “CreateScope” — measures how long it takes to create a completely new container scope as MVC Core and other frameworks do on each HTTP request.
- “Lambdas” — resolving services that were registered with some kind of
- “Internals” — resolving non-public types
- “Objects” — resolving services that were registered with a pre-built object
- “Singletons” — all singleton registrations of all kinds
- “Scope” — bad name, but all registrations with the “Scoped” lifetime
- “Transients” — all registrations with the “Transient” lifetime
There’s still some performance fat in BlueMilk’s code, but I’m saying that I’ve hit the point of diminishing returns for now and I’m staying put on performance.
BlueMilk v0.8 adds in some old StructureMap behavior for:
- Inline dependencies
- Instance construction policies
- Some limited attribute configuration usage
- More options for the build by lambda registrations so you can use
Func<IContainer, object>in addition to the thinner, ASP.Net Core compliant
I’m probably done working on BlueMilk for now other than the inevitable bug reports. When I do come back to it (or someone else picks it up), the next version (v0.9) will hopefully have support for decorators and interception similar to StructureMap’s existing model. I’d hope to have a 1.0 version out sometime this summer or fall after it’s been in production somewhere for awhile.