[C# WPF] なんとかしてWPFの描画を速くしたい「DrawRectangleでのPenの有無」

2019年6月26日

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

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

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

今回は「Penを使ってはいけない」について検証します。

スポンサーリンク

環境

  • Visual Studio 2019 Community
  • .NET Framework 4.7.2

確認すること

10,000個の四角形をDrawRectangle()で描画する際に「Penを指定する場合」と「Penにnullを設定する場合」での処理にかかる時間の違い

検証コード(Penを指定する場合)

MainWindow.xaml

<Window x:Class="Sample_Performance_Pen.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_Pen"
        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_Pen
{
    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_Pen
{
    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";
        }
    }
}

検証コード(Penにnullを設定する場合)

DrawRectangle()へのPen設定をnullに変えただけです。

View.cs

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

namespace Sample_Performance_Pen
{
    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, null, rect);
            }

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

結果

Penあり

Penなし

9ms差が出ていますが分かり辛いので描画数を100,000個に増やしてみます。

Penあり

Penなし

167msほど差がでました。

まとめ

Penはやっぱり重いみたいですね。

MFCのときから重かった気がするので、Penはできるだけ使わないように実装を考えないといけないです。

BrushとPenが同じ色の場合には、Penにはnullを指定して描画するように調整しましょう。

おしまい。

スポンサーリンク

C#,WPFC#,WPF,最適化

Posted by peliphilo