[C# WPF] なんとかしてWPFの描画を速くしたい「Structのnew」

2019年6月25日

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

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

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

今回は「Structを毎回newするコスト」について検証します。

C#のStructは早いからそんなことしなくてもいいという話があったので、どうなるか試しました。

結論としては、今回の検証では差がほとんどでませんでした。

スポンサーリンク

環境

  • Visual Studio 2019
  • .NET Framework 4.7.2

確認すること

10,000個の四角形をDrawRectangle()で描画する際に、引数に渡すRectを「毎回newする場合」と「メンバ変数を使い回す場合」とで処理にかかる時間の違い

検証コード(毎回newする場合)

まずは「毎回newする場合」の確認です。

MainWindow.xaml

<Window x:Class="Sample_Performance_Struct.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_Performance_Struct"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800"
        Loaded="Window_Loaded"
        >

    <Canvas>
        <local:View x:Name="xView"/>
        <Label x:Name="xLabel" Background="White"/>
    </Canvas>

</Window>

MainWindow.xaml.cs

using System;
using System.Collections.Generic;
using System.Windows;

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

            xView.ResultLabel = xLabel;
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            // 適当なシードで乱数を生成する(四角形を適当な位置へ置くのに使用)
            var randH = new Random(17280489);
            var randV = new Random(399594);

            var points = new List<Point>();
            for (var i = 0; i < 10000; ++i)
            {
                points.Add(new Point(randH.Next(800), randV.Next(450)));
            }

            xView.Points = points;
            xView.InvalidateVisual();
        }
    }
}

View.cs

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

namespace Sample_Performance_Struct
{
    class View : Control
    {
        public List<Point> Points;
        public Label ResultLabel;

        protected override void OnRender(DrawingContext drawingContext)
        {
            base.OnRender(drawingContext);

            if (Points == null || ResultLabel == null)
            {
                return;
            }

            var sw = new System.Diagnostics.Stopwatch();
            sw.Start();

            foreach (Point point in Points)
            {
                var rect = new Rect(point.X, point.Y, 5, 5);
                drawingContext.DrawRectangle(Brushes.CadetBlue, new Pen(Brushes.CadetBlue, 1), rect);
            }

            sw.Stop();
            ResultLabel.Content = "Result: " + sw.ElapsedMilliseconds.ToString() + " ms";
        }
    }
}

結果(毎回newする場合)

検証コード(Rectをメンバ変数にして使い回す場合)

次にこちら。変えるのはView.csだけです。

View.cs

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

namespace Sample_Performance_Struct
{
    class View : Control
    {
        public List<Point> Points;
        public Label ResultLabel;

        private Rect rect;

        protected override void OnRender(DrawingContext drawingContext)
        {
            base.OnRender(drawingContext);

            if (Points == null || ResultLabel == null)
            {
                return;
            }

            var sw = new System.Diagnostics.Stopwatch();
            sw.Start();

            foreach (Point point in Points)
            {
                rect.X = point.X;
                rect.Y = point.Y;
                rect.Width = 5;
                rect.Height = 5;
                drawingContext.DrawRectangle(Brushes.CadetBlue, new Pen(Brushes.CadetBlue, 1), rect);
            }

            sw.Stop();
            ResultLabel.Content = "Result: " + sw.ElapsedMilliseconds.ToString() + " ms";
        }
    }
}

結果(Rectをメンバ変数にして使い回す場合)

10,000だとほとんど変わりませんでした。

複数回試行して、どちらの場合も12ms〜15msくらい。

1,000,000に増やしてみる

7msほど差が出てますが、何回か試行するとnewする方が遅かったり、メンバ変数にする方が遅かったりとばらつきがありました。

まとめ

Rectを使う場合はあんまり速度差はなさそう。

自前の構造体を使う場合には、中身とか構造体のサイズによって結果が変わってきそうなのでまた試します。

おしまい。

スポンサーリンク