这种方法似乎很有效。我们可以通过简单地让每个并行循环增加一个计数器来“线性化”并行 for 循环:
Parallel.For(0, n, (i) => { Thread.Sleep(1000); Interlocked.Increment(ref cnt); });
(注意,感谢 Niclas,这++
不是原子的,必须使用lock
or Interlocked.Increment
)
每个并行运行的循环都会递增cnt
。效果是cnt
单调增加到n
,并且cnt/n
是 for 完成的百分比。由于没有争用cnt
,因此不存在并发问题,并且非常快速且非常准确。
For
我们可以通过简单的计算来测量执行过程中任何时候并行循环的完成百分比cnt/n
总计算时间可以很容易地通过将循环开始以来经过的时间除以循环所处的百分比来估算。当每个循环花费大致相同的时间量时,这两个量应该具有大致相同的变化率,并且表现相对良好(也可以平均小波动)。
显然,每个任务越不可预测,剩余计算时间就越不准确。这是意料之中的,一般来说,没有解决方案(这就是为什么它被称为近似值)。我们仍然可以完全准确地获得经过的计算时间或百分比。
“剩余时间”算法的任何估计的基本假设是每个子任务花费大约相同的计算时间(假设一个想要线性结果)。例如,如果我们有一个并行方法,其中 99 个任务非常快,1 个任务非常慢,我们的估计将非常不准确。我们的计数器会很快拉到 99,然后坐在最后一个百分比,直到缓慢的任务完成。我们可以线性插值并进行进一步的估计以获得更平滑的倒计时,但最终会有一个突破点。
以下代码演示了如何有效地测量并行度。注意 100% 的时间是真正的总执行时间,可以作为参考。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;
namespace ParallelForTiming
{
class Program
{
static void Main(string[] args)
{
var sw = new Stopwatch();
var pct = 0.000001;
var iter = 20;
var time = 20 * 1000 / iter;
var p = new ParallelOptions(); p.MaxDegreeOfParallelism = 4;
var Done = false;
Parallel.Invoke(() =>
{
sw.Start();
Parallel.For(0, iter, p, (i) => { Thread.Sleep(time); lock(p) { pct += 1 / (double)iter; }});
sw.Stop();
Done = true;
}, () =>
{
while (!Done)
{
Console.WriteLine(Math.Round(pct*100,2) + " : " + ((pct < 0.1) ? "oo" : (sw.ElapsedMilliseconds / pct /1000.0).ToString()));
Thread.Sleep(2000);
}
}
);
Console.WriteLine(Math.Round(pct * 100, 2) + " : " + sw.ElapsedMilliseconds / pct / 1000.0);
Console.ReadKey();
}
}
}