Dotneteers.net
All for .net, .net for all!

Windows Phone Performance – Part 2 of many – It’s Full of Stars!

VBandi's blog

Syndication

Subscribe

Generic Content

This is Part 2 of the translation of the performance chapter of the Windows Phone developer book I co-authored with many of my Hungarian peers. Other parts of this series can be found here.

We finished the previous part discussing the roles of the CPU and the GPU in the phone. Let’s see an example of the CPU’s task!

It’s Full of Stars!

Let’s create a simple application – an application we will use to examine the performance problems and ways to fix them. In the first step, this program displays a star field. Let’s create a new C# Windows Phone application, call it CpuAndGpu, and enter the following into MainPage.xaml.cs:

 

using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Media;
using System.Windows.Shapes;
using Microsoft.Phone.Controls;
 
namespace CpuAndGpu
{
    public partial class MainPage : PhoneApplicationPage
    {
        private Stopwatch stopwatch;
 
        // Constructor
        public MainPage()
        {
            stopwatch = new Stopwatch();
            stopwatch.Start();
            InitializeComponent();
            CreateStarField(100);   
 
            Loaded += OnLoaded;
        }
 
        private void OnLoaded(object sender, RoutedEventArgs routedEventArgs)
        {
            MessageBox.Show("Startup time: " + 
                stopwatch.ElapsedMilliseconds + " ms");
        }
 
        public void CreateStarField(int numOfStars)
        {
            Random x = new Random();
            for (int i = 0; i < numOfStars; i++)
            {
                Ellipse ellipse = new Ellipse();
                double size = x.NextDouble()*3;
                ellipse.Width = size;
                ellipse.Height = ellipse.Width;
                ellipse.Fill = new SolidColorBrush(Colors.White);
                ellipse.Margin = new Thickness(x.Next(456), x.Next(607), 0, 0);
                ellipse.HorizontalAlignment = HorizontalAlignment.Left;
                ellipse.VerticalAlignment = VerticalAlignment.Top;
 
                ContentPanel.Children.Add(ellipse);
            }
        }
    }
}

Let’s also remove the SplashScreen.jpg file from the project using the Solution Explorer.

The above program is very simple. In the constructor of the MainPage class, we start a stopwatch that measures the time spent since launch. In the Loaded event handler (which is invoked after the MainPage is displayed), we use a MessageBox to display the time it took for the app to launch. After starting the stopwatch, the InitializeComponent method is interpreting the page’s XAML, and then the CreateStarField method is called. This method draws randomly placed and sized stars – as many as specified in the numOfStars parameter.

If we launch the application in the emulator, we get the following result:

image

So, the application launches in 388 ms. But the app will ultimately have to run on the phone, so let’s start it there, too! On a first generation LG Optimus 7 device, the app starts in 558 ms. But what happens if we want to show more stars – thousands of them? The result of the measurements can be seen in this graph:

image

We could not even measure 20,000 stars on the phone, since it took more than 20 seconds to launch, and the OS has killed the app.

Experienced developers may spot that there is some room to make the drawing of the stars faster. We are setting the height and the width of the ellipse to the same random value (stars look like circles after all) – but we are doing this with the line

ellipse.Height = ellipse.Width; 

If we replace the above to

ellipse.Height = size;

and repeat the measurements of 5000 stars, it will be displayed in 4202 ms instead of the old 4300 on the phone. This is because we execute a Dependency Property get for every star. Maybe this 0,2 ms per star doesn’t seem like much – but in the core of a loop executed thousands of times, even small things like this matter.

The above experiment teaches us two important lessons:

  • The performance-characteristics of the emulator are totally different than that of the real phones. We shouldn’t make assumptions of the application’s performance based on its behavior in the emulator! Although the emulator is very precise, and is executing the same OS as the phone itself, it is still doing that on the processor and graphics card of a desktop computer. Always verify the application on a real device, and tune the performance on a phone, not in the emulator!
  • Common sense and the traditional .NET optimization techniques are still useful on the phone.

Dealing with Slow Application Launch

The above application obviously has a big problem – it is very slow to launch. Let’s see how it can be improved!

Decreasing Layout Costs

Looking more carefully at MainPage.xaml, we can see that the stars are placed in a Grid. The Grid is very flexible, adopts to the changes in available size – but all this flexibility comes at a price. Let’s replace the Grid with a much simpler Canvas:

<!--<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"></Grid>-->
<Canvas x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"></Canvas>

We will also have to modify the code so that it uses the Canvas.Top and Canvas.Left attached properties to place the stars instead of the margins:

 

public void CreateStarField(int numOfStars)
{
    Random x = new Random(numOfStars);
    for (int i = 0; i < numOfStars; i++)
    {
        Ellipse ellipse = new Ellipse();
        double size = x.NextDouble()*3;
        ellipse.Width = size;
        ellipse.Height = size;
        ellipse.Fill = new SolidColorBrush(Colors.White);
        ellipse.SetValue(Canvas.LeftProperty, (double)x.Next(456));
        ellipse.SetValue(Canvas.TopProperty, (double)x.Next(607));
//                ellipse.Margin = new Thickness(x.Next(456), x.Next(607), 0, 0);
//                ellipse.HorizontalAlignment = HorizontalAlignment.Left;
//                ellipse.VerticalAlignment = VerticalAlignment.Top;
 
        ContentPanel.Children.Add(ellipse);
    }
}

Running this version with 5000 stars, the app launches in 3093ms instead of the previously measured 4100ms!

In general, we can conclude that we should only use complicated layout systems when needed. Every resizing, change in the number of children results in a partial or full measurement cycle of the visual tree – and the less such work the CPU has to perform, the faster the application will be.

The Splash Screen

The Splash Screen is the full screen picture shown right after launching the application. The Splash Screen does not make launching the app faster – but if we can’t decrease the launch time below 1 or 2 seconds, the user will prefer watching a tasteful graphics than the empty screen. A bit of warning though – the SplashSceen.jpg image found in the default WP7 application template does not belong into the tasteful category!

The Marketplace regulations are also requiring the use of a Splash Screen if the application launches slower than 5 seconds. The Splash Screen is visible until the first screen is rendered.

Another advantage of the Splash Screen is that the user – in the absence of a stopwatch – will feel that the application loads faster, because something is happening on the screen. This feeling is called “subjective performance”. Expert use of such tricks can significantly enhance the user experience as we will discuss it later.

It is very easy to implement a Splash Screen in your app – just place an image called SplashScreen.jpg in the root directory of the Windows Phone project, and set its Build Action to Content.

Delaying the Initialization

In the sample app above, the launch of the application is significantly slower due to the time it takes to draw all the stars. What if we would only draw these stars after the app has loaded? Let’s move the call to the CreateStarField method from the constructor into the OnLoaded event handler:

private void OnLoaded(object sender, RoutedEventArgs routedEventArgs)
{
    CreateStarField(5000);   
    MessageBox.Show("Startup time: " + stopwatch.ElapsedMilliseconds + " ms");
}

If we launch this app on the phone, we can observe two important things. One of them is that when the MessageBox is displayed, the stars are not visible, and the number has decreased to 2876 ms from the original 3093 ms. The reason for this is that the actual drawing of the ellipses has not yet happened at this stage – it will only be performed after closing the MessageBox.

The other phenomenon is that the application launches fast, and the header text is displayed almost immediately. But this shouldn’t deceive us – the application will not react to user input while the stars are being added, since the UI thread is busy. We can easily verify this by adding a button to the MainPage.xaml.cs:

<Canvas x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
    <Button Canvas.ZIndex="1">I am not doing anything...</Button>
</Canvas>

Pressing the button will switch its colors to inverse: a black button with white text becomes a white button with black text. But rendering these changes is happening on the UI thread, which is busy with drawing the stars. The result is that the button will only react to the touch after all the drawing is done.

Background Threads to the Rescue

So, if the UI thread is so important for keeping interactivity alive, let’s move the drawing of the stars to a another thread! We have to modify the OnLoaded event handler and create a new method called BGCreateStars for this one:

 

private void OnLoaded(object sender, RoutedEventArgs routedEventArgs)
{
    Thread t = new Thread(BGCreateStars);
    t.Name = "Stars";
    t.Start();
 
//            CreateStarField(5000);   
//            MessageBox.Show("Startup time: " + stopwatch.ElapsedMilliseconds + " ms");
}
 
private void BGCreateStars()
{
    for (int i = 0; i < 500; i++)
    {
        UIDispatcher.BeginInvoke(() => CreateStarField(10));
        Thread.Sleep(30);
    }
}

The BGCreateStars executes the CreateStarField method 500 times – and each call will draw 10 stars. CreateStarField has to run on the UI thread, because it manipulates the UI. So, we launch it through the UIDispatcher.BeginInvoke method. The BGCreateStars is also careful not to overload the UI thread by resting 30 ms after each call. We have also removed the MessageBox, since it would be shown before the application stars, and wouldn’t be too useful this way.

Running this application, the user experience is much better: launch time is minimal, and the application immediately reacts to button presses. Stars are being drawn slowly, it looks like the app draws them one at a time.

Being Prepared is Always Good

This is all great and all – but what if we need the whole starfield, right after launch? There is a simple solution for this case, too: create a screenshot of the starfield, and set it as the background image of the ContentPanel. We have to place a file in the project (call it starsBG.png), and change its properties so that Build Action is Content, and Copy to Output Directory is Copy if newer. The MainPage.xaml.cs can be reset to the original (keeping only an InitializeComponent call in the constructor). Here is what the starsBG.png can look like:

image

And change the ContentPanel in MainPage.xaml.cs to this:

<Canvas x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
    <Canvas.Background>
        <ImageBrush Stretch="None" ImageSource="/starsBG.png"/>
    </Canvas.Background>
    <Button Canvas.ZIndex="1">I am not doing anything...</Button>
</Canvas>

And we’ve done it! The app starts within half a second, even with 5000 stars! If you still need a random starfield, simply prepare 10 different images, and set one of them as a background from code.

The Story So Far…

Naturally, the principles outlined above can be used in “real” apps as well. A good example for delayed initialization is our SurfCube 3D Browser app. Since the launch time of the application slowly grew beyond 5-6 seconds, we had more and more users complaining. Some of them even thought that the splash screen is only there since we loved the logo. He would have accepted this, but was outraged that the splash screen was still visible after buying the app!

To help ease this pain, starting with V4.0 of SurfCube, we are loading the sides of the cube with Visibility set to Collapsed. We only switch to Visible at the first rotation. This may be a little bit inconvenient, but we managed to reduce the application’s launch time to 3.5 seconds.

Another thing that may help a lot is moving rarely used modules into separate assemblies. This way, the runtime won’t load these assemblies until they are referenced.

An example for preparing data is my Hungarian nameday app, “Névnap”, which is currently the #1 free reference app in the Hungarian Marketplace. An SQL CE database is used to store and query the namedays, which is filled from a text file during the first run of the app. Further executions only use the SQL CE database, saving the time to parse the text file. An even better solution for this is to place the SQL database in the XAP during development, so that even the first launch can be fast.

Coming up: in the next post, I will discuss animations, the GPU and we will meet a ten-eyed drunken spaceship captain who is being buzzed by a TIE Fighter! Don’t miss it – and please keep the feedbacks coming!


Posted Feb 18 2012, 12:06 PM by vbandi

Comments

Windows Phone Performance ??? Part 2 of many ??? It???s Full of Stars! wrote Windows Phone Performance ??? Part 2 of many ??? It???s Full of Stars!
on Mon, Feb 20 2012 13:33

Pingback from  Windows Phone Performance ??? Part 2 of many ??? It???s Full of Stars!

Dew Drop – February 20, 2012 –- #1,269 | Alvin Ashcraft's Morning Dew wrote Dew Drop &ndash; February 20, 2012 &ndash;- #1,269 | Alvin Ashcraft&#039;s Morning Dew
on Mon, Feb 20 2012 15:28

Pingback from  Dew Drop – February 20, 2012 –- #1,269 | Alvin Ashcraft's Morning Dew

Raghuraman Kanchi wrote re: Windows Phone Performance – Part 2 of many – It’s Full of Stars!
on Wed, Mar 14 2012 19:44

Excellent and Useful Writeup.

I loved the Images Idea for Start Screen.

Glad to learn about the UIDispatcher.Invoke method too.

Thank you very much.

KRK