[C# WPF] なんとかしてWPFの描画を速くしたい「Canvas.Childrenへのオブジェクト追加/削除のコスト」

2019年6月25日

最近WPFのパフォーマンスチューニングに勤しんでいます。

300,000個ほどのオブジェクトを描画するデスクトップアプリを作っている中で、役に立ったり効果のあった話をまとめていきます。

基本的には速度低下を招くよろしくない実装の確認や、対策の紹介などしていきます。

今回は「CanvasへのAdd/Removeめっちゃ遅い」問題について検証します。

先に言っておきますが、対策は「できるだけCanvasに描画オブジェクトを出し入れしない」しか思いつきませんでした。

スポンサーリンク

環境

  • Visual Studio 2019
  • .NET Framework 4.7.2

確認すること

10,000個のオブジェクトをCanvasへAdd/Remove/Clearした場合にかかる時間

検証コード(どのくらい遅いのか)

10,000個のBorderをCanvasに対してAdd/Remove/Clearしてみます。
Releaseビルドで計測しています。

MainWindow.xaml

<Window x:Class="Sample_Preformence_Canvas.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Sample_Preformence_Canvas"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">

    <Grid>
        <Canvas x:Name="xCanvas"/>
    </Grid>

</Window>

MainWindow.xaml.cs

using System.Collections.Generic;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;

namespace Sample_Preformence_Canvas
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            var count = 10000;
            xCount.Content = "Objects: " + count.ToString();

            // オブジェクトの生成が計測に入らないように事前に生成しておきます
            var borders = new List<Border>();
            for (int i = 0; i < count; ++i)
            {
                borders.Add(new Border());
            }

            var sw = new Stopwatch();

            // Addの時間計測
            {
                sw.Start();

                for (int i = 0; i < count; ++i)
                {
                    xCanvas.Children.Add(borders[i]);
                }

                sw.Stop();
                xAdd.Content = "Add: " + sw.ElapsedMilliseconds.ToString() + " ms";
            }

            // Removeの時間計測
            {
                sw.Start();

                for (int i = 0; i < count; ++i)
                {
                    xCanvas.Children.Remove(borders[i]);
                }

                sw.Stop();
                xRemove.Content = "Remove: " + sw.ElapsedMilliseconds.ToString() + " ms";
            }

            // Clear用にCanvasへ再追加
            for (int i = 0; i < count; ++i)
            {
                xCanvas.Children.Add(borders[i]);
            }

            // Clearの時間計測
            {
                sw.Start();

                xCanvas.Children.Clear();

                sw.Stop();
                xClear.Content = "Clear: " + sw.ElapsedMilliseconds.ToString() + " ms";
            }
        }
    }
}

結果(どのくらい遅いのか)

Addはまぁぎりぎり許容範囲だとして、全部消すのに1000msもかかってます。

10,000個でこの速度なので、描画データの更新時に「全部消して入れ直せばいいや」というのがよろしくないことがわかります。

扱うオブジェクトが多い場合には、できるだけ動作中にCanvasへのオブジェクトの出し入れはしないように設計しましょう。

おしまい。

スポンサーリンク