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

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

ただいまの
回答率

90.35%

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

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 2,456

myora

score 5

 前提・実現したいこと

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

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

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

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

 該当のソースコード

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

<Page
    x:Class="ChartTest.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:ChartTest"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:Charting="using:WinRTXamlToolkit.Controls.DataVisualization.Charting"
    mc:Ignorable="d">

    <Grid x:Name="Grid" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

        <Charting:Chart Name="LineChart01" Margin="33,154,49,34">
        </Charting:Chart>
        <Button Content="Button" HorizontalAlignment="Left" Height="114" Margin="417,21,0,0" VerticalAlignment="Top" Width="496" Click="Button_Click"/>

    </Grid>
</Page>
using WinRTXamlToolkit.Controls.DataVisualization.Charting;


namespace ChartTest
{
    public class Item
    {
        public double Key { get; set; }
        public double Value { get; set; }
    }

    public sealed partial class MainPage : Page
    {

        List<Item> items;

        public MainPage()
        {
            this.InitializeComponent();

            // 描画する500点を決定し、itemsに代入
            items = new List<Item>();
            Random rnd = new System.Random();
            for (int i=0; i<500; i++)
            {
                items.Add(new Item { Key=rnd.Next(0, 100), Value=rnd.Next(0, 100) });
            }
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            // SeriesのCollectionを初期化
            this.LineChart01.Series.Clear();

            // Collection 生成
            LineSeries lineSeries = new LineSeries()
            {
                ItemsSource = items,
                IndependentValuePath = "Key",
                DependentValuePath = "Value",
                Title = "ChartTest"
            };

            // SeriesにAdd
            this.LineChart01.Series.Add(lineSeries);

        }
    }
}

![イメージ説明

 試したこと

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

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

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

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

 追記

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

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

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

ソースコード

<Page
    x:Class="ChartTest.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:ChartTest"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:Charting="using:WinRTXamlToolkit.Controls.DataVisualization.Charting"
    mc:Ignorable="d">

    <Grid x:Name="Grid" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

        <Charting:Chart Name="LineChart01" Margin="33,154,49,34">
        </Charting:Chart>
        <Button Content="グラフ生成" HorizontalAlignment="Left" Height="114" Margin="108,21,0,0" VerticalAlignment="Top" Width="496" Click="Button_Create_Click"/>
        <Button Content="グラフクリア" HorizontalAlignment="Left" Height="114" Margin="749,21,0,0" VerticalAlignment="Top" Width="496" Click="Button_Clear_Click"/>

    </Grid>
</Page>
using WinRTXamlToolkit.Controls.DataVisualization.Charting;


namespace ChartTest
{
     public class Item : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        private void RaisePropertyChanged([CallerMemberName]string propertyName = null)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        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 sealed partial class MainPage : Page
    {

        List<ObservableCollection<Item>> itemsList;

        public MainPage()
        {
            this.InitializeComponent();

            // 描画するグラフのList生成
            itemsList = new List<ObservableCollection<Item>>();
            Random rnd = new System.Random();
       // 30個のグラフを表示
            for (int j = 0; j < 30; j++)
            {
                // それぞれのグラフに10点の値を入れる。
                ObservableCollection<Item> items = new ObservableCollection<Item>();
                for (int i = 0; i < 10; i++)
                {
                    items.Add(new Item { Key = i, Value = rnd.Next(0, 100) });
                }
                itemsList.Add(items);
            }
        }

        // ボタンクリックでグラフの生成
        private void Button_Create_Click(object sender, RoutedEventArgs e)
        {
            int i = 0;
            // グラフの数だけ描画する
            foreach (var items in itemsList)
            {

                // LineSeries 生成
                LineSeries lineSeries = new LineSeries()
                {
                    ItemsSource = items,
                    IndependentValuePath = "Key",
                    DependentValuePath = "Value",
                    Title = i
                };

                // SeriesにAdd
                this.LineChart01.Series.Add(lineSeries);

                i++;
            }
        }

        // ボタンクリックでグラフのクリア
        private void Button_Clear_Click(object sender, RoutedEventArgs e)
        {
            // SeriesのCollectionを初期化
            this.LineChart01.Series.Clear();
        }
    }
}

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

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

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

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

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

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • ebiryo

    2018/03/29 17:59

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

    キャンセル

  • myora

    2018/03/30 00:36

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

    キャンセル

回答 2

checkベストアンサー

+3

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

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

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

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

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/03/30 10:49

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

    キャンセル

  • 2018/03/31 14:03

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

    キャンセル

  • 2018/03/31 14:04

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

    キャンセル

+3

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

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

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

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

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/03/30 01:28

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

    キャンセル

  • 2018/03/30 02:02

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

    キャンセル

  • 2018/03/30 09:26 編集

    Clear() の後に GC.Collect(); Debug.WriteLine(GC.GetTotalMemory(true)); とするとガベージコレクション後の割当済みメモリの大きさが出力ウィンドウに出力されます。

    これを繰り返して何度やっても数字が大きくなることが確認できたらその記録とソースを併せて作者に報告してください。

    これ以上は作者の方で対応する以外無いように思えます。

    キャンセル

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

  • ただいまの回答率 90.35%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る