C#任务(有或没有TaskContinuationOptions.ExecuteSynchronously)性能与线程循环,.net线程更糟糕?

时间:2012-03-07 15:45:08

标签: .net multithreading performance task-parallel-library parallel-processing

我正在考虑处理行动队列时的最佳工作技巧(操作顺序非常重要,因此每个操作必须在之前的操作之后进行)

我对运行带有延续选项的任务的性能结果感到非常失望,这些选项假设在同一个线程上运行(我希望在循环中运行的线程具有类似的结果)....我将不胜感激关于这些性能结果的评论,顺便说一句 - 似乎JIT可能提高了性能,因此在某些情况下运行时没有任何线程比使用线程更好,在我的示例中,你可以看到450K项目的数组大小更快主线程比运行循环的其他线程


考虑的方法:

  • 在每个操作上以简单的for循环运行(顺序方法)
  • 运行Task.ContinueWith方法。此方法正在线程池线程上执行,并且其他操作也在之前执行(线程池决定它运行在哪个线程上)。
  • 运行Task.ContinueWith方法,将TaskContinuationOptions 设置为TaskContinuationOptions.ExecuteSynchronously,这导致所有任务在同一个线程上执行
  • 在单独的线程上运行 - 新的System.Threading.Thread使用for循环执行操作

方法代码:

我创建了运行于和数组的简单测试器应用程序并设置了arr [i] = i * i 我是:100,000到450,000(每次测试之间跳跃50,000)

结果:

  

-------------------------运行100000件物品的测试&迭代---------------------------

     

结果简单: 24.0013 MS

     

Task.ContinueWith()结果: 691.0395 MS

     

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

     

Thread.Start结果: 16.0009 MS

     

{skip ... skip ... skip - 这太长了......}

     

-------------------------使用450000项目运行测试&迭代---------------------------

     

结果简单: 16.0009 MS

     

Task.ContinueWith()结果: 3686.2108 MS

     

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

     

Thread.Start结果: 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));
        }
    }

2 个答案:

答案 0 :(得分:1)

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

其次,当你第一次调用一个方法时,它必须被JIT编译,所以你应该忽略第一轮的结果。

第三,你应该在没有连接调试器的情况下在Release more中运行它(Ctrl + F5,而不是F5),如果你还没有这样做的话。

第四,不要忘记GC,它可以以不可预测的方式改变您的测量结果。

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

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

总而言之,如果您想要执行很多次的简单操作,只需使用简单循环即可。我认为没有任何理由可以使用其他任何东西。通过循环,您知道计算机将执行您的代码并且(几乎)没有其他任何内容。

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

答案 1 :(得分:0)

你说所有项目都需要按顺序执行。这意味着最多只有一个CPU可以忙。所以你做的工作量相同,仍然在单个CPU上,但需要额外的开销。 当然,这个比较慢。我不确定你在期待什么。

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