zzzprojects / EntityFramework-Plus

Entity Framework Plus extends your DbContext with must-haves features: Include Filter, Auditing, Caching, Query Future, Batch Delete, Batch Update, and more
https://entityframework-plus.net/
MIT License
2.27k stars 318 forks source link

Using Futures takes a lot of JIT compilation time #779

Closed ArtemAvramenko closed 10 months ago

ArtemAvramenko commented 1 year ago

1. Description

I've been checking what resources are being spent on in my application and found that a significant amount of time is spent on JIT compilation. As it turns out, the reason is the use of Futures.

2. Exception

Test application metrics (the same thing happens in a real application):

Time spent in JIT (ms / 1 sec)                                        98.998

It would be great if EntityFramework-Plus would cache compilation results so that performance would increase dramatically.

3. Fiddle or Project

Even the simplest example reveals the problem:

        static void Test()
        {
            using var context = new MyContext();
            var usersFuture = context.Users.Future();
            var companiesFuture = context.Companies.Future();
            _ = usersFuture.ToList();
            _ = companiesFuture.ToList();
        }

Full code - https://gist.github.com/ArtemAvramenko/46afef540b1def14d42a9c24f272c745

4. Any further technical details

I used dotnet-counters to collect the metrics.

Update: If you remove Futures from the code, the "Number of Methods Jitted" metric stops growing and "Time spent in JIT" becomes zero.

JonathanMagnan commented 1 year ago

Hello @ArtemAvramenko ,

Are you including the time that EF Core use to JIT compile itself? Our library is very small compared to everything that EF Core had to compile to execute the first query. Even if you use our library, we still use EF Core and you cannot include the part when calculating the time of JIT.

Best Regards,

Jon

ArtemAvramenko commented 1 year ago

Hello @JonathanMagnan,

I certainly removed Futures for comparison. CPU load drops several times after that and Time spent in JIT becomes equal to zero.

JonathanMagnan commented 1 year ago

I certainly removed Futures for comparison

Did you add these 2 lines to have a good comparison?

var users = context.Users.ToList();
var companies = context.Companies.ToList();

From what I remember, this is when you make the first query that EF Core will JIT compile most of his method. I feel it weird that the JIT compile will be close to zero without our library. We will surely investigate more if you tell me this is the behavior you still have.

ArtemAvramenko commented 1 year ago

@JonathanMagnan Yes, that's exactly the code I was comparing it to. With it, JIT compilation is called only at the first call. But if Futures are added, then each time Test() is called, new +4 methods are generated.

ArtemAvramenko commented 11 months ago

The problem remains in version 8.x

JonathanMagnan commented 10 months ago

Hello @ArtemAvramenko ,

We made some progress in finding out exactly where the JIT issue started, but to be honest, I'm not sure if we will be able to fix it or not.

We will look one more time to find out exactly if we can skip a line of code that causes it or not.

JonathanMagnan commented 10 months ago

Hello @ArtemAvramenko ,

We made more investigation this week, and I believe we will simply close as work as intended

When creating a query, EF Core compiles it and caches the query plan. So the next time the same query is called, it doesn't longer require JIT compile or at least calls some method that causes a JIT compile, such as the one found here: https://github.com/dotnet/efcore/blob/397dc68aa9599f3fd7bf04b5e447614dfd3f90c2/src/EFCore/Query/QueryCompilationContext.cs#L169

So it explain why the JIT compile is 0 when running the query multiple times without our library.

On our side, the query is always re-created as we combine them. We don't cache the plan either. So we always need to execute some of the code and the visitor found in my previous link every time.

Could it be possible to improve this to reduce the JIT compile time? Surely, but at this point, I believe we are currently too deep in methods specific to EF Core to really make it happen.

Let me know if at least the explanation makes sense.

Best Regards,

Jon

ArtemAvramenko commented 10 months ago

Hello @JonathanMagnan,

I imagined it was possible to cache the result without recompiling again. But obviously there will be a lot of pitfalls, such as different SQL that EF generates depending on the parameter values. This and many other things will have to be taken into account when creating the cache.

At the moment, I will try to remove the use of EntityFramework-Plus and see how negatively it will affect the load on the database. A slight increase in the load on the database will be acceptable to me, as it will significantly reduce the application's load on the CPU.