Are the Func <T> parameters in the constructor a slowdown in my IoC resolution?

I am trying to improve the performance of my IoC container. We use Unity and SimpleInjector, and we have a class with this constructor:

public AuditFacade( IIocContainer container, Func<IAuditManager> auditManagerFactory, Func<ValidatorFactory> validatorCreatorFactory, IUserContext userContext, Func<ITenantManager> tenantManagerFactory, Func<IMonitoringComponent> monitoringComponentFactory) : base(container, auditManagerFactory, GlobalContext.CurrentTenant, validatorCreatorFactory, userContext, tenantManagerFactory) { _monitoringComponent = new Lazy<IMonitoringComponent>(monitoringComponentFactory); } 

I also have another class with this constructor:

 public AuditTenantComponent(Func<IAuditTenantRepository> auditTenantRepository) { _auditTenantRepository = new Lazy<IAuditTenantRepository>(auditTenantRepository); } 

I see that the second gets a resolution of 1 millisecond, most of the time, while the first takes an average of 50-60 milliseconds. I am sure that the argument is relatively slower due to the parameters, it has more parameters. But how can I improve the performance of this slower one? Is it a fact that we use Func<T> as parameters? What can I change if it causes slowness?

+5
source share
3 answers

Perhaps a lot can improve your current design. These improvements can be placed in five different categories, namely:

  • Possible abuse of base classes
  • Using the anti-locator service template
  • Using an anti-context context template
  • Comforting abstractions
  • Do too much in injection constructors

Possible abuse of base classes

The general consensus is that you should prefer composition over inheritance . Inheritance is often overused and often adds more complexity than using a composition. With inheritance, a derived class is closely related to the implementation of the base class. I often see that the base class is used as a practical utility class, containing all sorts of helper methods for cross-cutting tasks and other behavior that some of the derived classes may require.

Often the best approach is to remove the base class together and inject the service into the implementation (the AuditFacade class in your case), which exposes only those functions that the service needs. Or in the case of cross-cutting issues, do not introduce this behavior at all, but complete the implementation with a decorator that extends the behavior of the class with cross-cutting issues.

In your case, I think that the complication obviously occurs, since 6 out of 7 nested dependencies are not used by the implementation, but are passed only to the base class. In other words, these 6 dependencies are implementation details of the base class, while the implementation is still forced to know about them. By exhausting (part of) this base class behind the service, you can minimize the number of dependencies that AuditFacade needs two dependencies: Func<IMonitoringComponent> and a new abstraction. The implementation of this abstraction will have 6 constructor dependencies, but AuditFacade (and other implementations) do not pay attention to this.

Using antivirus Locator Service

AuditFacade dependent on the IIocContainer abstraction, and this is very similar to the implementation of the service locator pattern . A service locator should be considered an anti-pattern because:

it hides class dependencies, causing runtime errors instead of compile-time errors, and also make the code more complicated for because it becomes unclear when you are breaking the change.

There are always better alternatives for injecting your container or abstraction over your container into the application code. Please note that in some cases you may need to insert a container in the factory implementation, but at the same time, if they are placed inside the Root of Composition , there is no harm, since the Locator service is roles, not mechanics .

Using an anti-context context template

The static property GlobalContext.CurrentTenant is an implementation of Mark Seemann, and I write about this template in our book :

Problems with AMBIENT CONTEXT are related to problems with SERVICE SALES. Main problems:

  • DEPENDENCE is hidden.
  • Testing is becoming more complicated.
  • It is very difficult to change the DEPENDENCE based on its context. [clause 5.3.3]

The use in this case is really a strange IMO, because you grab the current tenant from some static property inside your constructor to pass it to the base class. Why doesn't the base class call this property?

But no one should call this static property. Using these static properties makes your code more difficult to read and maintain. This simplifies unit testing, and since your code base will usually be dotted with calls of such static, it becomes a hidden dependency; it has the same disadvantages as using the Locator service.

Comforting abstractions

A Leaky Abstraction is a principle of dependency inversion breaking when an abstraction violates the second part of the principle, namely:

B. Abstractions should not depend on details. Details should depend on abstraction.

Although Lazy<T> not an abstraction in itself ( Lazy<T> is a specific type), it can become a fuzzy abstraction when used as an argument to a constructor. For example, if you directly insert Lazy<IMonitoringComponent> instead of IMonitoringComponent (which you mostly do in your code), the new Lazy<IMonitoringComponent> dependency eliminates implementation details. This Lazy<IMonitoringComponent> tells the consumer that the IMonitoringComponent implementation IMonitoringComponent is expensive or takes a long time to create. But why should the consumer care about this?

But there are more problems with this. If at one point in time the used IUserContext implementation becomes expensive to create, we should start making radical changes throughout the application (violation of the Open / Closed principle ), because all IUserContext dependencies need to be changed to Lazy<IUserContext> , and all users of this IUserContext should be modified to use userContext.Value. instead userContext.Value. . And you also have to change all your unit tests. And what happens if you forget to change one IUserContext link to Lazy<IUserContext> or when you accidentally depend on IUserContext when creating a new class? You have a bug in the code, because at that moment the context of the user context is immediately created, and this will cause a performance problem (this causes the problem, because it is for this reason that you use Lazy<T> in the first place).

So why do we make radical changes to our code base and pollute it with this extra layer of indirection? There is no reason for this. The fact that dependency is expensive to create is an implementation detail . You have to hide it behind an abstraction. Here is an example:

 public class LazyMonitoringComponentProxy : IMonitoringComponent { private Lazy<IMonitoringComponent> component; public LazyMonitoringComponentProxy(Lazy<IMonitoringComponent> component) { this.component = component; } void IMonitoringComponent.MonitoringMethod(string someVar) { this.component.Value.MonitoringMethod(someVar); } } 

In this example, we hid Lazy<IMonitoringComponent> behind the proxy class . This allows us to replace the original implementation of IMonitoringComponent with this LazyMonitoringComponentProxy without having to make any changes to the rest of the applicaiton. With Simple Injector, we can register this type as follows:

 container.Register<IMonitoringComponent>(() => new LazyMonitoringComponentProxy( new Lazy<IMonitoringComponent>(container.GetInstance<CostlyMonitoringComp>)); 

And just as Lazy<T> can be abused as a skipping abstraction, the same goes for Func<T> , especially when you do this for performance reasons. With the proper use of DI, in most cases you do not need to introduce factory abstractions in your code, such as Func<T> .

Note that if you embed Lazy<T> and Func<T> everywhere, you make the unnecessary code base difficult.

Doing too much in injection constructors

But besides the fact that Lazy<T> and Func<T> are leaky abstractions, the fact that you need them is an indication of a problem with your application, because the injection constructors should be simple . If designers take a lot of time, your designers do too much. Constructor logic is often difficult to verify, and if such a constructor makes a call to the database or requests data from an HttpContext, checking your object graphs becomes a lot harder to the point where you can skip checking together. Skipping an object graph check is horrible because it forces you to click through the entire application to see if your DI container is configured correctly.

Hope this gives you some ideas for improving the design of your classes.

+9
source

You can connect to the Simple Injector pipeline and add profiling, which allows you to determine which types to create slowly. Here's an extension method that you can use:

 public struct ProfileData { public readonly ExpressionBuildingEventArgs Info; public readonly TimeSpan Elapsed; public ProfileData(ExpressionBuildingEventArgs info, TimeSpan elapsed) { this.Info = info; this.Elapsed = elapsed; } } static void EnableProfiling(Container container, List<ProfileData> profileLog) { container.ExpressionBuilding += (s, e) => { Func<Func<object>, object> profilingWrapper = creator => { var watch = Stopwatch.StartNew(); var instance = creator.Invoke(); profileLog.Add(new ProfileData(e, watch.Elapsed)); return instance; }; Func<object> instanceCreator = Expression.Lambda<Func<object>>(e.Expression).Compile(); e.Expression = Expression.Convert( Expression.Invoke( Expression.Constant(profilingWrapper), Expression.Constant(instanceCreator)), e.KnownImplementationType); }; } 

And you can use it as follows:

 var container = new Container(); // TODO: Your registrations here. // Hook the profiler List<ProfileData> profileLog = new List<ProfileData>(1000); // Call this after all registrations. EnableProfiling(container, profileLog); // Trigger verification to allow everything to be precompiled. container.Verify(); profileLog.Clear(); // Resolve a type: container.GetInstance<AuditFacade>(); // Display resolve time in order of time. var slowestFirst = profileLog.OrderByDescending(line => line.Elapsed); foreach (var line in slowestFirst) { Console.WriteLine(string.Format("{0} ms: {1}", line.Info.KnownImplementationType.Name, line.Elapsed.TotalMilliseconds); } 

Note that the points shown include the time it takes to resolve the dependencies, but this will probably allow you to pretty easily find out what type causes the delay.

There are two important things that I want to point out here:

  • This code will have an extremely negative impact on the performance of object graphs and
  • The code is NOT thread safe.

Therefore, do not use it in a production environment.

+2
source

All that you have is connected with this. As a rule, more constructor parameters that recursively solve take longer than fewer parameters. But you have to decide whether the price is too high.

In your case, 50 ms will cause a bottleneck? Do you only create 1 instance or do you disable them? Just comparing 1 ms to 50 ms can make you disapprove slower, but if the user cannot say that 50 ms has passed and this does not cause a problem elsewhere in your application, why run the hoops to make it faster, if you don’t know when it will be needed?

0
source

Source: https://habr.com/ru/post/1203729/


All Articles