簡體   English   中英

為什么等待異步這么慢?

[英]Why is await async so slow?

我終於得到了VS2012並得到了一個簡單的演示並且正在努力檢查異步的潛在性能提升並等待,但令我沮喪的是它更慢! 它可能我做錯了,但也許你可以幫助我。 (我還添加了一個簡單的Threaded解決方案,並且按預期運行得更快)

我的代碼使用一個類來根據系統中的內核數量對數組求和(-1)我有4個內核,所以我看到了大約2倍的加速(2.5個線程)用於線程,但是減少了2倍的速度同樣的事情,但使用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;
        }
    }
}

有什么想法嗎? 我在做異步/等待錯誤嗎? 我很樂意嘗試任何建議。

將“異步”與“並行化”分開是很重要的。 await有助於簡化編寫異步代碼。 並行運行的代碼可能(或可能不)涉及異步,並且異步的代碼可能會或可能不會並行運行。

沒有關於await任何內容旨在使並行代碼更快。 await的目的是使編寫異步代碼更容易 ,同時最大限度地減少負面性能影響。 使用await永遠不會比正確編寫的非等待異步代碼更快(盡管因為使用await編寫正確的代碼更容易,它有時會更快,因為程序員無法在沒有等待的情況下正確編寫異步代碼,或者不是我不願意花時間去做這件事。如果非同步代碼編寫得很好,它會比await代碼更好地執行,如果不是更好。

C#確實有專門針對並行化的支持,它只是沒有特別的await 任務並行庫(TPL)以及並行LINQ(PLINQ)具有幾種非常有效的並行化代碼的方法,通常比天真的線程實現更有效。

在您的情況下,使用PLINQ的有效實現可能是這樣的:

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

請注意,這將有效地將輸入序列划分為將並行運行的塊; 它將負責確定塊的適當大小和並發工作器的數量,並且它將適當地聚合那些正確同步的庄園中的工作者的結果,以確保正確的結果(與您的線程示例不同)並且高效(意味着它不會完全序列化所有聚合)。

async不適用於重型並行計算。 您可以使用Task.RunTask.WhenAll進行基本的並行工作,但任何嚴肅的並行工作都應該使用任務並行庫(例如, Parallel )完成。 客戶端的異步代碼是關於響應性 ,而不是並行處理

一種常見的方法是使用Parallel進行並行工作,然后將其包裝在Task.Run並使用await來保持UI響應。

您的基准測試有幾個缺陷:

  • 你是第一次運行的計時,包括初始化時間(加載class Task ,JIT編譯等)
  • 您正在使用DateTime.Now ,這對於毫秒范圍內的計時來說太不准確了。 您需要使用StopWatch

修好了這兩個問題; 我得到以下基准測試結果:

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個線程。

快速查看,結果是預期的:您的異步和只使用一個線程,而異步等待它完成,所以它比多線程總和慢。

您可以使用異步,以防在完成工作時還有其他內容可以完成。 因此,對於任何速度/響應改進而言,這都不是正確的測試。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM