19

私はついにVS2012を手に入れ、簡単なデモを立ち上げ、asyncとawaitの潜在的なパフォーマンス向上をチェックするために働いていましたが、残念なことに遅くなりました! 私が何か間違ったことをしている可能性がありますが、あなたが私を助けてくれるかもしれません。(単純なスレッド ソリューションも追加しましたが、これは期待どおりに高速に実行されます)

私のコードは、システムのコア数 (-1) に基づいて、クラスを使用して配列を合計します。同じことですが、async/await を使用します。

コード:(System.Managementコア検出器を機能させるには、への参照を追加する必要があることに注意してください)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.Management;
using System.Diagnostics;

namespace AsyncSum
{
    class Program
    {
        static string Results = "";

        static void Main(string[] args)
        {
            Task t = Run();
            t.Wait();

            Console.WriteLine(Results);
            Console.ReadKey();
        }

        static async Task Run()
        {
            Random random = new Random();

            int[] huge = new int[1000000];

            for (int i = 0; i < huge.Length; i++)
            {
                huge[i] = random.Next(2);
            }

            ArraySum summer = new ArraySum(huge);

            Stopwatch sw = new Stopwatch();

            sw.Restart();
            long tSum = summer.Sum();
            for (int i = 0; i < 100; i++)
            {
                tSum = summer.Sum();
            }
            long tticks = sw.ElapsedTicks / 100;

            long aSum = await summer.SumAsync();
            sw.Restart();
            for (int i = 0; i < 100; i++)
            {
                aSum = await summer.SumAsync();
            }
            long aticks = sw.ElapsedTicks / 100;

            long dSum = summer.SumThreaded();
            sw.Restart();
            for (int i = 0; i < 100; i++)
            {
                dSum = summer.SumThreaded();
            }
            long dticks = sw.ElapsedTicks / 100;


            long pSum = summer.SumParallel();
            sw.Restart();
            for (int i = 0; i < 100; i++)
            {
                pSum = summer.SumParallel();
            }
            long pticks = sw.ElapsedTicks / 100;

            Program.Results += String.Format("Regular Sum: {0} in {1} ticks\n", tSum, tticks);
            Program.Results += String.Format("Async Sum: {0} in {1} ticks\n", aSum, aticks);
            Program.Results += String.Format("Threaded Sum: {0} in {1} ticks\n", dSum, dticks);
            Program.Results += String.Format("Parallel Sum: {0} in {1} ticks\n", pSum, pticks);
        }
    }

    class ArraySum
    {
        int[] Data;
        int ChunkSize = 1000;
        int cores = 1;


        public ArraySum(int[] data)
        {
            Data = data;

            cores = 0;
            foreach (var item in new System.Management.ManagementObjectSearcher("Select * from Win32_Processor").Get())
            {
                cores += int.Parse(item["NumberOfCores"].ToString());
            }
            cores--;
            if (cores < 1) cores = 1;

            ChunkSize = Data.Length / cores + 1;
        }

        public long Sum()
        {
            long sum = 0;
            for (int i = 0; i < Data.Length; i++)
            {
                sum += Data[i];
            }
            return sum;
        }

        public async Task<long> SumAsync()
        {
            Task<long>[] psums = new Task<long>[cores];
            for (int i = 0; i < psums.Length; i++)
            {
                int start = i * ChunkSize;
                int end = start + ChunkSize;

                psums[i] = Task.Run<long>(() =>
                {
                    long asum = 0;
                    for (int a = start; a < end && a < Data.Length; a++)
                    {
                        asum += Data[a];
                    }
                    return asum;
                });
            }

            long sum = 0;
            for (int i = 0; i < psums.Length; i++)
            {
                sum += await psums[i];
            }

            return sum;
        }

        public long SumThreaded()
        {
            long sum = 0;
            Thread[] threads = new Thread[cores];
            long[] buckets = new long[cores];
            for (int i = 0; i < cores; i++)
            {
                int start = i * ChunkSize;
                int end = start + ChunkSize;
                int bucket = i;
                threads[i] = new Thread(new ThreadStart(() =>
                {
                    long asum = 0;
                    for (int a = start; a < end && a < Data.Length; a++)
                    {
                        asum += Data[a];
                    }
                    buckets[bucket] = asum;
                }));
                threads[i].Start();
            }

            for (int i = 0; i < cores; i++)
            {
                threads[i].Join();
                sum += buckets[i];
            }

            return sum;
        }

        public long SumParallel()
        {
            long sum = 0;
            long[] buckets = new long[cores];
            ParallelLoopResult lr = Parallel.For(0, cores, new Action<int>((i) =>
            {
                int start = i * ChunkSize;
                int end = start + ChunkSize;
                int bucket = i;
                long asum = 0;
                for (int a = start; a < end && a < Data.Length; a++)
                {
                    asum += Data[a];
                }
                buckets[bucket] = asum;
            }));

            for (int i = 0; i < cores; i++)
            {
                sum += buckets[i];
            }

            return sum;
        }
    }
}

何かご意見は?私は非同期/待機を間違っていますか? どんな提案でも喜んで試します。

4

4 に答える 4

26

「非同期」と「並列化」を区別することが重要です。 await非同期コードの記述を容易にするためにあります。並列で実行されるコードには非同期が含まれる場合と含まれない場合があり、非同期のコードは並列で実行される場合とされない場合があります。

await並列コードを高速化するようには設計されていません。の目的は、パフォーマンスへの悪影響を最小限に抑えながら、await非同期コードの記述を容易にすることです。を使用してawaitも、正しく記述された非 await 非同期コードよりも高速になることはありません (ただし、正しいコードを記述するawait方が簡単であるため、プログラマが await なしでその非同期コードを正しく記述できないか、そうでないため、より高速になる場合があります)。非非同期コードが適切に記述されていれば、コードよりも少しは良くないとしても、ほぼ同じように動作しawaitます。

C# は、特に並列化をサポートしていますが、具体的ではありませんawait。Task Parallel Library (TPL) と Parallel LINQ (PLINQ) には、コードを並列化するための非常に効果的な手段がいくつかあります。これらの手段は、単純なスレッド化された実装よりも一般的に効率的です。

あなたの場合、PLINQ を使用した効果的な実装は次のようになります。

public static int Sum(int[] array)
{
    return array.AsParallel().Sum();
}

これにより、入力シーケンスが並列に実行されるチャンクに効率的に分割されることに注意してください。チャンクの適切なサイズと同時ワーカーの数を決定し、これらのワーカーの結果を適切に集約して、正しい結果を確保するために適切に同期し(スレッド化された例とは異なり)、効率的です(つまり、すべての集計を完全にシリアル化するわけではありません)。

于 2013-03-27T17:57:46.650 に答える
12

async負荷の高い並列計算は意図されていません。Task.Runを使用して基本的な並列作業を行うことができますが、本格的Task.WhenAllな並列作業はタスク並列ライブラリ (例: Parallel) を使用して行う必要があります。クライアント側の非同期コードは、並列処理ではなく応答性に関するものです。

一般的なアプローチはParallel並列作業に を使用し、それを にラップしてTask.Run使用awaitし、UI の応答性を維持することです。

于 2013-03-27T17:55:45.100 に答える
10

ベンチマークにはいくつかの欠陥があります。

  • 初期化時間 (ロードclass Task、JIT コンパイルなど)を含む最初の実行のタイミングを計っています。
  • を使用していますがDateTime.Now、これはミリ秒範囲のタイミングには不正確すぎます。使用する必要がありますStopWatch

これら 2 つの問題が修正されました。次のベンチマーク結果が得られます。

Regular Sum:  499946 in 00:00:00.0047378
Async Sum:    499946 in 00:00:00.0016994
Threaded Sum: 499946 in 00:00:00.0026898

Async が最速のソリューションとして登場し、2 ミリ秒未満で済みます。

これが次の問題です。2 ミリ秒という速さで何かを計測することは、非常に信頼性が低くなります。他のプロセスがバックグラウンドで CPU を使用している場合、スレッドはそれより長く一時停止する可能性があります。数千回のベンチマーク実行で結果を平均化する必要があります。

また、コア検出数はどうなっていますか?私のクアッドコアは 333334 のチャンク サイズを使用しており、3 つのスレッドしか実行できません。

于 2013-03-27T18:08:14.060 に答える
6

ざっと見てみると、結果は予想どおりです。非同期合計は 1 つのスレッドのみを使用しており、非同期で終了を待機しているため、マルチスレッド合計よりも遅くなります。

仕事をしている間に他に何かを完了する必要がある場合は、非同期を使用します。したがって、これは速度/応答の改善のための適切なテストにはなりません。

于 2013-03-27T17:55:58.677 に答える