Tag Archives: performance

WP7 performance tip: translate transforms

In my previous blog post, I described a control called the PivotIndicator.

The PivotIndicator is made of a small rectangle that is animated on an X axis: its position is updated every time the selected item of the Pivot changes. Here is the code which setup this animation:

var border = new Border { /* init properties... */ };
var animation = new DoubleAnimation
{
    Duration = new Duration(TimeSpan.FromSeconds(0.5)),
};

Storyboard.SetTarget(this.animation, this.border);
Storyboard.SetTargetProperty(this.animation, new PropertyPath(Canvas.LeftProperty));

The problem I noticed was that this animation wasn’t fluid. In particular, when the PivotItem contains a lot of items…

As you probably already know, the WP7 platform introduces a Compositor thread. This thread does not exist in the “desktop” version of Silverlight. The idea is that this thread can handle simple animations independently from the UI thread (which can be quite busy…) and leverage the GPU.

Now, in my code, I’m using a DoubleAnimation to animate a Canvas.Left property. What is wrong with that ? Actually, the problem is the property I’m animating, not the animation itself. When I setup an animation using Canvas.Left, there is absolutely no way the Compositor thread can handle it. Why ?

Because when the property changes, it triggers a layout update in the Canvas. This process is totally “CPU-bound” and executes on the UI thread. The dependency property changes, it calls InvalidateLayout which update the position of the item (by calling .Arrange() on each of its child). In this case, the GPU & the compositor thread cannot be used.

Now, the trick is to do the exact same animation with a different code:

var border = new Border { /* init properties... */ };
var translateTransform = new TranslateTransform();
border.RenderTransform = translateTransform;
var animation = new DoubleAnimation
{
    Duration = new Duration(TimeSpan.FromSeconds(0.5)),
};

Storyboard.SetTarget(this.animation, translateTransform);
Storyboard.SetTargetProperty(this.animation, new PropertyPath(TranslateTransform.XProperty));

Notice that all I change is using an TranslateTransform: the target of the animation is not the border but the translate transform it self. A TranslateTransform is an operation which can be handled by the GPU. Now, his animation is handled by the Compositor Thread and will be fluid even though the UI thread gets very busy !

Hope it helps 🙂

 

 

Windows Phone performance analysis & optimization during TechDays

In about 2 weeks now, I’ll have the chance to be part of the French TechDays in Paris as a speaker. This year, I’ll own a session called “Windows Phone performance analysis & optimisation” with my colleague Charlotte.

The agenda looks like the following:

  • why performance analysis ?
  • device vs emulator
  • leveraging WP7 threads
  • using the VS profiler for WP7
  • tips and tricks

During the session we will use a “real” app we’re working on for a few months now (I’ll share more details after the session). We have some cool tips that haven’t been shared anywhere before, so if performance is a topic of interest for you, stat tuned !

Click on the following image for a link to the TechDays website:

I’m planning to share the most of the content of this session on my blog soon after the event.

Don’t hesitate to stop by and say hi…

Track memory usage of your Windows Phone 7.1 app in real time

Update January 17th: I just found out that Peter Torr released more than a year ago a similar helper class which is very nice. You can check out his solution here.

Memory usage is an important aspect, especially on mobile device. If you want to publish an app on the Windows Phone marketplace you must satisfy the Technical Certification Requirements: “5.2.5 Memory Consumption: An application must not exceed 90 MB of RAM usage, except on devices that have more than 256 MB of memory.”

In this post, I’m sharing a technique to track the memory usage of a WP7 app in real tile in every single page of the app. By adding only one line of code in your existing app, you’ll be able to display memory usage in all your pages (without any changes):

Download source code and example

How to integrate the component in your existing app?

  1. import the MemoryWatcher class in your existing project
  2. in the InitializePhoneApplication method, add a new line after the creation of the RootFrame:
// Create the frame but don't set it as RootVisual yet; this allows the splash
// screen to remain active until the application is ready to render.
RootFrame = new PhoneApplicationFrame();
// Add the following line !
new MemoryWatcher(RootFrame) { IsDisplayed = true };

How it works?

During its initialization, the MemoryWatcher control will set an event handler to have a callback whenever the user navigates to a new page. When the new page is loaded, it checks if it can dynamically insert the MemoryWatcher control. This is done by checking the root UI element of the page and inserting the watcher control in it. Here is the full code of the MemoryWatcher class:

using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Navigation;
using System.Windows.Threading;

using Microsoft.Phone.Controls;
using Microsoft.Phone.Info;

namespace MemoryWatcherDemo
{
    public class MemoryWatcher : ContentControl
    {
        private readonly DispatcherTimer timer;
        private readonly PhoneApplicationFrame frame;
        private const float ByteToMega = 1024 * 1024;

        public bool IsDisplayed { get; set; }

        public MemoryWatcher(PhoneApplicationFrame frame)
        {
            if (frame == null)
                throw new ArgumentNullException("frame");

            this.frame = frame;
            this.frame.Navigated += new NavigatedEventHandler(this.OnFrameNavigated);
            this.frame.Navigating += new NavigatingCancelEventHandler(this.OnFrameNavigating);

            this.timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(100) };
            this.timer.Tick += new EventHandler(this.OnTimerTick);

            // setup some basic properties to ensure the content will be visible
            this.Foreground = new SolidColorBrush(Colors.Red);
            this.VerticalContentAlignment = VerticalAlignment.Center;
            this.HorizontalContentAlignment = HorizontalAlignment.Center;
            this.Margin = new Thickness(0, -35, 0, 0);
        }

        private void OnFrameNavigated(object sender, NavigationEventArgs e)
        {
            if (!this.IsDisplayed)
                return;

            var page = this.frame.Content as PhoneApplicationPage;
            if (page != null)
            {
                Panel host = page.Content as Panel;
                if (host != null && !host.Children.Any(c => c is MemoryWatcher))
                {
                    this.timer.Start();
                    host.Children.Add(this);
                }
            }
        }

        private void OnFrameNavigating(object sender, NavigatingCancelEventArgs navigatingCancelEventArgs)
        {
            var page = this.frame.Content as PhoneApplicationPage;
            if (page != null)
            {
                Panel host = page.Content as Panel;
                if (host != null && host.Children.Contains(this))
                {
                    this.timer.Stop();
                    host.Children.Remove(this);
                }
            }
        }

        private void OnTimerTick(object sender, EventArgs e)
        {
            try
            {
                string currentMemory = (DeviceStatus.ApplicationCurrentMemoryUsage / ByteToMega).ToString("#.00");
                string peakMemory = (DeviceStatus.ApplicationPeakMemoryUsage / ByteToMega).ToString("#.00");

                this.Content = string.Format("Current: {0}MB Peak: {1}MB", currentMemory, peakMemory);
            }
            catch (Exception)
            {
                this.timer.Stop();
            }
        }
    }
}

Note:

  • The MemoryWatcher is looking for a Panel type in order to add itself to the list of children in the page. You might want to modify and improve this portion in order to better fit your needs.
  • The attached project targets Windows Phone 7.1, if you want to use the code in a 7.0 version, you should change the way the memory values are read (see this article for more details)

Enjoy the code and start tracking memory leaks 🙂

Download source code and example