7

Parallel.For ループを使用して、計算の実行速度を上げています。

計算にかかるおおよその時間を測定したいと思います。通常、各ステップにかかる時間を測定し、ステップ時間にステップの総数を掛けて合計時間を見積もるだけです。

たとえば、100 のステップがあり、一部のステップに 5 秒かかる場合、合計時間が約 500 秒になることを除けばよいでしょう。(いくつかのステップを平均して、私がやりたいことをユーザーに継続的に報告することができます)。

これを行うために私が考えることができる唯一の方法は、parallel.for 間隔を分割し、それぞれを測定することによって、本質的に元の方法に戻る外側の for ループを使用することです。

for(i;n;i += step)
    Time(Parallel.For(i, i + step - 1, ...))

これは一般的にあまり良い方法ではありません。数回の非常に長いステップまたは多数の短いステップがタイミングの問題を引き起こすからです。

誰にもアイデアはありますか?

(合計時間ではなく、parallel.for の完了にかかる時間をリアルタイムで見積もる必要があることを認識してください。実行にどれだけの時間が残っているかをユーザーに知らせたいのです)。

4

6 に答える 6

4

この方法はかなり効果があるようです。各並列ループにカウンターをインクリメントさせるだけで、並列 for ループを「線形化」できます。

Parallel.For(0, n, (i) => { Thread.Sleep(1000); Interlocked.Increment(ref cnt); });     

(注、Niclas のおかげで、これ++はアトミックではなく、lockorを使用する必要がありますInterlocked.Increment)

並行して実行される各ループは、 をインクリメントしますcnt。効果は にcnt単調に増加しncnt/nfor が完了した割合です。には競合がないため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();
        }
    }
}
于 2012-10-15T21:16:34.557 に答える
1

これはほとんど答えられません。

まず第一に、すべてのステップが何をするのかが明確ではありません。一部の手順は、I/O 集約型または計算集約型である場合があります。

さらに、Parallel.For は要求です。コードが実際に並列で実行されるかどうかはわかりません。コードが実際に並列で実行されるかどうかは、状況 (スレッドとメモリの可用性) によって異なります。次に、I/O に依存する並列コードがある場合、I/O が完了するのを待っている間、1 つのスレッドが他のスレッドをブロックします。また、他のプロセスが何をしているのかもわかりません。

これが、何かにかかる時間を予測する際に非常にエラーが発生しやすく、実際には無駄なことです。

于 2012-10-15T11:42:37.967 に答える
1

以前に完了したすべてのタスクの平均を測定するための可能な解決策を次に示します。各タスクが終了すると、Action<T>が呼び出され、すべての時間を要約して、終了したタスクの合計で割ることができます。ただし、これは現在の状態に過ぎず、将来のタスクや平均を予測する方法はありません。(他の方もおっしゃっていますが、これはかなり難しいです)

ただし、メソッドレベルで宣言された変数の両方でロック競合が発生する可能性があるため、問題に適合するかどうかを測定する必要があります。

     static void ComputeParallelForWithTLS()
            {
                var collection = new List<int>() { 1000, 2000, 3000, 4000 }; // values used as sleep parameter
                var sync = new object();
                TimeSpan averageTime = new TimeSpan();
                int amountOfItemsDone = 0; // referenced by the TPL, increment it with lock / interlocked.increment

                Parallel.For(0, collection.Count,
                    () => new TimeSpan(),
                    (i, loopState, tlData) =>
                    {
                        var sw = Stopwatch.StartNew();
                        DoWork(collection, i);
                        sw.Stop();
                        return sw.Elapsed;
                    },
                    threadLocalData =>   // Called each time a task finishes
                    {
                        lock (sync)
                        {
                            averageTime += threadLocalData; // add time used for this task to the total.
                        }
                        Interlocked.Increment(ref amountOfItemsDone); // increment the tasks done
                        Console.WriteLine(averageTime.TotalMilliseconds / amountOfItemsDone + ms."); 
/*print out the average for all done tasks so far. For an estimation, 
multiply with the remaining items.*/
                    });
            }
            static void DoWork(List<int> items, int current)
            {
                System.Threading.Thread.Sleep(items[current]);
            }
于 2012-10-15T12:32:11.793 に答える
1

この問題は、答えるのが難しい問題です。非常に長いステップまたは多数の非常に短いステップを使用するタイミングの問題は、並列パーティショナーが処理できる範囲の端でループが機能することに関連している可能性があります。

デフォルトのパーティショナーは非常に動的であり、実際の問題については何もわかっていないため、動的負荷分散による並列実行の利点を享受しながら、目の前の問題を解決できる良い答えはありません。

予測される実行時間の信頼できる見積もりを達成することが非常に重要な場合は、おそらくカスタム パーティショナーを設定し、パーティショニングに関する知識を活用して、1 つのスレッドのいくつかのチャンクからタイミングを推測できます。

于 2012-10-15T11:58:42.620 に答える
0

ここで、時間と速度を測定するクラスを書きました

public static class Counter
{
    private static long _seriesProcessedItems = 0;
    private static long _totalProcessedItems = 0;
    private static TimeSpan _totalTime = TimeSpan.Zero;
    private static DateTime _operationStartTime;
    private static object _lock = new object();
    private static int _numberOfCurrentOperations = 0;



    public static void StartAsyncOperation()
    {
        lock (_lock)
        {
            if (_numberOfCurrentOperations == 0)
            {
                _operationStartTime = DateTime.Now;   
            }

            _numberOfCurrentOperations++;
        }
    }

    public static void EndAsyncOperation(int itemsProcessed)
    {
        lock (_lock)
        {
            _numberOfCurrentOperations--;
            if (_numberOfCurrentOperations < 0) 
                throw new InvalidOperationException("EndAsyncOperation without StartAsyncOperation");

            _seriesProcessedItems +=itemsProcessed;

            if (_numberOfCurrentOperations == 0)
            {
                _totalProcessedItems += _seriesProcessedItems;
                _totalTime += DateTime.Now - _operationStartTime;
                _seriesProcessedItems = 0;
            }
        }
    }

    public static double GetAvgSpeed()
    {
        if (_totalProcessedItems == 0) throw new InvalidOperationException("_totalProcessedItems is zero");
        if (_totalProcessedItems == 0) throw new InvalidOperationException("_totalTime is zero");
        return _totalProcessedItems / (double)_totalTime.TotalMilliseconds;
    }

    public static void Reset()
    {
        _totalProcessedItems = 0;
        _totalTime = TimeSpan.Zero;
    }
}

使用例とテスト:

    static void Main(string[] args)
    {
        var st = Stopwatch.StartNew();
        Parallel.For(0, 100, _ =>
        {
            Counter.StartAsyncOperation();
            Thread.Sleep(100);
            Counter.EndAsyncOperation(1);
        });

        st.Stop();
        Console.WriteLine("Speed correct {0}", 100 / (double)st.ElapsedMilliseconds);

        Console.WriteLine("Speed to test {0}", Counter.GetAvgSpeed());
    }
于 2014-02-11T22:10:36.250 に答える
0

メソッドが完了したら、各ステップレポートでメソッドを実行することを提案します。もちろん、これはスレッドセーフでは少し注意が必要なので、実装するときに覚えておく必要があります。これにより、合計のうち完了したタスクの数を追跡できます。また、個々のステップに費やされた時間を (ある程度) 簡単に知ることができます。これは、外れ値などを取り除くのに役立ちます。

編集:アイデアを示すためのいくつかのコード

Parallel.For(startIdx, endIdx, idx => {
    var sw = Stopwatch.StartNew();
    DoCalculation(idx);
    sw.Stop();
    var dur = sw.Elapsed;
    ReportFinished(idx, dur);
});

ここで重要なのはReportFinished、完了したタスクの数とそれぞれの期間に関する継続的な情報を提供することです。これにより、このデータの統計を行うことで、残り時間についてより正確な推測を行うことができます。

于 2012-10-15T11:36:28.560 に答える