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

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

新規登録して質問してみよう
ただいま回答率
85.48%
UWP

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

C#

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

Visual Studio

Microsoft Visual StudioはMicrosoftによる統合開発環境(IDE)です。多種多様なプログラミング言語に対応しています。

XAML

XAML(Extensible Application Markup Language)はWPF、Silverlight、Windows PhoneそしてWindows Store appsでユーザーインターフェースを定義するために使われるXML言語です。

WPF

Windows Presentation Foundation (WPF) は、魅力的な外観のユーザー エクスペリエンスを持つ Windows クライアント アプリケーションを作成するための次世代プレゼンテーション システムです

Q&A

解決済

2回答

7333閲覧

【UWP】折れ線グラフを描画する際に、メモリリークする

myora

総合スコア7

UWP

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

C#

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

Visual Studio

Microsoft Visual StudioはMicrosoftによる統合開発環境(IDE)です。多種多様なプログラミング言語に対応しています。

XAML

XAML(Extensible Application Markup Language)はWPF、Silverlight、Windows PhoneそしてWindows Store appsでユーザーインターフェースを定義するために使われるXML言語です。

WPF

Windows Presentation Foundation (WPF) は、魅力的な外観のユーザー エクスペリエンスを持つ Windows クライアント アプリケーションを作成するための次世代プレゼンテーション システムです

0グッド

0クリップ

投稿2018/03/29 03:52

編集2018/03/29 17:05

前提・実現したいこと

UWPにおいて、折れ線グラフを表示したいです。
グラフを表示するために、外部ライブラリの、「WinRT Xaml Toolkit Data Visualization Controls.UWP」をNugetからインストールして使用しています。

発生している問題・エラーメッセージ

メモリリークと思われる現象が発生しており、折れ線グラフを更新するたびに、使用メモリが増えていきます。また、描画までにかかる処理時間もだんだんと遅くなっていきます(15回グラフ更新すると、1,2秒遅くなりました)。
メモリリークしていることも謎ですが、描画時間が遅くなることのほうが疑問に感じています。

詳しくは、最下部の「追記」項目をお読みください。「該当のソースコード」項目は読み飛ばしていただいてもかまいません。

該当のソースコード

こちらがソースコードです。このアプリは、ボタンを押下すると、500点からなる折れ線グラフを描画・更新します。描画する500点は起動時に乱数によって決定します。

Xaml

1<Page 2 x:Class="ChartTest.MainPage" 3 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 4 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 5 xmlns:local="using:ChartTest" 6 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 7 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 8 xmlns:Charting="using:WinRTXamlToolkit.Controls.DataVisualization.Charting" 9 mc:Ignorable="d"> 10 11 <Grid x:Name="Grid" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> 12 13 <Charting:Chart Name="LineChart01" Margin="33,154,49,34"> 14 </Charting:Chart> 15 <Button Content="Button" HorizontalAlignment="Left" Height="114" Margin="417,21,0,0" VerticalAlignment="Top" Width="496" Click="Button_Click"/> 16 17 </Grid> 18</Page>

C#

1using WinRTXamlToolkit.Controls.DataVisualization.Charting; 2 3 4namespace ChartTest 5{ 6 public class Item 7 { 8 public double Key { get; set; } 9 public double Value { get; set; } 10 } 11 12 public sealed partial class MainPage : Page 13 { 14 15 List<Item> items; 16 17 public MainPage() 18 { 19 this.InitializeComponent(); 20 21 // 描画する500点を決定し、itemsに代入 22 items = new List<Item>(); 23 Random rnd = new System.Random(); 24 for (int i=0; i<500; i++) 25 { 26 items.Add(new Item { Key=rnd.Next(0, 100), Value=rnd.Next(0, 100) }); 27 } 28 } 29 30 private void Button_Click(object sender, RoutedEventArgs e) 31 { 32 // SeriesのCollectionを初期化 33 this.LineChart01.Series.Clear(); 34 35 // Collection 生成 36 LineSeries lineSeries = new LineSeries() 37 { 38 ItemsSource = items, 39 IndependentValuePath = "Key", 40 DependentValuePath = "Value", 41 Title = "ChartTest" 42 }; 43 44 // SeriesにAdd 45 this.LineChart01.Series.Add(lineSeries); 46 47 } 48 } 49} 50

![イメージ説明

試したこと

ボタンが押下された際に、this.LineChart01.Series.Clear();を行って、Collectionの中身をクリアしているにも関わらず、メモリが増えていきます。
そのため、this.Grid.Children.Remove()を使用してLineChart01自体を削除することも試みましたが、メモリが減ることはありませんでした。

補足情報(FW/ツールのバージョンなど)

開発環境は、Visual Studio 2017 Communityです。

Debug版での実行に基づき、メモリや処理速度を判断しています。

追記

メモリリークする、処理が遅くなる、についての詳細な部分について記載します。

まず、検証するソースコードですが、今後の実用性の観点から以下のようなアプリに変更しました。
アプリ概要: 「グラフ生成」と「グラフクリア」の二つのボタンが配置されている。「グラフ生成」ボタンを押下すると、10点分の折れ線グラフが30個描画される。次に、「グラフクリア」ボタンを押下すると、描画されているグラフを消す。

「グラフ生成」ボタンを押下後のイメージ
グラフの描画時

ソースコード

Xaml

1<Page 2 x:Class="ChartTest.MainPage" 3 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 4 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 5 xmlns:local="using:ChartTest" 6 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 7 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 8 xmlns:Charting="using:WinRTXamlToolkit.Controls.DataVisualization.Charting" 9 mc:Ignorable="d"> 10 11 <Grid x:Name="Grid" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> 12 13 <Charting:Chart Name="LineChart01" Margin="33,154,49,34"> 14 </Charting:Chart> 15 <Button Content="グラフ生成" HorizontalAlignment="Left" Height="114" Margin="108,21,0,0" VerticalAlignment="Top" Width="496" Click="Button_Create_Click"/> 16 <Button Content="グラフクリア" HorizontalAlignment="Left" Height="114" Margin="749,21,0,0" VerticalAlignment="Top" Width="496" Click="Button_Clear_Click"/> 17 18 </Grid> 19</Page>

C#

1using WinRTXamlToolkit.Controls.DataVisualization.Charting; 2 3 4namespace ChartTest 5{ 6 public class Item : INotifyPropertyChanged 7 { 8 public event PropertyChangedEventHandler PropertyChanged; 9 private void RaisePropertyChanged([CallerMemberName]string propertyName = null) 10 { 11 if (PropertyChanged != null) 12 PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); 13 } 14 15 private double x; 16 private double y; 17 18 public double Key { 19 get { return x; } 20 set { 21 if (value != x) 22 { 23 x = value; 24 RaisePropertyChanged(); 25 } 26 } 27 } 28 public double Value 29 { 30 get { return y; } 31 set 32 { 33 if (value != y) 34 { 35 y = value; 36 RaisePropertyChanged(); 37 } 38 } 39 } 40 41 } 42 43 public sealed partial class MainPage : Page 44 { 45 46 List<ObservableCollection<Item>> itemsList; 47 48 public MainPage() 49 { 50 this.InitializeComponent(); 51 52 // 描画するグラフのList生成 53 itemsList = new List<ObservableCollection<Item>>(); 54 Random rnd = new System.Random(); 55       // 30個のグラフを表示 56 for (int j = 0; j < 30; j++) 57 { 58 // それぞれのグラフに10点の値を入れる。 59 ObservableCollection<Item> items = new ObservableCollection<Item>(); 60 for (int i = 0; i < 10; i++) 61 { 62 items.Add(new Item { Key = i, Value = rnd.Next(0, 100) }); 63 } 64 itemsList.Add(items); 65 } 66 } 67 68 // ボタンクリックでグラフの生成 69 private void Button_Create_Click(object sender, RoutedEventArgs e) 70 { 71 int i = 0; 72 // グラフの数だけ描画する 73 foreach (var items in itemsList) 74 { 75 76 // LineSeries 生成 77 LineSeries lineSeries = new LineSeries() 78 { 79 ItemsSource = items, 80 IndependentValuePath = "Key", 81 DependentValuePath = "Value", 82 Title = i 83 }; 84 85 // SeriesにAdd 86 this.LineChart01.Series.Add(lineSeries); 87 88 i++; 89 } 90 } 91 92 // ボタンクリックでグラフのクリア 93 private void Button_Clear_Click(object sender, RoutedEventArgs e) 94 { 95 // SeriesのCollectionを初期化 96 this.LineChart01.Series.Clear(); 97 } 98 } 99}

次に、グラフ描画時間とメモリの測定です。
[1]. 本アプリをDebugモードで起動します。
[2]. 「グラフ生成」ボタン押下 → 「グラフクリア」ボタン押下 → 「グラフ生成」ボタン押下 → ・・・
と繰り返します。
[3]. この時の、各ボタンを押下してから処理が完了するまでの時間を、Debugモードの診断ツールのプロセスメモリグラフとCPUグラフの処理時間から読み取ります。

測定結果が以下です。(1枚目がシステム起動時、2枚目が上記の[2].を5分間繰り返したときの画像です)
赤丸の部分が、「グラフ生成」ボタンを押下してから、グラフが描画されるまでの時間で、
青丸の部分が、「グラフクリア」ボタンを押下してから、グラフが消えるまでの時間です。

システム起動時
システム起動時
5分経過時
5分経過時

上記のグラフから、以下のことが言えると考えました。
・メモリは起動時には急激に増加し、それ以降は、徐々に増加していく。
・処理時間は、グラフ描画時間はほとんど変わらないが、グラフクリアをする時間が5分経過時には、2倍近く遅くなっている。

とくに、処理時間が遅くなることが困っています。
アドバイス等ありましたらよろしくお願いします。

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

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

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

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

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

ebiryo

2018/03/29 08:59

メモリの使用量が増えるというのはどのように確認されたのでしょうか? GCによる回収が行われるまで増え続けるのは正常な動きだと思うのですが。
myora

2018/03/29 15:36

質問内容に追記しました。Dubugの診断ツールを見る限りでは、GCの開始は何度もされていると思います。
guest

回答2

0

ベストアンサー

以下に同様の報告があがっているようですね。

Windows UWP: Chart-Rendering slow/blocking UI #23

そもそも、ライブラリの作者さんからもデータ数が500とかは想定外だ、みたいなコメントが。。。

変わりといってはなんですが、OxyPlotとかはどうでしょうか?
OxyPlot
試しにデータ数1000とかでやってみましたがスムーズに動作しました。

WPFのときからよく使っていますが、サンプルや情報も豊富なので使用が許されるのならお勧めです(メモリリークしているかどうかは検証していませんが)

投稿2018/03/30 01:05

ebiryo

総合スコア797

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

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

Zuishin

2018/03/30 01:49

先にそちらを確認しておけばよかったですね。 作者の方は「WPF は高々 500~1000 まででしか良好なパフォーマンスを発揮しない」と言っていますが、直後に「200 以下でも同様でした」と反論を受け、「25 ならうまくいく」として DirectX を薦めていますね。
myora

2018/03/31 05:03

使ってるライブラリがバグっていたんですね! ありがとうございます!
myora

2018/03/31 05:04

解決出来て良かったです。OxyPlot使ってみます
guest

0

そのライブラリを使っていないので詳細はわかりませんが、読んでみてまずそうなところが二点あります。
一つは ItemINotifyPropertyChanged を実装していないこと。
もう一つが List<Item> を使っていることです。

itemsItemsSource に代入されていますが、このプロパティは名前からデータバインディングに使われるものと推測されます。
つまり INotifyPropertyChanged を実装していない List<Item> がバインドされ、またその要素である個々の Item がバインドされると思います。

WPF では INotifyPropertyChanged を実装していないクラスもバインド可能ですが、その際には個々のプロパティに対してプロパティディスクリプタの取得が行われ、バインドされたコントロールとデータがともにシステムから強く参照されます。
これによりメモリリークが発生します。
またプロパティディスクリプタによる値の取得・設定は低速なのでパフォーマンスも悪くなります。

解決法は、List<Item> の代わりに ObservableCollection<Item> を使うことと、ItemINotifyPropertyChanged を実装させることです。

最初に言ったように私はこのライブラリを使っていないので、問題がここだけかどうかはわかりません。
しかしここに「も」問題があることは間違いないので直してください。
運が良ければこれで解決しますし、たとえ解決しなくても元には戻さないでください。

投稿2018/03/29 05:55

Zuishin

総合スコア28660

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

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

myora

2018/03/29 14:51

回答ありがとうございます。 指摘されか箇所について修正したいのですが、 INotifyPropertyChanged の実装は以下のようにするだけで大丈夫でしょうか? public class Item : INotifyPropertyChanged { public double Key { get; set; } public double Value { get; set; } public event PropertyChangedEventHandler PropertyChanged; }
Zuishin

2018/03/29 15:00

大丈夫じゃありません。プロパティが変わった時にイベントを発生させる必要があります。
myora

2018/03/29 15:08

こういうことでしょうか? public class Item : INotifyPropertyChanged { private double x; private double y; public double Key { get { return x; } set { if (value != x) { x = value; RaisePropertyChanged(); } } } public double Value { get { return y; } set { if (value != y) { y = value; RaisePropertyChanged(); } } } public event PropertyChangedEventHandler PropertyChanged; private void RaisePropertyChanged([CallerMemberName]string propertyName = null) { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } }
Zuishin

2018/03/29 15:11

インデントが無いので読みにくいですが多分合ってると思います。
myora

2018/03/29 15:38

インデントそろってなくてごめんなさい。 丁寧にありがとうございます! しかし、残念ながら、根本的な問題の解決には至らなかったです。 質問内容に追記をしたので、お時間がありましたら、閲覧のほどよろしくお願いいたします。
Zuishin

2018/03/29 15:46

見ましたが、Item がここに書かれたものと違います。 それと ObservableCollection<Item> とすべきところが List<ObservableCollection<Item>> となっています。
myora

2018/03/29 15:54

Itemの部分のコピー間違ってました。修正しました。 ObservableCollection<Item> items; の部分ですが、今回は、itemsを30個表示したいため、それをさらにitemsListに格納するようにしています。 表示する際には、 foreach(var items in itemsList) {} により、ObservableCollection<Item>のitemsをバインドするようにしています。
Zuishin

2018/03/29 16:28

直す前に仕様変更するのは悪手です。 直すときには直すことに集中しましょう。 新しいバグが入ります。 直すべきところを直して試しても結果は変わりませんか?
myora

2018/03/29 17:02

指摘ありがとうございます。 以前の500点をプロットするプログラムにおいて、指摘された項目の修正を実施しましたが、 追記項目の測定結果ほど顕著ではありませんが、メモリが増加し続けることと、処理速度がだんだん遅くなることは解決しませんでした。
Zuishin

2018/03/30 00:27 編集

Clear() の後に GC.Collect(); Debug.WriteLine(GC.GetTotalMemory(true)); とするとガベージコレクション後の割当済みメモリの大きさが出力ウィンドウに出力されます。 これを繰り返して何度やっても数字が大きくなることが確認できたらその記録とソースを併せて作者に報告してください。 これ以上は作者の方で対応する以外無いように思えます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問