私は、C#5 の async/await コンストラクトを使用して記述された単純な C# コンソール アプリを使用していくつかのベンチマークを行っていますが、数値が加算されません (実際、それらは加算され、それが問題です ;))
私は 3 つの異なるシナリオのベンチマークを行っています。1) SQL サーバー ストアド プロシージャへの 20K 呼び出し。2) 単純な HTTP サーバーへの 20K 呼び出し 3) シナリオ 1) と 2) を一緒に
シナリオの詳細は次のとおりです。
1) SQL サーバーのストアド プロシージャへの 20K の呼び出し。
このシナリオでは、外部の SQL Server ストアド プロシージャを 20,000 回呼び出します。CallSqlStoredProcedureAsync メソッドは、ADO.NET 非同期メソッド (OpenAsync、ExecuteNonQueryAsync ...) を使用します。メソッドのエントリで Task.Yield() を待機して、非同期ポイントに到達する前に同期コードの実行を回避し、わずかな時間だけループがブロックされるのを回避します。
var tasks = new List<Task>();
for (int i=0; i<20000; i++)
{
tasks.Add(this.CallSqlStoredProcedureAsync());
}
Task.WhenAll(tasks).Wait();
これは、平均 70% の CPU 消費で約10 秒で完了します。
2) 単純な HTTP サーバーへの 20,000 回の呼び出し
このシナリオでは、HttpClient と非同期メソッド (PostAsync) を使用して、外部 Web サーバーで URL を呼び出します。
for (int i=0; i<20000; i++)
{
tasks.Add(this.SendRequestToHttpServerAsync());
}
これは、平均 30% の CPU 消費で約30 秒で完了します
3) シナリオ 1) と 2) を一緒に
for (int i=0; i<20000; i++)
{
tasks.Add(this.CallSqlStoredProcedureAsync());
tasks.Add(this.SendRequestToHttpServerAsync());
}
これは約40 秒で完了し、CPU 消費の平均は約 20 秒間で 70%、残りの 20 秒間で 30% になります。
それでは質問です
シナリオ 3 でベンチマークに 40 秒かかる理由がわかりません。実行がシーケンシャルだった場合、またはシナリオ 1 と 2 で CPU (または I/O) が 100% だった場合、シナリオ 1 のタイミング + シナリオ 2 のタイミングになるのは正常であると言えます。
async/await コンストラクトを使用して完全に非同期化することを考えると、シナリオ #3 に期待していたのは、シナリオ #2 の期間である 30 秒 (「チェーン内の最も弱いリンク」) 以内に完了することでした。
ここでわからないことがあります:(
どんな手掛かり?
編集: @svick リクエストごとに、ベンチマークの完全なコードを次に示します (一部の役に立たないものを除く)。
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<Task>();
if (scenario == 1)
{
for (int i=0; i<iterations; i++)
{
tasks.Add(this.CallSqlStoredProcedureAsync().ContinueWith(t => Console.WriteLine(t.Exception), TaskContinuationOptions.OnlyOnFaulted));
}
}
else if (scenario == 2)
{
var httpClient = new HttpClient();
for (int i=0; i<iterations; i++)
{
tasks.Add(httpClient.PostAsync(uri, new StringContent("Hello")).ContinueWith(t => Console.WriteLine(t.Exception), TaskContinuationOptions.OnlyOnFaulted));
}
}
else if (scenario == 3)
{
var httpClient = new HttpClient();
for (int i=0; i<iterations; i++)
{
tasks.Add(this.CallSqlStoredProcedureAsync().ContinueWith(t => Console.WriteLine(t.Exception), TaskContinuationOptions.OnlyOnFaulted));
tasks.Add(httpClient.PostAsync(uri, new StringContent("Hello")).ContinueWith(t => 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();
}
}
}
}