【问题标题】:Summing every element in a byte array对字节数组中的每个元素求和
【发布时间】:2018-08-27 07:54:28
【问题描述】:

现在,我是线程和异步/同步编程以及所有这些东西的新手。所以,我一直在练习,在 youtube 上看到了这个问题。问题是对字节数组的每个内容求和。它来自名为 Jamie King 的频道。他用线程做到了这一点。我决定用任务来做这件事。我把它做成异步的,它比同步的慢。两者相差360毫秒!我想知道你们中的任何人是否可以以异步方式更快地完成它。如果有,请发帖! 这是我的:

    static Random Random = new Random(999);

    static byte[] byteArr = new byte[100_000_000];
    static byte TaskCount = (byte)Environment.ProcessorCount;
    static int readingLength;

    static void Main(string[] args)
    {
        for (int i = 0; i < byteArr.Length; i++)
        {
            byteArr[i] = (byte)Random.Next(11);
        }

        SumAsync(byteArr);
    }

    static async void SumAsync(byte[] bytes)
    {
        readingLength = bytes.Length / TaskCount;
        int sum = 0;
        Console.WriteLine("Running...");

        Stopwatch watch = new Stopwatch();

        watch.Start();
        for (int i = 0; i < TaskCount; i++)
        {
            Task<int> task = SumPortion(bytes.SubArray(i * readingLength, readingLength));
            int result = await task;
            sum += result;
        }

        watch.Stop();

        Console.WriteLine("Done! Time took: {0}, Result: {1}", watch.ElapsedMilliseconds, sum);

    }

    static async Task<int> SumPortion(byte[] bytes)
    {
        Task<int> task = Task.Run(() =>
        {
            int sum = 0;
            foreach (byte b in bytes)
            {
                sum += b;
            }
            return sum;
        });

        int result = await task;

        return result;
    }

注意bytes.SubArray 是一种扩展方法。我有一个问题。异步编程比同步编程慢吗? 请指出我的错误。

感谢您的宝贵时间!

【问题讨论】:

标签: c# multithreading task


【解决方案1】:

您需要使用WhenAll()并在最后返回所有任务:

    static async void SumAsync(byte[] bytes)
    {
        readingLength = bytes.Length / TaskCount;
        int sum = 0;
        Console.WriteLine("Running...");

        Stopwatch watch = new Stopwatch();

        watch.Start();
        var results = new Task[TaskCount];
        for (int i = 0; i < TaskCount; i++)
        {
            Task<int> task = SumPortion(bytes.SubArray(i * readingLength, readingLength));
            results[i] = task
        }
        int[] result = await Task.WhenAll(results);
        watch.Stop();

        Console.WriteLine("Done! Time took: {0}, Result: {1}", watch.ElapsedMilliseconds, result.Sum());

    }

当您使用WhenAll() 方法时,您会合并所有Task 结果,因此任务将并行运行,为您节省大量必要的时间。

您可以在docs.microsoft.com 中阅读更多相关信息。

【讨论】:

  • @MisterMagoo 添加了解释:)
  • 这对我很有帮助。它从 360 毫秒的差异提高到 106 毫秒。非常感谢!
【解决方案2】:

异步并不明显变慢 - 而是在后台运行(例如等待与网站建立连接) - 因此主线程在等待某事发生时不会停止。

【讨论】:

    【解决方案3】:

    最快的方法可能是手动滚动Parallel.ForEach() 循环。

    与单线程方法相比,Plinq 甚至可能不会给您带来加速,而且肯定不会像 Parallel.ForEach() 那样快。

    这是一些示例计时代码。当您尝试此操作时,请确保它是 RELEASE 构建并且您没有在调试器下运行它(这将关闭 JIT 优化器,即使它是 RELEASE 构建):

    using System;
    using System.Collections.Concurrent;
    using System.Diagnostics;
    using System.Linq;
    using System.Threading;
    using System.Threading.Tasks;
    
    namespace Demo
    {
        static class Program
        {
            static void Main()
            {
                // Create some random bytes (using a seed to ensure it's the same bytes each time).
    
                var rng = new Random(12345);
                byte[] byteArr = new byte[500_000_000];
                rng.NextBytes(byteArr);
    
                // Time single-threaded Linq.
    
                var sw = Stopwatch.StartNew();
                long sum = byteArr.Sum(x => (long)x);
                Console.WriteLine($"Single-threaded Linq took {sw.Elapsed} to calculate sum as {sum}");
    
                // Time single-threaded loop;
    
                sw.Restart();
                sum = 0;
    
                foreach (var n in byteArr)
                    sum += n;
    
                Console.WriteLine($"Single-threaded took {sw.Elapsed} to calculate sum as {sum}");
    
                // Time Plinq
    
                sw.Restart();
                sum = byteArr.AsParallel().Sum(x => (long)x);
                Console.WriteLine($"Plinq took {sw.Elapsed} to calculate sum as {sum}");
    
                // Time Parallel.ForEach() with partitioner.
    
                sw.Restart();
                sum = 0;
    
                Parallel.ForEach
                (
                    Partitioner.Create(0, byteArr.Length),
    
                    () => 0L,
    
                    (subRange, loopState, threadLocalState) =>
                    {
                        for (int i = subRange.Item1; i < subRange.Item2; i++)
                            threadLocalState += byteArr[i];
    
                        return threadLocalState;
                    },
    
                    finalThreadLocalState =>
                    {
                        Interlocked.Add(ref sum, finalThreadLocalState);
                    }
                );
    
                Console.WriteLine($"Parallel.ForEach with partioner took {sw.Elapsed} to calculate sum as {sum}");
            }
        }
    }
    

    在我的八核 PC 上使用 x64 构建得到的结果是:

    • 单线程 Linq 用 00:00:03.1160235 计算总和为 63748717461
    • 单线程取00:00:00.7596687求和为63748717461
    • Plinq 用 00:00:01.0305913 计算总和为 63748717461
    • Parallel.ForEach 与partioner 采用00:00:00.0839141 计算总和为63748717461

    我使用 x86 构建得到的结果是:

    • 单线程 Linq 采用 00:00:02.6964067 计算总和为 63748717461
    • 单线程取00:00:00.8200462计算总和为63748717461
    • Plinq 用 00:00:01.1251899 计算总和为 63748717461
    • Parallel.ForEach 与partioner 采用00:00:00.1084805 计算总和为63748717461

    如您所见,使用 x64 构建的 Parallel.ForEach() 最快(可能是因为它计算了 long 总数,而不是因为更大的地址空间)。

    Plinq 比 Linq 非线程解决方案快大约三倍。

    带有分区器的Parallel.ForEach() 速度提高了 30 倍以上。

    但值得注意的是,非 linq 单线程代码比 Plinq 代码快。在这种情况下,使用Plinq 是没有意义的;它让事情变得更慢!

    这告诉我们,加速不仅仅是来自多线程 - 它还与 LinqPlinq 的开销与手动滚动循环相比。

    一般来说,你应该只在每个元素的处理时间比较长的时候使用Plinq(并且将一个字节添加到运行总和需要很短的时间)。

    Plinq 相对于带有分区器的Parallel.ForEach() 的优势在于它编写起来更简单 - 但是,如果它最终比简单的foreach 循环慢,那么它的实用性是值得怀疑的.因此,在选择解决方案之前计时非常重要!

    【讨论】:

    • 这太完美了!你向我介绍了很多全新的东西。非常感谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-02-23
    • 2021-06-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多