C#任务(带或不带TaskContinuationOptions.ExecuteSynchronously)性能与线程循



我在考虑处理操作队列时的最佳工作技术(操作顺序很重要,所以每个操作都必须在前一个操作之后)

我对运行带有延续选项的Task的性能结果感到非常失望,这些选项假设在同一个线程上运行(我原本希望得到与循环中运行的线程类似的结果)。。。。我很感激对这些性能结果的任何评论,顺便说一句,JIT似乎提高了性能,因此在某些情况下,在没有任何线程的情况下运行比有线程的情况要好,在我下面的例子中,你可以看到,在主线程中处理450K项目的数组大小比其他运行循环的线程更快


考虑的方法:

  • 在每个操作上运行simple-for-loop(顺序方法)
  • 运行Task.ContinueWith方法。这个方法是在线程池线程上执行的,每个其他操作也是在它的前一个操作之后执行的(线程池决定它在哪个线程上运行)
  • 运行Task.ContinueWith方法,其中TaskContinuationOptions设置为TaskContinuationOptions.ExecuteSynchronously,这导致所有任务都在同一线程上执行
  • 在单独的线程上运行-新的System.Threading.thread使用for循环执行操作

方法代码:

我创建了一个简单的测试应用程序,它在和数组上运行,并设置arr[I]=I*I其中i是:100000到450000(每次测试之间跳跃50000)

结果:

-------------------------100000个项目的运行测试&迭代-------------------------

结果简单:24.0013 MS

Task.ContinuteWith()结果:691.0395 MS

Task.ContinuteWith(TaskContinuationOptions.ExecuteSynchronously)结果:91.0052 MS

线程启动结果:16.0009 MS

{跳过…跳过…跳过-这太长了…}

-------------------------450000个项目的运行测试;迭代-------------------------

结果简单:16.0009 MS

Task.ContinuteWith()结果:3686.2108 MS

Task.ContinuteWith(TaskContinuationOptions.ExecuteSynchronously)结果:415.0238 MS

线程启动结果:35.002 MS

按任意键退出

源代码

static int max = 100000;
    static int[] array;
    static DateTime start;
    static int valueOfMax = 0;
    static void Main(string[] args)
    {
        for (valueOfMax = max; valueOfMax < max * 5; valueOfMax += (max/2))
        {
            Console.WriteLine(string.Format("------------------------- Running test with {0} items & iterations---------------------------", valueOfMax));
            array = new int[valueOfMax];
            start = DateTime.Now;
            Console.Write("Simple for results :                                                                 ");
            for (int i = 0; i < valueOfMax; i++)
            {
                doSomething(i);
            }
            start = DateTime.Now;
            Action<int> action = doSomething;
            Task lastTask = Task.Factory.StartNew(() => { int p = 4; });
            Console.Write("Task.ContinueWith() result :                                                         ");
            for (int i = 0; i < valueOfMax; i++)
            {
                var valueOfI = i;
                lastTask = lastTask.ContinueWith((task) => doSomething(valueOfI));
            }
            lastTask.Wait();
            start = DateTime.Now;
            lastTask = Task.Factory.StartNew(() => { int p = 4; });
            Console.Write("Task.ContinueWith(TaskContinuationOptions.ExecuteSynchronously) result :             ");
            for (int i = 0; i < valueOfMax; i++)
            {
                var valueOfI = i;
                lastTask = lastTask.ContinueWith((task) => doSomething(valueOfI), TaskContinuationOptions.ExecuteSynchronously);
            }
            lastTask.Wait();
            start = DateTime.Now;
            Thread t = new Thread(delegate()
            {
                for (int i = 0; i < valueOfMax; i++)
                {
                    doSomething(i);
                }
            });
            Console.Write("Thread.Start result :                                                                ");
            t.Start();
            t.Join();
        }
        Console.WriteLine("Press any key to exit");
        Console.ReadKey();
    }
    static void doSomething(int i)
    {
        array[i] = i * i;
        if ((i+1) == valueOfMax)
        {
            DateTime end = DateTime.Now;
            var diff = end - start;
            Console.WriteLine(string.Format("{0} MS", diff.TotalMilliseconds));
        }
    }

首先,不应该使用DateTime.Now进行性能测量,因为它太不精确了。您应该使用StopWatch。在这种情况下,这样做会使测量结果大不相同。

其次,当您第一次调用一个方法时,必须对其进行JIT编译,因此您可能应该忽略第一轮的结果。

第三,您应该在Release more中运行它,而不附加调试器(Ctrl+F5,而不是F5),如果您还没有这样做的话。

第四,别忘了GC,它可以以不可预测的方式改变你的测量。

现在,让我们考虑一下您将要做什么:如果您想在循环中运行一些代码,那么只需在循环中执行即可。简单的循环非常有效,很难有任何性能接近它的东西。

Task呢?我认为将它们用于如此简单的操作是不现实的。如果你想快速重复地运行简单的操作,你应该让你的代码尽可能简单,而不是涉及闭包、堆分配、线程同步和谁知道还有什么,如果你像现在这样使用Task,这些都是必要的。

总之,如果您有一个想要执行很多次的简单操作,只需使用简单循环即可。我看不出有任何理由使用其他东西。有了循环,你就知道计算机将执行你的代码,而(几乎)什么都不执行。

TaskContinueWith()确实有它们的位置,特别是如果你有一些更复杂的控制流(比如有一些任务做了一些事情,然后有两个不同的任务在其中一个完成后开始,然后有另一个任务在它们完成后开始)。或者,如果您想使应用程序可组合。但是,如果尝试使用它们而不是for循环,不要惊讶于结果不如恒星。

您说所有项目都需要按顺序执行。这意味着最多可以有一个CPU处于繁忙状态。因此,您正在做同样数量的工作,仍然在单个CPU上,但有额外的开销当然,这个比较慢。我不知道你在期待什么。

我认为你真正想要的是有一个专门的线程来处理你的工作项,并从BlockingCollection中提取它们。这里有一个非常好的教程:http://blogs.msdn.com/b/csharpfaq/archive/2010/08/12/blocking-collection-and-the-producer-consumer-problem.aspx

最新更新