2

I'm running a Performance Testing on EF because we were having some issue running concurrent calls on our Server.

Here is my Tests which i'm executing against northwind database, with 20,000 extra employees in the database to slow down retrieval

single execution generally return in about 451 ms, however when calling in parallel it seems I'm seeing some weird extra execution time

When I Run 10 Concurrent calls I'm getting 4 times the over head. It may seem trivial but when I execute things on my database you can see it a whole lot worse.

When running sp_whoisactive you can see that sql has already finished and it is wait to send back the (246ms)ASYNC_NETWORK_IO

Sql is not bottle necked.

class Program
{
    public static int MAX_IO_THREADS = 2000;
    public static int MIN_IO_THREADS = 1000;
    public static int CONCURRENT_CALLS = 10;

    static void Main(string[] args)
    {
        ConfigureThreadPool();
        WarmUpContexts();

        ProfileAction();
        Console.ReadKey();
    }

    protected static void ProfileAction()
    {
        var callRange = Enumerable.Range(0, CONCURRENT_CALLS);

        var stopwatch = new Stopwatch();
        stopwatch.Start();

        var results = new ConcurrentDictionary<int, string>();
        var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = 512 };

        Parallel.ForEach(callRange, parallelOptions, i =>
        {
            var callSw = new Stopwatch();
            callSw.Start();
            var result = RunTest();
            callSw.Stop();
            results.TryAdd(i, $"Call Index: {i} took {callSw.ElapsedMilliseconds} milliseconds to complete");

        });

        stopwatch.Stop();
        var milliseconds = stopwatch.ElapsedMilliseconds;

        foreach (var result in results.OrderBy(x => x.Key))
        {
            Console.WriteLine(result);
        }

        Console.WriteLine("Total Time " + milliseconds);
    }

    public static void ConfigureThreadPool()
    {
        ThreadPool.GetMinThreads(out var defaultMinWorkerCount, out var _);
        ThreadPool.GetMaxThreads(out var defaultMaxWorkerCount, out var _);

        ThreadPool.SetMaxThreads(defaultMaxWorkerCount, MAX_IO_THREADS);
        var successMin = ThreadPool.SetMinThreads(defaultMinWorkerCount, MIN_IO_THREADS);
    }

    static void WarmUpContexts()
    {
        using (var stagingContext = new NORTHWNDEntities())
        {
            stagingContext.Employees.First();
        }
    }


    static int RunTest()
    {
        using (var context = new Entities())
        {
            var employees = context.Employees.ToArray();
            return employees.Length;
        }
    }
}

How can I optimize the performance of my threads so they actually return NOTE if I call async this will hang forever,

could this be a bug in .net?

johnny 5
  • 19,893
  • 50
  • 121
  • 195
  • `MaxDegreeOfParallelism = 512` Unless you have 512 logical cores on your CPUs, which I find highly unlikely, this will completely kill the performances of your app – Kevin Gosse Jan 25 '18 at 23:45
  • @KevinGosse I've tested with less as well and it has the samei ssues – johnny 5 Jan 25 '18 at 23:47
  • Why do you use the dbcontext being created in each iteration? You get too many parallel queries to your database. Also, `ToArray` method can be time and memory consuming as well, as the arrays goes to Large Object Heap, which isn't being optimized by default – VMAtm Jan 26 '18 at 18:11
  • @VMAtm, Entityframework Context aren't thread safe, but also, I was trying to emulate behavior of the request. How can you Optimize the Heap – johnny 5 Jan 26 '18 at 19:38
  • Heap shouldn;t be optimized, it shouldn't be pressured - simply do not create the arrays, use some other structures unless you need exactly the array – VMAtm Jan 26 '18 at 22:18
  • @VMAtm turns out I was getting overhead for tracking. I was calling toarray to simulate a very long operation. But all of the latency was from mapping – johnny 5 Jan 26 '18 at 23:53
  • You probably should post this as an answer – VMAtm Jan 27 '18 at 01:47

1 Answers1

1

A Few reasons why my project code was Delaying.

1.Configure Thread Pool. Opening up your IO threads will help delay with request that are not only cpu bound. Such as writing a file to disk, or a network bound request.

Note: if you're setting higher values you need to be careful not to have you min exceed the max, because these functions are poorly named, and should be called TrySetMin/TrySetMax since they return a bool whether successful or not

Be careful setting these I was only uses extremely high numbers for testing and learning purposes

ThreadPool.SetMaxThreads(defaultMaxWorkerCount, MAX_IO_THREADS);
ThreadPool.SetMinThreads(defaultMinWorkerCount, MIN_IO_THREADS);

2.GcServer Apparently, in .Net by Default the Garbage Collector is optimized for single Core performance instead of multicore Performance. You can read more on this issue on MSDN

Note: in .Net 4.0 Enabling GCServer Causing issues for application who had UI. In .Net 4.5 these issues were fixed by enabling gcConcurrent=true by Default

<configuration>  
   <runtime>  
      <gcServer enabled="true"/>  
   </runtime>  
</configuration>  

3.Additional Delays caused by EntityFrameWork, It looks like mapping relationships is really expensive, even if you are not pull out any related entities.

var employees = context.Employees.AsNoTracking().ToArray();

In was testing using an extremely large database, Moral is, if you are loading data for GET requests, if possible load without tracking.
Note: This may cause unexpected behavior as, different entities who share a common Foreign Key will have Different CLR object associated with them

johnny 5
  • 19,893
  • 50
  • 121
  • 195
  • Hey @johnny5, I had a similar question at https://stackoverflow.com/questions/52726292/either-my-asp-net-api-or-my-ef6-sql-database-is-super-slow-either-on-azure-or-m however, I just found yours and, I wanted to know more about the amount of thread you're talking about and, I wanted to know if you knew a lot about the azure server's plan and their performances (DTU, vCore, etc). I already upvoted your question and answer since I found them very interesting. Passing to `async` seemed to help, your point 3 as well, but still, concurrency is still a big issue – Emixam23 Dec 07 '18 at 15:49
  • I haven’t messed with azure much but I’ll do my best to help – johnny 5 Dec 07 '18 at 17:18
  • 1
    @Emixam23 I left a comment with a few issues if I have time later I’ll investigate more but the things I wrote should help – johnny 5 Dec 07 '18 at 17:28