我在考虑处理操作队列时的最佳工作技术(操作顺序很重要,所以每个操作都必须在前一个操作之后)
我对运行带有延续选项的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
,这些都是必要的。
总之,如果您有一个想要执行很多次的简单操作,只需使用简单循环即可。我看不出有任何理由使用其他东西。有了循环,你就知道计算机将执行你的代码,而(几乎)什么都不执行。
Task
和ContinueWith()
确实有它们的位置,特别是如果你有一些更复杂的控制流(比如有一些任务做了一些事情,然后有两个不同的任务在其中一个完成后开始,然后有另一个任务在它们完成后开始)。或者,如果您想使应用程序可组合。但是,如果尝试使用它们而不是for
循环,不要惊讶于结果不如恒星。
您说所有项目都需要按顺序执行。这意味着最多可以有一个CPU处于繁忙状态。因此,您正在做同样数量的工作,仍然在单个CPU上,但有额外的开销当然,这个比较慢。我不知道你在期待什么。
我认为你真正想要的是有一个专门的线程来处理你的工作项,并从BlockingCollection中提取它们。这里有一个非常好的教程:http://blogs.msdn.com/b/csharpfaq/archive/2010/08/12/blocking-collection-and-the-producer-consumer-problem.aspx