DryIoc: Performance degradation with ImportMany in v4 compared to v2

Hi Maksim,

I’ve recently upgraded an application running DryIoc v2.12.6 to DryIoc v4.8.1
and noticed substantial performance degradation (it runs about ~70x slower).

The application is a simple intranet web server that hosts web APIs implemented as C# scripts. Server loads scripts from the database and recompiles them on-the-fly when they are changed. It uses DryIoc to wire them up to the built-in services and to the other scripts. This feature relies on DryIoc’s dynamic registrations.

I trimmed down the project to reproduce the isolated issue. My benchmark basically imports and calls the same web service 1000 times in a row. I also tested a v3 version of DryIoc to check if it performs any better.

Here is the log:

v2.12.6.0

D:\Externals\DryIocPerf\Bin\v2\net46>DryIoc2Perf.exe
[02:53:07 INF] Starting up...
[02:53:07 INF] Started.
[02:53:07 INF] Hello there. Starting the benchmark.
[02:53:07 INF] Imported web services: 104
[02:53:07 INF] GetNow: 2021-07-13T02:53:07.0000000+03:00
[02:53:07 INF] Warming up...
[02:53:08 INF] Running the benchmark...
[02:53:08 INF] Time elapsed: 00:00:00.0756021

v3.0.2.0

D:\Externals\DryIocPerf\Bin\v3\net46>DryIoc3Perf.exe
[02:53:13 INF] Starting up...
[02:53:14 INF] Started.
[02:53:14 INF] Hello there. Starting the benchmark.
[02:53:14 INF] Imported web services: 104
[02:53:14 INF] GetNow: 2021-07-13T02:53:14.0000000+03:00
[02:53:14 INF] Warming up...
[02:53:14 INF] Running the benchmark...
[02:54:20 INF] Time elapsed: 00:01:05.5922111

v4.8.1.0

D:\Externals\DryIocPerf\Bin\v4\net46>DryIoc4Perf.exe
[03:04:06 INF] Starting up...
[03:04:06 INF] Started.
[03:04:06 INF] Hello there. Starting the benchmark.
[03:04:06 INF] Imported web services: 104
[03:04:06 INF] GetNow: 2021-07-13T03:04:06.0000000+03:00
[03:04:06 INF] Warming up...
[03:04:06 INF] Running the benchmark...
[03:04:12 INF] Time elapsed: 00:00:05.4144036
  • v4.8.1 performs ~72 times slower than v2.12.6
  • v3.0.2 performs ~876 times slower than v2.12.6

Perhaps I’m doing something wrong in my dynamic registration resolver… Upd. No, it seems to reproduce even if dynamic callback does nothing.

I’m going to clean up the code and upload it to a separate github repo. The benchmark is actually quite small, but the dependencies are heavy.

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Comments: 35 (35 by maintainers)

Commits related to this issue

Most upvoted comments

@yallie I have burned it down to the propagating of the already resolved DynamicRegistration factory through the wrappers chain. But it requires the explicit support in the wrapper factory implementation. Currently, the factory is propagated up to Func/Action with or without arguments and to Lazy<T> and Lazy<T, TMeta>. The factory is propagated from and through the Array and collection wrappers, KeyValuePair and the Meta/Tuple wrappers.

ExportFactory<T> and ExportFactory<T, TMeta> is not supported yet. I postponing it for the next version or the PRs.

The factory won’t propagate up-to but not through the Lazy and Func into the resolution calls - essentially it means the DynamicRegistration will be called again. It is because of the dynamic nature of those wrappers - they may be used for the late registration done after the wrappers are resolved but not yet consumed.

@yallie Hello,

Currently the expressions cached only for services and not for wrappers, I’ve experimented with it and number of tests start to fail, so it is for the reason. Though I am planning to look at it closer later. For now I am simplifying the story of combining dynamic and normal registrations, removing the one problem from the picture before diving back to the caching.

@yallie

110k is so much better because my numbers were ~2900k, not 290k 😃

No, it was actually 1.1m.

I’ve improved it further to 330k, not good whatsover 😕

So, as the next step I will add the cache for the dynamic factories into the resolution request.

@yallie Thanks, it was me not updating the GitHub page, forgot that GH is half-SPA app.

I suspect that at least some of these 2 million calls of the dynamic resolver might not be necessary.

I suspect this too and will try to cut to absolute minimum. But generally the dynamic provider is treated as the black box possibly reconfigurable at runtime (“dynamic”). So the client should memoize on their end.

@yallie It is a great insite into the problem. I have started profiling but kind of suck in this topic…

Will check what’s happening.

Hi @dadhi, thank you 😃

Looks like v4 calls dynamic registration lookup callback much more frequently than v2:

image

  • v2 — 5670 calls of the GetDynamic registration callback
  • v3 — 2915281
  • v4 — 2902207

I’m surprised that while v4 has almost the same number of calls as v3, it’s still 10x faster than v3.

My guess was that v4 tries more types to look up. But it turned out not to be true.

The types being looked up are the same in both versions:

  • DryIoc.Meta`2[System.Lazy`1[System.Func`1[Ultima.WebServices.IWebService]],Ultima.WebServices.IWebServiceMetadata]
  • DryIoc.Meta`2[System.Lazy`1[Ultima.WebServices.IWebService],Ultima.WebServices.IWebServiceMetadata]
  • DryIoc.Meta`2[T,TMetadata]
  • System.Collections.Generic.IEnumerable`1[System.Collections.Generic.KeyValuePair`2[System.Object,System.Lazy`2[System.Func`1[Ultima.WebServices.IWebService],Ultima.WebServices.IWebServiceMetadata]]]
  • System.Collections.Generic.IEnumerable`1[System.Collections.Generic.KeyValuePair`2[System.Object,System.Lazy`2[Ultima.WebServices.IWebService,Ultima.WebServices.IWebServiceMetadata]]]
  • System.Collections.Generic.IEnumerable`1[System.Lazy`2[System.Func`1[Ultima.WebServices.IWebService],Ultima.WebServices.IWebServiceMetadata]]
  • System.Collections.Generic.IEnumerable`1[System.Lazy`2[Ultima.WebServices.IWebService,Ultima.WebServices.IWebServiceMetadata]]
  • System.Collections.Generic.IEnumerable`1[T]
  • System.Collections.Generic.KeyValuePair`2[System.Object,System.Lazy`2[System.Func`1[Ultima.WebServices.IWebService],Ultima.WebServices.IWebServiceMetadata]]
  • System.Collections.Generic.KeyValuePair`2[System.Object,System.Lazy`2[Ultima.WebServices.IWebService,Ultima.WebServices.IWebServiceMetadata]]
  • System.Collections.Generic.KeyValuePair`2[TKey,TValue]
  • System.Func`1[TResult]
  • System.Func`1[Ultima.WebServices.IWebService]
  • System.Lazy`1[System.Func`1[Ultima.WebServices.IWebService]]
  • System.Lazy`1[T]
  • System.Lazy`1[Ultima.Client.IUserMessages]
  • System.Lazy`1[Ultima.IConstantManager]
  • System.Lazy`1[Ultima.Linq.ITableSource]
  • System.Lazy`1[Ultima.Log.ILogManager]
  • System.Lazy`1[Ultima.Server.Data.ISqlService]
  • System.Lazy`1[Ultima.WebServices.IWebService]
  • System.Lazy`2[System.Func`1[Ultima.WebServices.IWebService],Ultima.WebServices.IWebServiceMetadata]
  • System.Lazy`2[System.Func`1[Ultima.WebServices.IWebService],Ultima.WebServices.IWebServiceMetadata][]
  • System.Lazy`2[T,TMetadata]
  • System.Lazy`2[Ultima.WebServices.IWebService,Ultima.WebServices.IWebServiceMetadata]
  • System.Lazy`2[Ultima.WebServices.IWebService,Ultima.WebServices.IWebServiceMetadata][]
  • System.Object
  • System.String
  • Ultima.Client.IUserMessages
  • Ultima.Dictionaries.IDictionaryManager
  • Ultima.Documents.IDocumentManager
  • Ultima.IConstantManager
  • Ultima.Linq.ITableSource
  • Ultima.Log.ILogger
  • Ultima.Log.ILogger`1[T]
  • Ultima.Log.ILogger`1[Ultima.WebServices.GetNow]
  • Ultima.Log.ILogManager
  • Ultima.Server.Composition.ServerCallImport`1[T]
  • Ultima.Server.Composition.ServerCallImport`1[Ultima.WebServices.IPermissionService]
  • Ultima.Server.Data.ISqlService
  • Ultima.Server.WebServices.IRequestContextProvider
  • Ultima.WebServices.IWebService

I’m surprised that it looks up System.Object, System.String and open generics like System.Lazy[T]. It also tries all kind of wrappers. I was assuming it should look up unwrapped/undecorated types.