C#でParallel

RecordedValues()使って、タグの情報を取得するコードを書いてます。

1000タグで、それぞれのタグにはだいたい4000のタイムスタンプと値があります。

だいたい12 -18秒くらいかかりました。このデータ量で12-18秒は一般的と言えるのでしょうか?

さらに処理時間を短くするのにParallel処理を入れてみましたが、処理時間は変わりませんでした。

下記のようなコードにして試してみました。

この場合、Parallel処理はできないのでしょうか?

また、他に処理時間を短縮できる方法がありますか?

 

IEnumerable<AFValues> res = pointList.RecordedValues(range, AFBoundaryType.Inside, "", true, config);

Parallel.ForEach(res,  pointResults =>

{

      pointResults.GetValueArrays(out values, out timestamp, out flags);

});

  • PI Data Archiveとコードは別のマシンになります。

    また、RecordedValues()の時間はほどんどかかっていません。すぐに返ってきます。

    そのあとのForEachのループで10秒とかかかってしまいます。

  • ありがとうございます。

     

    このループで際にParalell処理をいれて、処理時間を短くすることはできないのでしょうか?

    (Parallelありのコードとなしのコードで試しましたが、処理時間は変わりませんでした。)

  • 確認ですが、PI Data Archiveとコードは同じマシンで実行していますでしょうか。

    12-18秒と言いましたが、RecordedValuesの関数だけは何秒ぐらい掛かりますでしょうか。

  • 私の環境で再現し、同じぐらいの時間が掛かりました。

    GetValuesArrayの関数はただ、AFValuesを使いループしているだけなので、

    別の呼び方で早く処理ではないと考えられます。

     

    パフォーマンスが悪い場合は、生データの代わりに内挿値を取得するとイベントの数を減ることができます。

  • 明確にするために、GetValuesArray似ている関数を作成しました。

    結果は、Parallelの実行する時間とForEachのは同じぐらい掛かります。

     

            public static void GetValueArrays(AFValues afvalues, out object[] values, out AFTime[] timestamps)

            {

                int count = afvalues.Count();

                values = new object[count];

                timestamps = new AFTime[count];

                for (int i = 0; i < count; i++)

                {

                    values = afvalues.Value;

                    timestamps = afvalues.Timestamp;

                }

                return;

            }

     

    Parallelについて詳しくないですがスレッドの管理するだけに時間が掛かります。

    タグ毎に10msぐらい時間が掛かります。もし、スレッドの管理する時間は何msだとしたら、パフォーマンスがあまり変わらないと考えられます。

  • そのほか、以下ユーザーカンファレンスのビデオもパフォーマンス向上の参考になります。

    Best Practices for Building AF SDK applicationのビデオのご紹介(ユーザーカンファレンス2017)

    まずはRPC Metricsを見てみるのが良いかと思います。

  • Hello,

     

    If Google Translate does an insufficient job of my English to Japanese, I trust my colleagues Kenji Hashimoto and Jerome Lefebvre can help clean it up.

     

    GetValueArrays executes client-side upon an AFValues collection that has already been retrieved from the server.  We are in the process of updating the help to clarify that GetValueArrays is useful if you are working with a foreign Operating System (e.g. Linux) that is incapable of reading AF SDK objects.  GetValueArrays will convert the AF SDK objects to simpler values where you would pass the arrays to that foreign system.  However, if your intent is to process over those arrays within your current Windows application, then you should not be using GetValueArrays.

     

    Jerome is correct is that spinning up a thread for each parallel task carries a tiny bit of overhead.  But if you have lots of such tiny bits and add them up, then it becomes a significant bit of overhead.  If you have small, fairly fast tasks, then processing in parallel may actually take longer because each parallel thread has overhead to spin up and later spin down.  In the case of GetValueArrays, I would expect each task to be small and fairly fast because each AFValues collection is approximately 1000 values already in client memory.  If you must use GetValueArrays, it would be better to parallel in chunks of tags rather than individual tags.  For 4000 tags, perhaps 16 chunks consisting of 250 tags.  Now the spin up + spin down overhead is reduced by 3984 threads.

     

    The problem is there is also little need to parallelize into chunks because the PIPointList.RecordedValues is streaming values to you.  You would not have a full list of the AFValues per tag until after all data has been fetched.  Therefore, I really see little need to use your own parallel calls for what you have posted.

     

    The biggest thing that has me curious is the PIPagingConfiguration options specified in your config variable.  Can you share what your configuration settings are?

     

    The quick math is that with 4000 tags each with 1000 values means there will be 4 million values retrieved.  Since your concern is about performance and not errors, I can deduce that you have increased your ArcMaxCollect tuning parameter.

     

    PIPointList.RecordedValues should be what is taking the most time.  It will stream values to you, but it does that in pages as determined by your config variable.  The biggest help to your performance is to minimize idle time on the client PC while the PI Data Archive is busy fetching data.  That is to say you make the "one" data request for PIPointList.RecordedValues and then immediately process the data being returned in the many streamed pages without waiting for it all to be returned.  That way you may be completely finished processing, for example,  the first 1000 tags before the last 1000 have even been sent to you.  (Those numbers are just for illustration.)

     

    Instead of trying parallel code, I would suggest playing around with the PIPagingConfiguration.  See what happens if you did a PIPageType.TagCount with differing values of 100, 50, 25, or 10.  Because RecordedValues is being streamed in pages, I would avoid any parallel calls because there is little need for it here.

     

    UPDATE: If you would like to understand more of what's taking so much time, you can investigate using RPC Metrics in your code.  Note that for PIServer metrics, you must be using AF 2.9 or later.  Below is a blog link with code and instructions on how to easily produce such metrics:

     

    Using metrics in your AF SDK code

     

     

    日本語訳 (Japanese Translation):(from Kenji Hashimoto

    GetValueArraysは、すでにサーバーから取得されたAFValuesコレクションに対してクライアント側にて実行されます。

    AF SDKオブジェクトを読み取ることができないオペレーティングシステム(Linuxなど)で作業している場合、GetValueArraysが役立つことをヘルプファイルにも記載する予定です。

    GetValueArraysは、AF SDKオブジェクトの配列を外部システムに渡すためにより簡単な値に変換します。

    ただし、Windowsアプリケーション内でのみ、これらの配列を処理する場合は、GetValueArraysを使用する必要はありません。

     

    Jeromeの回答の中で、正しいことは、並列のタスクの中でスレッドをループさせることは小さなオーバヘッドを伴うことです。

    1つ1つは小さなオーバーヘッドですが、そのような小さなビットをたくさん実行し、それらを合計すると、オーバーヘッドはかなりのビット数となります。

    実行するタスクが少なく、かなり早く処理が終わるタスクの場合は、パラレルコールはスレッドがスピンアップして処理を実行、そしてスピンダウンするためのオーバーヘッドがあるため、パラレル処理はシングルコールに比べて実際には時間がかかる場合もあります。

    GetValueArraysの場合、各AFValuesコレクションの約1000個の値はすでにクライアントメモリにあるため、各タスクは小さくなり、高速に処理できるものになると思います。

    GetValueArraysを使用する必要がある場合は、個々のタグではなく、タグのまとまりでパラレル処理する方がよいでしょう。

    4000個のタグについては、おそらく250個のタグからなる16個のまとまりにてパラレル処理するようにします。

    4000スレッド立てる場合に比べスピンアップ+スピンダウンのオーバーヘッドが3984スレッド削減されることになります。

     

    PIPointList.RecordedValuesは値をストリーミング化しているため、パラレルコールをする必要性はほとんどありません。

    パラレルコールする場合、すべてのデータが取得されるまで、タグごとのAFValuesの完全なリストはありません。

    このため、投稿したコードに対して、パラレルに呼び出すメリットはほとんど感じません。

     

    疑問点は、config変数で指定されたPIPagingConfigurationオプションです。この設定に何を使用しているか教えてください。

     

    簡単な計算では、1000個の値を持つ4000個のタグを使用すると、400万個の値を取得することになります。

    本投稿はエラーではなくパフォーマンスに関するものなので、ArcMaxCollectチューニングパラメータを事前に増やしたと推測できます。

     

    PIPointList.RecordedValuesは、最も時間を要するものでなければなりません。

    このメソッドは値をストリーミングしますが、それはconfig変数によって決まるページ単位で行われます。

    パフォーマンスへの最大の助けとなるのは、PIデータアーカイブがデータ取得によりビジー状態になっている間に、クライアントPCのアイドル時間を最小限に抑えることです。

    つまり、PIPointList.RecordedValuesに対する「1つの」データリクエストを作成し、返されるデータがすべて返されるのを待たずに、ストリーミングされたページ単位で返されるデータをクライアントで処理できます。

    そうすれば、例えば最初の1000タグのデータが送信されてから処理を実行していれば、その処理が終わるころには残りの1000タグのデータも送信されていることも考えられます。 (これらの数字は説明のためのものです。)

     

    よってパラレルコールを試すのではなく、PIPagingConfigurationを使用することをお勧めします。

    PIPageType.TagCountにて100,50,25または10という設定を実行した場合どうなるかを確認してみてください。

    RecordedValuesはページごとにストリーミングされるため、パラレルコールを使用する理由はほとんどないです。

     

    更新:パフォーマンス時間について、もっと理解したい場合は、コード内でRPC Metricを使用して調べることができます。

    PIServerMetricでは、AF 2.9以降を使用する必要があります。以下は、このような指標を簡単に作成するためのコードと手順に関するブログのリンクです。

    Using metrics in your AF SDK code