Note that there are some explanatory texts on larger screens.

plurals
  1. POBenchmarking async await incomprehension
    primarykey
    data
    text
    <p>I am doing some benchmarks using a simple C# console app written using async/await constructs of C#5 and numbers don't add up (well in fact they add up, and that's the problem ;))</p> <p>I am benchmarking three different scenarios : 1) 20K calls to an SQL server Stored Procedure. 2) 20K calls to a simple HTTP server 3) Scenario 1) and 2) together</p> <p>Here are more details about the scenarios :</p> <p><strong>1) 20K calls to an SQL server Stored Procedure.</strong></p> <p>In this scenario I call an external SQL Server stored proc 20K times. The CallSqlStoredProcedureAsync method uses ADO.NET async methods (OpenAsync, ExecuteNonQueryAsync ...). I even await Task.Yield() at the method entry to avoid any synchronous code execution before reaching asynchronous point, to avoid blocking my loop for the slightest amount of time.</p> <pre><code>var tasks = new List&lt;Task&gt;(); for (int i=0; i&lt;20000; i++) { tasks.Add(this.CallSqlStoredProcedureAsync()); } Task.WhenAll(tasks).Wait(); </code></pre> <p>This completes in approx <strong>10 seconds</strong> with CPU consumption average of 70%.</p> <p><strong>2) 20K calls to a simple HTTP server</strong></p> <p>In this scenario I call an url on an external webserver using HttpClient and async methods as well (PostAsync).</p> <pre><code>for (int i=0; i&lt;20000; i++) { tasks.Add(this.SendRequestToHttpServerAsync()); } </code></pre> <p>This completes in approx <strong>30 seconds</strong> with CPU consumption average of 30%</p> <p><strong>3) Scenario 1) and 2) together</strong></p> <pre><code>for (int i=0; i&lt;20000; i++) { tasks.Add(this.CallSqlStoredProcedureAsync()); tasks.Add(this.SendRequestToHttpServerAsync()); } </code></pre> <p>This completes in approx <strong>40 seconds</strong> with CPU consumption average of 70% for approx 20 sec, then 30% for the remaining 20 seconds.</p> <p><strong>Now for the Question</strong></p> <p>I don't understand why the benchmark is taking 40 seconds for scenario #3. If execution was sequential or if my CPU (or I/O) was a 100% for scenario 1 and 2, I would say this was normal to have the timing of scenario 1 + the timing of scenario 2.</p> <p>Considering I am going fully asynchronous using async/await constructs, what I was expecting for scenario #3 was for it to complete within 30 seconds ("weakest link in the chain"), being the duration of scenario #2.</p> <p>There is something I don't understand here :( </p> <p>Any clue?</p> <p>EDIT: Per @svick request, here is the complete code of the benchmark (excluding some useless stuff)</p> <pre><code> static void Main(string[] args) { var bench = new Bench(); while (true) { string iterationsAndScenario = Console.ReadLine(); var iterations = int.Parse(iterationsAndScenario.Split(' ')[0]); var scenario = int.Parse(iterationsAndScenario.Split(' ')[1]); var sw = new Stopwatch(); sw.Start(); bench.Start(iterations, scenario).Wait(); sw.Stop(); Console.WriteLine("Bench too {0} ms", sw.EllapsedMilliseconds); } } public class Benchmark { public Task Start(int iterations, int scenario) { var tasks = new List&lt;Task&gt;(); if (scenario == 1) { for (int i=0; i&lt;iterations; i++) { tasks.Add(this.CallSqlStoredProcedureAsync().ContinueWith(t =&gt; Console.WriteLine(t.Exception), TaskContinuationOptions.OnlyOnFaulted)); } } else if (scenario == 2) { var httpClient = new HttpClient(); for (int i=0; i&lt;iterations; i++) { tasks.Add(httpClient.PostAsync(uri, new StringContent("Hello")).ContinueWith(t =&gt; Console.WriteLine(t.Exception), TaskContinuationOptions.OnlyOnFaulted)); } } else if (scenario == 3) { var httpClient = new HttpClient(); for (int i=0; i&lt;iterations; i++) { tasks.Add(this.CallSqlStoredProcedureAsync().ContinueWith(t =&gt; Console.WriteLine(t.Exception), TaskContinuationOptions.OnlyOnFaulted)); tasks.Add(httpClient.PostAsync(uri, new StringContent("Hello")).ContinueWith(t =&gt; Console.WriteLine(t.Exception), TaskContinuationOptions.OnlyOnFaulted)); } } return Task.WhenAll(tasks); } public async Task CallSqlStoredProcedureAsync() { await Task.Yield(); using (var conn = new SqlConnection(connectionString)) { using (var cmd = new SqlCommand("sp_mystoreproc", conn)) { cmd.CommandType = CommandType.StoredProcedure; cmd.Parameters.AddWithValue("@param1", 'A'); cmd.Parameters.AddWithValue("@param2", 'B'); await cmd.Connection.OpenAsync(); await cmd.ExecuteNonQueryAsync(); } } } } </code></pre>
    singulars
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload