質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
Windows 10

Windows 10は、マイクロソフト社がリリースしたOSです。Modern UIを標準画面にした8.1から、10では再びデスクトップ主体に戻され、UIも変更されています。PCやスマホ、タブレットなど様々なデバイスに幅広く対応していることが特徴です。

UWP

UWPは、Universal Windows Platformの略。様々なデバイス向けに提供されているアプリケーションを共通のフレームワーク上で動作可能にする仕組みで、Windows10で導入されました。

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Windows

Windowsは、マイクロソフト社が開発したオペレーティングシステムです。当初は、MS-DOSに変わるOSとして開発されました。 GUIを採用し、主にインテル系のCPUを搭載したコンピューターで動作します。Windows系OSのシェアは、90%を超えるといわれています。 パソコン用以外に、POSシステムやスマートフォンなどの携帯端末用、サーバ用のOSもあります。

Q&A

解決済

1回答

3483閲覧

C#(UWP)でグラフをリアルタイムで遅延させることなくプロットしたいです。

fuha_drms

総合スコア5

Windows 10

Windows 10は、マイクロソフト社がリリースしたOSです。Modern UIを標準画面にした8.1から、10では再びデスクトップ主体に戻され、UIも変更されています。PCやスマホ、タブレットなど様々なデバイスに幅広く対応していることが特徴です。

UWP

UWPは、Universal Windows Platformの略。様々なデバイス向けに提供されているアプリケーションを共通のフレームワーク上で動作可能にする仕組みで、Windows10で導入されました。

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Windows

Windowsは、マイクロソフト社が開発したオペレーティングシステムです。当初は、MS-DOSに変わるOSとして開発されました。 GUIを採用し、主にインテル系のCPUを搭載したコンピューターで動作します。Windows系OSのシェアは、90%を超えるといわれています。 パソコン用以外に、POSシステムやスマートフォンなどの携帯端末用、サーバ用のOSもあります。

0グッド

0クリップ

投稿2019/08/29 14:31

c#(UWP)でグラフを遅延なくリアルタイムプロットしたい

C#の初心者です。
windows10でUWPを使ってリアルタイムでグラフをプロットするようなスクリプトを書いています。

グラフのライブラリはOxyPlotを使用しています。

具体的にはデータが逐次入っていって、データが500個溜まると、右から左へグラフがスライドするというものです。
ここまでは実現できたのですが、そのままプログラムを動かし続けると、だんだんとグラフがカクカクとスライドするようになり、0.8秒から1秒ほど止まってはスライドしてまた止まるという挙動を繰り返しています。
グラフをスライドしている間はGC Collectionは頻繁に作動している状態です。
グラフを描画するコードの最初と最後に時間を測って実行時間(コードの中のDifference_time)を算出しづづけているのですが、
プログラムの実行時間が長くなるにつれてこの実行時間も長くなってしまいます。
その結果、グラフがスライドするときに止まったり動いたりという挙動を繰り返しています。

グラフが一時的に止まらずにスムーズにスライドする状態を維持したいのですが、

なぜこのような現象が起きているのでしょうか?
GCCollectionが動いていてもメモリーがなくなっているからでしょうか?
それともそれぞれのコードの小さな実行時間が積み重なった結果グラフが止まってしまうのでしょうか?
改善策を教えていただきたいです。

グラフの描写は、Task.Run(() => Graph());で別スレッドを立てて非同期で実行して、
グラフの値はずっと1000で固定しています。

該当のソースコード

private int cnt = 0; private async Task Graph() { while (true) { //計測開始時間を算出 var start_time = long.Parse(DateTime.Now.ToString("yyyyMMddHHmmssffff")); //グラフの値は一定で試してみる int number = 1000; //グラフの初期設定(軸のメモリやタイトルなど) var newMyModel = new PlotModel { Title = "Example 1" }; MyModel = newMyModel; MyModel.Axes.Add(new LinearAxis { Position = AxisPosition.Bottom, Minimum = 0.0, Maximum = 300.0, Title = "X軸" }); MyModel.Axes.Add(new LinearAxis { Position = AxisPosition.Left, Minimum = 0.0, Maximum = 5000.0, Title = "Y軸" }); line = new LineSeries(); line.Color = OxyColors.Blue; MyModel.Series.Add(line); //グラフの値(number)を投入 line.Points.Add(new DataPoint(cnt, number)); await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, async () => { //ここでUIのviewにグラフを表示させる GraphPloting.Model = MyModel; }); if (line.Points.Count > 500) { //500個データが溜まったら、軸移動 MyModel.Axes.Clear(); MyModel.Axes.Add(new LinearAxis { Position = AxisPosition.Bottom, Minimum = cnt - 500, Maximum = (cnt - 500) + 500 }); //x軸の設定 } else { //500個溜まるまでは軸はスライドさせない MyModel.Axes.Clear(); MyModel.Axes.Add(new LinearAxis { Position = AxisPosition.Bottom, Minimum = 0.0, Maximum = cnt }); //x軸の設定 } if (line.Points.Count > 500) { //軸移動によってはみ出した値を消す line.Points.RemoveAt(0); } //横軸の値を増やす cnt += 1; //終了時間を計測 var end_time = long.Parse(DateTime.Now.ToString("yyyyMMddHHmmssffff")); //ここでどんどん計算時間が増えていく Debug.WriteLine("Difference_time: " + (end_time - start_time)); //ここで移動した軸やグラフの値を更新。 //しかし上記のDifference_timeが増えるにつれてグラフのスライドがカクカクして度々0.8秒ほど止まってしまう MyModel.InvalidatePlot(true); } }

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

初期化したら使いまわせるであろうPlotModelLineSeriesのインスタンスは初回一回のみの作成にして、while内でnewを極力使わないように調整すればGCの頻度を抑えられるのではないかと思います。

csharp

1private int cnt = 0; 2private async Task Graph() 3{ 4 //グラフの初期設定(軸のメモリやタイトルなど) 5 var newMyModel = new PlotModel { Title = "Example 1" }; 6 MyModel = newMyModel; 7 MyModel.Axes.Add(new LinearAxis { Position = AxisPosition.Bottom, Minimum = 0.0, Maximum = 300.0, Title = "X軸" }); 8 MyModel.Axes.Add(new LinearAxis { Position = AxisPosition.Left, Minimum = 0.0, Maximum = 5000.0, Title = "Y軸" }); 9 line = new LineSeries(); 10 line.Color = OxyColors.Blue; 11 MyModel.Series.Add(line); 12 13 await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, async () => 14 { 15 //ここでUIのviewにグラフを表示させる 16 GraphPloting.Model = MyModel; 17 }); 18 19 while (true) 20 { 21 //計測開始時間を算出 22 var start_time = long.Parse(DateTime.Now.ToString("yyyyMMddHHmmssffff")); 23 24 //グラフの値は一定で試してみる 25 int number = 1000; 26 27 //グラフの値(number)を投入 28 line.Points.Add(new DataPoint(cnt, number)); 29 30 if (line.Points.Count > 500) 31 { 32 //500個データが溜まったら、軸移動 33 MyModel.Axes.Clear(); 34 MyModel.Axes.Add(new LinearAxis { Position = AxisPosition.Bottom, Minimum = cnt - 500, Maximum = (cnt - 500) + 500 }); //x軸の設定 35 36 } 37 else 38 { 39 //500個溜まるまでは軸はスライドさせない 40 MyModel.Axes.Clear(); 41 MyModel.Axes.Add(new LinearAxis { Position = AxisPosition.Bottom, Minimum = 0.0, Maximum = cnt }); //x軸の設定 42 43 } 44 45 if (line.Points.Count > 500) 46 { 47 //軸移動によってはみ出した値を消す 48 line.Points.RemoveAt(0); 49 } 50 51 //横軸の値を増やす 52 cnt += 1; 53 54 //終了時間を計測 55 var end_time = long.Parse(DateTime.Now.ToString("yyyyMMddHHmmssffff")); 56 57 //ここでどんどん計算時間が増えていく 58 Debug.WriteLine("Difference_time: " + (end_time - start_time)); 59 60 //ここで移動した軸やグラフの値を更新。 61 //しかし上記のDifference_timeが増えるにつれてグラフのスライドがカクカクして度々0.8秒ほど止まってしまう 62 MyModel.InvalidatePlot(true); 63 64 } 65}

LinearAxisも毎回newしてるのでリファクタリングできるとより処理を軽量化できそうですね。


あと全然関係ないですが、時間計測は以下でもいけます。文字列型にしてパースさせるとそれだけで重いので避けたいなと。

csharp

1var startTime = DateTime.Now; 2/* 何かの処理 */ 3TimeSpan differenceTime = DateTime.Now - startTime; 4Debug.WriteLine("Difference_time: " + differenceTime);

追記

テストとして作成したOxyPlotのコードです。

https://drive.google.com/open?id=1VmIRN1wteoetsLpOy-ZiTNbZidy0tyTI

DispatcherTimerを使って1秒毎に更新、更新毎に100個のデータを追加する形で試しています。
InvalidatePlot()はModelの内容を走査し表示に反映する仕組みのようなので、Axisなどはnewせずとも、同じインスタンスを使いまわして問題ないようです。少しでもGCを減らしていけば動作軽量化に繋がるはずということで使いまわしていけるといいじゃないでしょうか。

投稿2019/08/29 23:59

編集2019/08/31 05:57
tor4kichi

総合スコア763

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

YAmaGNZ

2019/08/30 00:40

細かいようですが、 TimeSpan differenceTime = EndTime.Now - startTime; ではなく TimeSpan differenceTime = DateTime.Now - startTime; ですね
tor4kichi

2019/08/30 03:07

仰る通りです。回答本文に反映しました。
fuha_drms

2019/08/31 02:41

ご指摘ありがとうございます。 アプリ起動の時のみグラフの初期化をしてみましたが、20秒ほど回し続けていると、だんだんとグラフが止まる頻度が高くなってきました。 時間を継続する部分を除いて計算コストを省いても時間と共に遅れが生じてしまいます。 どうすれば改善できるのでしょうか?
tor4kichi

2019/08/31 05:46

追加で質問されている時点でベストアンサーではないと思いますが、それはそれとして。 OxyPlot(2.0.0-unstable1035)を使って試してみましたが、画像処理にどうしても動的メモリを消費するためGCによるメモリ回収のコストが大きいように感じました。ですのでInvalidatePlotを実行するタイミングを十分に絞ることで対処できそうです。(更新を1秒間隔で試してみると消費メモリ量は50MB前後をウロウロする形で落ち着いてました) whileで最速で回して高頻度な更新をしなければいけない理由があればその理由に合わせて対処が必要かもしれませんが、実用上1秒ごとの更新で足りるんじゃないかなと思います。 テストした内容は一応回答のほうにくっつけてみます。(zip貼れたかわからないので、別の形になるやも)
tor4kichi

2019/08/31 06:41

さらに試してみました。 0.016秒(だいたい60 fps)更新に設定するとグイっと更新した後に少し止まって、またぐいっと更新、といった挙動しました。この場合CPU使用率は30%近くを示していました。CPUを使いすぎないように何かによってスロットリングしてるようにも見えますが、OxyPlotとUWP/xamlのどちらか、あるいは他の何かがそうさせているのか、仔細はわかりませんでした。 一方で、0.16秒(6 fps)更新にすると特に詰まりはなく一定間隔を保った表示更新がされているのを確認しました。 OxyPlot.Windowsのソースを見てみると、PlotViewのUpdateVisualsメソッド内でCanvas.Children.Clear()を使って表示要素を一旦全部削除し、続けてCanvas.Childrenに対して線やTextなどの描画要素をPlotModelに基づいて生成し加えているようで、内部的な描画要素の使い回しがされていないようです。このため必然的にメモリ使用量が増える実装になっているように見えます。 更新間隔をコントロールして感覚的にスムーズに動いていると見えるラインを探っていくか、極めて表示パフォーマンスが重要であるなら、他のライブラリや自前での実装を検討する必要があるかもしれないと思います。
fuha_drms

2019/09/02 02:00

細かく調べて頂いてありがとうございます。自分の方でも同じような挙動を確認しました。他のライブラリでも試してみます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問