この方法はかなり効果があるようです。各並列ループにカウンターをインクリメントさせるだけで、並列 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
合計計算時間は、ループの開始からの経過時間をループのパーセンテージで割ることで簡単に見積もることができます。これらの 2 つの量は、各ループにかかる時間がほぼ同じである場合、ほぼ同じ変化率を持つ必要があり、比較的適切に動作します (小さな変動も平均化できます)。
明らかに、各タスクが予測不可能であるほど、残りの計算時間は不正確になります。これは予想されることであり、一般に解決策はありません (これが近似値と呼ばれる理由です)。経過した計算時間またはパーセンテージを完全な精度で取得できます。
「残り時間」アルゴリズムの推定の根底にある仮定は、各サブタスクがほぼ同じ計算時間を要することです (線形の結果が必要であると仮定します)。たとえば、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();
}
}
}