DataBinding performance in WinRT and the Bindable attribute

By jay at November 26, 2012 20:51 Tags: , , , ,

tl;dr: The Bindable attribute can be placed on standard C# classes in Metro Apps to make them appear in the generated IXamlMetadataProvider class, to create static metadata. This technique allows for a 10% increase in data-binding performance over reflection based binding, but also adds a temporary cost in JITting, until Windows generates native images 24 hours later.

Databinding in WPF/WinRT is very easy to use. Just put the name of the field you want to bind, set the DataContext, and voila, it is displayed on screen. Yet, this is a tricky feature under the hood. It relies on the presence of an arbitrary string that may exist in the current DataContext to get the data to be displayed.

In WPF and Silverlight, this is fairly easy to do because everything is in managed code. Resolving that data member was performed using a bit of type Reflection, where the string "{Binding SomeValue}" would result in a sequence of Type.GetProperty to get a PropertyInfo instance, then call GetValue to get the actual value.

But in WinRT, all this is a lot different, mainly because WinRT is purely native and there is no reflection or metadata there.

Reflection for C#/XAML apps using WinRT

How does that work then? Well, that’s where some magic happens during the compilation of a C#/XAML project, a subject that I covered a while back.

The compilation process parses the Xaml files to find nodes that reference actual C# or WinMD defined types, and generates a fairly big class that implements the IXamlMetadataProvider class. This class will provide detailed information about the types such as how to create them, their members, whether they are read-only or not, enum values, if there is a content property, etc…

This generated class is basically a static reflection engine for WinRT. At runtime, this class is provided to WinRT which in turn, calls it to determine what to do with the nodes it just parsed.


The magic with DataBinding POCO classes

There’s actually one interesting scenario, when using databinding POCO classes. Let’s say you write this:

<TextBlock x:Name=”test” Text={Binding MyValue}” />

Create the following class:

public class MyClass
   public string MyValue { get; set; }

And DataBind it using this code :

test.DataContext = new MyClass { MyValue = “Hello world !” };

Everything will work as expected.

But if you look at the generated IXamlMetadataProvider class, you’ll notice that there no reference to the MyClass type.

I stated previously that WinRT can’t do reflection, so how does that work? Simple. .NET Runtime magic !

Digging a bit deeper, by placing a breakpoint behind the C# auto property, we’ll find this stack trace:

--> MyApp.exe!MyApp.MyClass.MyValue.get()
    [Native to Managed Transition]
    mscorlib.dll!System.Runtime.InteropServices.WindowsRuntime.CustomPropertyImpl.InvokeInternal(object target, object[] args, bool getValue)
    mscorlib.dll!System.Runtime.InteropServices.WindowsRuntime.CustomPropertyImpl.GetValue(object target)
    [Native to Managed Transition]
    [Managed to Native Transition]

The runtime provides a way for WinRT to actually be able to perform reflection on arbitrary types that are not known at compile time by the IXamlMetadataProvider implementation, using the internal CustomPropertyImpl class.

This is completely transparent to the developer, and works very efficiently.


Adding a POCO to the IXamlMetadataProvider

Knowing that DataBinding also works in a completely native environment using C++/CX, tells some very interesting information.

To be able to databind a type in a C++/XAML app, it is required to place the Bindable attribute. This will force the C++ compiler to generate code that performs the same static reflection class found in C#/Xaml apps. A similar IXamlMetadataProvider implementing class will be generated with this metadata, allowing the Xaml parser to work properly, the same way it does with C#.

When looking at the documentation for Bindable, here what can be read:

“Specifies that a type defined in C++ can be used for binding.”

Which implies that it should only be used for C++ created types.

Yet, because this attribute is defined in WinRT, it can be used on C# classes, and surprise, classes marked with it also get added to the generated IXamlMetadataProvider !

This attribute can also be used to mark types as Bindable when provided in another assembly, but that are never referenced in Xaml.

I’m a big fan of code generation over reflection, particularly if what’s being reflected upon is known at compile time. I firmly believe that it is most of the time a way better use of CPU power to perform this code generation a compile time, over doing it countless times on the client at runtime.

This is why I’ll favor generating code, and this "Bindable" finding sound like music to my ears…



Profiling the Binding performance using Reflected and Bindable marked types

But let’s be clear. Microsoft did not think this technique would be interesting, because they would have otherwise told to add that attribute to classes. Still, let’s see how both binding infrastructure perform.

Before that, there are actually two things to consider.

First, the generated code for the IXamlMetadataProvider is not free. Even if it is mostly non-conditional, it still needs to be executed when the app starts to build the type definitions to provide to the WinRT Xaml engine. If this class grows very big, this may adversely impact the performance of the metadata lookup. A big switch/case on strings construct is translated into a standard IDictionary<string, int>, and reading this dictionary is not free.

Second, if there are lots of bindable types, even if the provider is composed of a single big switch/case and lots of small get/set methods, this means that this big method will need to be JITed. This can take a substantial amount of time at the startup of the app. This begs for NGEN though, as I’ve discussed in a previous article, to eliminate this overhead.


Testing the startup speed

Testing the startup speed can be done using code generation using T4. Considering an app that has around 30 databind-able types, all comprised of 15 fields, here’s the startup time up to the data observed on the screen of a Surface RT:

  • Reflection, 1.9s with JIT, 1.8s with NGEN
  • Bindable, 3.1s with JIT, 1.8s with NGEN


Testing the binding speed

Testing the binding speed be done using two simple types (one with the Bindable attribute, the other without), with one field, both databound to a TextBlock Text property. The loop is about setting 5000 times the DataContext to a new instance of the type:

  • Reflection: 2.00s with a string, 2.45s with an int
  • Bindable: 1.78s with a string, 2.20s with an int


This makes for improvement of about 10%, which is interesting, particularly considering the fact that ARM tablets are not that powerful.

Unsurprisingly, since everything is passed out as an object both in the IXamlMetadataProvider and CustomPropertyImpl classes, binding to value types is a bit more expensive because of boxing operations.

I'll provide the code for these two basic tests, if someone's interested.


Is this good for you ?

It may or may not be useful to you, depending on how much the non-NGENed startup is important for you, but also how much you rely on DataBinding.

I for one think that an added second to the startup to gain a rough 10% in binding speed seems a fairly reasonable deal, particularly when NGEN passes by afer a while to suppress this overhead.

Remember that an app only has 16.6ms to perform operations on the dispatcher between screen refreshes to maintain 60 Frames per second and keep a smooth user experience.

On an ARM tablet, added to other performance tweaks, this can overall make a difference.

blog comments powered by Disqus

About me

My name is Jerome Laban, I am a Software Architect, C# MVP and .NET enthustiast from Montréal, QC. You will find my blog on this site, where I'm adding my thoughts on current events, or the things I'm working on, such as the Remote Control for Windows Phone.