【问题标题】:C# Parallel and Task are too slowC# Parallel 和 Task 太慢了
【发布时间】:2018-04-05 23:09:31
【问题描述】:

我在尝试使用并行或任务计划或其他方式在 4 秒内完成工作时遇到问题。该脚本很简单,它会尝试一次处理 100 次并等待结果。我的电脑是 E5-2603 Xeon 处理器(4 核,4 线程,1.80GHz),16GB,处理 10000 个任务需要很长时间。

我可以做些什么来加快这个过程?

测试脚本

static void Main(string[] args)
        {
            //Monitor performance
            Stopwatch totalTime = new Stopwatch();


            //Test case # 1
            totalTime.Start();
            TaskTechnique();
            totalTime.Stop();
            Console.WriteLine("TaskTechnique total time: " + totalTime.Elapsed.TotalSeconds);
            Console.WriteLine("--------------------------------------");

            //Test case #2
            totalTime.Reset();
            totalTime.Start();
            ParalelelTechnique();
            totalTime.Stop();
            Console.WriteLine("ParalelelTechnique total time: " + totalTime.Elapsed.TotalSeconds);
            Console.WriteLine("--------------------------------------");

        }

When Task Class Definition Namespace: System.Threading.Tasks

处理 100 个小任务需要 21 秒。代码如下

    /// <summary>
    /// Using task to process all tasks
    /// </summary>
    private static void TaskTechnique()
    {

        //Test case 1 process all 100 tasks at once
        List<Task<string>> tasks = new List<Task<string>>();
        for (int i = 0; i < 100; i++)
        {
            tasks.Add(
                    Task.Factory.StartNew
                    (
                            () =>
                            {
                                return MrDelay(i);
                            }
                    )
                );
        }

        Task.WaitAll(tasks.ToArray());//Wait until finished 
    }

And when I used Parallel,处理时间为 33 秒。

  /// <summary>
        /// Using Paralelel to process 100 times
        /// </summary>
        private static void ParalelelTechnique()
        {
            //Monitor performance
            Stopwatch startTime = new Stopwatch();
            //Test case 2 using parallel
            startTime.Restart();
            int maxTask = 100;
            var result = Parallel.For
                (
                1, 101,
                new ParallelOptions { MaxDegreeOfParallelism = maxTask },
                (i, state) =>
                {
                    Console.WriteLine("Beginning iteration {0} at ", i, DateTime.Now.ToLocalTime());
                    string longTaskResult = MrDelay(i);
                    Console.WriteLine("Completed iteration {0} result at : {1}", i, longTaskResult);
                }
                );
            startTime.Stop();
            Console.WriteLine("test case 2: " + startTime.Elapsed.TotalSeconds);
        }

延迟处理代码。

/// <summary>
    /// Just to slow down the test to 3 seconds before return the data back
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    private static string MrDelay(int id)
    {
        //Delay 3 seconds then return the result
        System.Threading.Thread.Sleep(3000);
        return "ok request id: " + id.ToString() + "  processed time: " + DateTime.Now.ToLocalTime();
    }

【问题讨论】:

  • StartNewParallel.For 都是专门为 CPU 限制 工作而设计的。如果您实际上没有 CPU 绑定的工作要做(在您的示例中您没有),它们将无法有效工作。如果您想创建一个将在 3 秒内完成的任务,请使用 Task.Delay
  • 如果你想加快速度,你可以删除所有Thread.Sleeps!你让工人睡觉,然后想知道他们为什么不工作!
  • 另外,做一些数学运算。你有 100 个任务。每个任务需要 3 秒的工作时间。您有四个核心,因此可以同时运行四个工作人员。所以每个核心有 25 个任务,所以大约需要 75 秒。你在 21 年就完成了工作,那你为什么抱怨呢?
  • 正如 Servy 所说,这里的要点是:如果您正在执行 CPU 密集型工作,那么 您的测试需要实际对 CPU 施加压力,而不是睡眠,以便任务并行库可以有效地调度核心。如果您正在执行 IO 绑定工作,那么 您的测试应该将所有内容保存在一个线程上,并通过将等待放在正确的位置让编译器担心控制流。您创建了一个示例程序,它的所有内容都完全错误,因此得到垃圾结果也就不足为奇了。
  • 对,如果您主要下载数据,那么并行化将无济于事;它会让情况变得更糟。 为您收到的每封邮件雇用一个人是否可以让您的邮件更快送达?当然不是。您只需要 一个 人在邮件到达时 进行分类。但是,如果你有一万份纳税申报表要准备,那么雇佣四名工人确实完成工作的速度是自己完成工作的四倍。 仅当核心饱和时才并行化工作。永远不要并行化等待 IO 事件的工作。

标签: c# parallel-processing


【解决方案1】:

对于海量服务代码,你首先要回答这个问题:应该最大化什么?

例如,使用 Tasks 旨在实现“最大吞吐量”。这意味着您的任务可能会经常中断等待某事,因此让 CPU 内核切换到另一个任务,以免浪费时间。每个任务的平均执行时间相对较长,但在给定时间内处理的平均任务量却在飙升。

这里的另一种情况是“最大 CPU 消耗”。例如,那些工作人员喜欢处理音频/视频流,像密码学这样的大型数学磨坊等。这样,最有效的方法是每个备用 CPU 内核有一个线程/任务,而不是更多(因为切换到另一个线程会花费你额外的 CPU 努力)。与前面的示例相反,这里我们在给定时间内处理的平均任务量相对较低。但是由于中断最小化,平均执行时间尽可能短。

当然,在实际实践中,你在这两个最大值之间会有一些东西。当然,您必须更多地了解什么会限制您的 CPU(内核调用、缓存污染、错误共享、数据竞争等等)。您可以从“No Bugs Hare”的精彩文章开始:http://ithare.com/using-parallel-algorithm-without-a-clue-90x-performance-loss-instead-of-8x-gain/

【讨论】:

    【解决方案2】:

    感谢您的提示,我想出了另一个使用多线程的解决方案。此应用程序将用于桌面应用程序,因此这是可以接受的。我能够使用线程在不到 3 秒的时间内处理 200 个任务,而不是 100 个任务的 30 多秒。

      static void Main(string[] args)
        {
            #region MyRegion
            for (int i = 0; i < 200; i++)
            {
                //Create thread
                Thread thread = new Thread(() => DoBigJobThread(i));
                thread.Start();
            }
            #endregion
            //Slow  down to show screen
            System.Threading.Thread.Sleep(3000000);
        }
    
        static void DoBigJobThread(int id)
        {
            Console.WriteLine(MrDelaySimple(id));
        }
    
        private static string MrDelaySimple(int id)
        {
            //Delay 3 seconds
            System.Threading.Thread.Sleep(3000);
            return "ok request id: " + id.ToString() + "  processed time: " + DateTime.Now.ToLocalTime();
        }
    

    【讨论】:

    • 永远不要这样做。再次阅读 cmets;这正是解决此问题的错误方法。 IO 的正确解决方案是将所有工作保持在 一个线程 上,并使用异步工作流来协调任务。 CPU 工作的正确解决方案是线程数与处理器数一样多,而不是更多。如果你让线程来做 IO 工作,或者租用的线程多于为它们提供服务的 CPU,那么你做的事情非常非常错误。
    • 这个解决方案基本上是“我有200封信要寄,所以我要雇200个秘书”。这非常浪费,而且无法扩展。
    • 如果您想知道正确的处理方法是什么,请阅读前面评论中以“IO 的正确解决方案是......”开头的句子。
    猜你喜欢
    • 2014-10-04
    • 2015-05-08
    • 2018-07-21
    • 1970-01-01
    • 2013-03-10
    • 2014-06-07
    • 2016-05-31
    • 2011-07-07
    • 2015-08-23
    相关资源
    最近更新 更多