8

Below is a (very naive) implementation of Conway's Game of Life in WPF. It's just a demo...

xaml:

<Window x:Class="wpf_conway_life_2013_05_19.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="500" Width="500">
    <Grid>
        <Canvas Name="canvas"
            Width="auto"
            Height="auto" 
            HorizontalAlignment="Stretch"  
            VerticalAlignment="Stretch">
        </Canvas>
    </Grid>
</Window>

code behind:

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows.Threading;

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

            var random = new Random();

            var data = new int[100, 100];

            var dataB = new int[100, 100];

            Func<int, int, int> at = (x, y) =>
                {
                    if (x < 0) x = 100 + x;
                    if (x >= 100) x = x % 100;
                    if (y < 0) y = 100 + y;
                    if (y >= 100) y = y % 100;

                    return data[x, y];
                };

            for (var x = 0; x < 100; x++)
                for (var y = 0; y < 100; y++)
                    data[x, y] = random.Next(2);

            var rectangles = new Rectangle[100, 100];

            for (var x = 0; x < 100; x++)
                for (var y = 0; y < 100; y++)
                {
                    rectangles[x, y] = new Rectangle();

                    canvas.Children.Add(rectangles[x, y]);
                }

            canvas.SizeChanged += (s, e) =>
                {
                    for (var x = 0; x < 100; x++)
                    {
                        for (var y = 0; y < 100; y++)
                        {
                            rectangles[x, y].Width = canvas.ActualWidth / 100;
                            rectangles[x, y].Height = canvas.ActualHeight / 100;

                            Canvas.SetLeft(rectangles[x, y], (canvas.ActualWidth / 100) * x);
                            Canvas.SetTop(rectangles[x, y], (canvas.ActualHeight / 100) * y);
                        }
                    }
                };

            Action macroStep = () =>
                {
                    dataB = new int[100, 100];

                    for (var x = 0; x < 100; x++)
                    {
                        for (var y = 0; y < 100; y++)
                        {
                            var neighbors = 0;

                            for (var i = -1; i <= 1; i++)
                                for (var j = -1; j <= 1; j++)
                                    if (i == 0 && j == 0)
                                        continue;
                                    else
                                        neighbors += at(x + i, y + j);

                            dataB[x, y] = data[x, y];

                            if (neighbors < 2) dataB[x, y] = 0;
                            if (neighbors == 3) dataB[x, y] = 1;
                            if (neighbors > 3) dataB[x, y] = 0;

                            rectangles[x, y].Fill = dataB[x, y] == 0 ? new SolidColorBrush(new Color()) : Brushes.Black;
                        }
                    }

                    data = dataB;
                };

            var timer = new DispatcherTimer();

            timer.Tick += (s, e) => macroStep();

            timer.Start();
        }
    }
}

Here's what it looks like:

enter image description here

If I replace new SolidColorBrush(new Color()) with Brushes.White the program runs much more slowly. Why?

I'm testing on Windows 7 64-bit using 2010 Express.

Dave Clemmer
  • 3,741
  • 12
  • 49
  • 72
dharmatech
  • 8,979
  • 8
  • 42
  • 88
  • 2
    To narrow down whether it's a rendering or a retrieval issue, if you keep a cached reference to the brush outside your loop and use that reference in place of `Brushes.White`, is the speed affected? System brushes are cached in a `Dictionary`, which has to be locked each time an item is retrieved. – Simon MᶜKenzie May 20 '13 at 06:08
  • 2
    Do you get the same behavior with `Brushes.Transparent`? – Rachel May 20 '13 at 19:21
  • 1
    @SimonMcKenzie Good suggestion. However, I tried it and it makes no difference. (By the way, congratulations on MapSnap; very cool WP7 app!) – dharmatech May 21 '13 at 00:12
  • Thanks! You'll have to give it a go ;) Although I have no idea, I wonder if WPF parallelises some rendering, which could potentially be compromised if all items share the same brush. If you create the new brush instance outside the loop, is the performance the same? – Simon MᶜKenzie May 21 '13 at 00:17
  • @Rachel `Brushes.Transparent` is also slow. Funny thing is, it doesn't start out as slow as `Brushes.White` but after about 5 iterations, it becomes much slower. – dharmatech May 21 '13 at 00:17
  • By the way, I assume this is just for fun, but for true performance you'd do much better using a `WriteableBitmap`... – Simon MᶜKenzie May 21 '13 at 21:55
  • @SimonMcKenzie I also would have guessed that storing a brush in a variable outside the loop would be the most efficient. But after testing various combinations of setting up the brushes, it seems the fastest is to create a new brush and color, i.e. `new SolidColorBrush(new Color() {...})`. It's very bizarre. – dharmatech May 22 '13 at 12:59
  • @Rachel Thanks for the suggestion. It's bizarre but allocating a new `SolidColorBrush` each time seems to be the fastest approach. Would still love to know why though. – dharmatech May 22 '13 at 13:08
  • Try creating new SolidColorBrush just once and Freeze it. – Stipo May 25 '13 at 21:10
  • @Stipo Thanks for the suggestion. I gave that a shot but `new SolidColorBrush(...)` is still faster. – dharmatech May 25 '13 at 21:37

2 Answers2

1

Because new Color() has alpha value of zero, which means WPF doesn't have to render it because it's fully transparent - on the other hand White color's alpha is 255, which means it is completely solid white color which have to be rendered.

Dave Clemmer
  • 3,741
  • 12
  • 49
  • 72
Jaska
  • 1,412
  • 1
  • 18
  • 39
  • Good suggestion Jaska. But if that were the case, then this would be just as slow: `new SolidColorBrush(new Color() { R = 255, G = 255, B = 255, A = 255 })`. However, it's not. – dharmatech May 21 '13 at 00:18
0

There is nothing special about using Brushes.White.

If you define your own local brush outside the macroStep event handler, and then freeze it, it will behave exactly identical to using Brushes.White. If you don't freeze it first, it will behave far, far worse.

The best performance is to create your brush once at the beginning of each call to macroStep, before the loop, and then freeze it. It is a significant slowdown if you create a new brush inside the innermost loop.

Also, if you increase the interval on the timer for the badly behaving code, it will actually fix the performance issue. My guess is that there's some kind of resource cleanup that would occur on a background thread after it finishes rendering each time, that's tied to the internals of the brush, but it's starved from being able to do its cleanup because you're turning right around and using the brush in the next iteration. To demonstrate this, I created a pool of brushes, and use a different brush each time:

SolidColorBrush[] brushes = new SolidColorBrush[2];
for (int i = 0; i < brushes.Length; i++)
{
    var brush = new SolidColorBrush(new Color());
    brush.Freeze();
    brushes[i] = brush;
}
int brushIx = 0;

Action macroStep = () =>
{
    dataB = new int[100, 100];
    var brush = brushes[brushIx++ % brushes.Length];
...
    rectangles[x, y].Fill = dataB[x, y] == 0
        ? brush
        : Brushes.Black;
    data = dataB;
};

If you set the number of brushes to 1, this will give the same behavior as using Brushes.White. But if you set it to 2 or more, you'll get the performance you expect.

Dave Clemmer
  • 3,741
  • 12
  • 49
  • 72
Bryce Wagner
  • 2,640
  • 1
  • 26
  • 43
  • Thanks for the suggestion Bryce. Did you test this code on your system, comparing the different variations? On my system, creating a new brush in the inner loop is still faster than creating and freezing a brush, either outside or inside `macroStep`. It's very bizarre. – dharmatech Jun 04 '13 at 18:04
  • 1
    @dharmatech, I've just tested all three cases - your original with "new SolidColorBrush(new Color())", the suggested by Bryce - with arrays of freezed Brushes and again you original with just Brushes.White. And on my machine the fastest was the second one - with arrays of Brushes (no surprise here). The slowest was the last one - with Brushes.White. And case with original "new SolidColorBrush(new Color())" was somewhere in the middle. It may be worth to add that difference between the winner and the second one though it was noticeable, but not as much as between the second and the third one. – Sevenate Jun 12 '13 at 13:36