1

私は、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(); 
            }
        }
    }
}
4

1 に答える 1

0

シナリオ 1 と 2 の結果から、DB へのクエリは HTTP リクエストよりも速く完了すると思います (もちろん、ローカル ディスクはインターネットよりも待ち時間が短いため)。したがって、通常は 10 秒で完了します。しかし、送信するタスクの半分は HTTP リクエストであるため、計算能力の半分はそれでビジーであり、DB に必要な時間は 10 秒から 20 秒に 2 倍になります。この間、CPU 使用率は 0.7 であり、これは達成可能な最大使用率です (タスクの 50% が CPU の 30% しか使用しない場合でも、リソース使用率を最大化しているため、プールは実際には効果的です)。

その後、DB 要求が完了し、HTTP 要求だけが残ります。DB と HTTP は同時に実行されましたが、リソースの半分だけが HTTP 専用であったため、これらの 20 秒は 10 秒の非競合実行に相当し、他の 30 秒 - 10 秒 = 20 秒の実行が残ります。

CPU 使用率が 100% でな​​くても、タスク マネージャーは 40,000 スレッドを作成しません。これは、オペレーティング システムがクラッシュするためです。

于 2013-10-19T00:57:03.283 に答える