C++/CX Performance Pitfalls
Writing applications in C++/CX is not like writing normal C++ applications. The interoperability between pure C++ code and the Windows Runtime (WinRT) can be surprisingly expensive. In this article based on Sridhar Madhugiri’s video, C++/CX Best Practices, we look at some of the ways to avoid performance problems in Windows 8 development.
At the boundaries of your application there are various impediments to performance.
One example is data transformation. Consider the typical boundary between a web service client and the rest of your application. Most web services are encoded with UTF-8, while most Windows applications use UTF-16 internally. (The UTF-16 encoding is so prevalent in Windows that it is sometimes mistakenly referred to as the “Unicode” encoding.) The cost of data transformation can be fixed or it may vary widely depending on the specific values in the data itself.
The next cost comes from type conversion. For example, you may need a wstring but have a wchar_t *. Though the data contained in each looks the same in memory, you may still have to pay the price to copy it from the one data structure to the other.
The final cost comes from data copy operations. Sometimes you have to pay for copying data at the boundary even when no data transformations or type conversions are needed.
Why talk about these now? Because WinRT itself is a boundary between your application and the rest of the operating system. Recognizing it as such, and avoiding crossing it where possible, is essential for writing high performance C++/CX applications.
If you can’t avoid crossing the WinRT boundary, look for ways to reduce the amount of data copying, type conversion, and data transformation needed. For example, if your data source and destination both require UTF-8, try to avoid converting it into UTF-16 as you just have to convert it back again.
In most applications strings are the predominate data type. Files systems, web services, UI, messaging, charms and contracts, the list of places that heavily rely on strings goes on and on. Unfortunately the type of string you use matters quite a bit.
Internally, most applications are going to be using std::wstring or perhaps std::wchar_t*, as will most of the third-party libraries that you rely on. But when talking to the WinRT libraries you’ll need to switch to Platform::String^. Each of these conversions is going to require a memory allocation paired with a copy operation.
A key difference between String^ and the native C++ version is that String^ is immutable. This emphasis on immutable strings in the WinRT runtime probably comes from .NET and the CLR. As indicated by the ^ token, String^ is also reference counted.
One could argue all day about the relative merits of mutable and immutable strings, but at the end of the day one truth remains. Since the C++ standard library only understands mutable strings, and WinRT only understands immutable strings, you are going to have to deal with both. And as said before, that means making a copy of the string.
Library Authors: If you are building a general purpose library for others to use, you should consider offering multiple versions of your API, one for each type of string. That would eliminate the need to guess which type of string the consumer is going to happen to have at the moment they need to call your library.
A lot of string-based operations don’t actually need strings, but rather they need iterators over the string. Since iteration is the same over mutable and immutable data structures, you can create STL style iterators directly on platform strings using the normal xxx_iterator( begin(string), end(string), …) syntax.
Going the other direction, look for APIs that return a wchar_t* directly instead of first wrapping it in a wstring. When you find one of those, you can create a new platform string with just the address of the first element in the array and its length. This eliminates creating a wstring that would be discarded immediately after the matching platform string is created.
When calling WinRT APIs with input parameters of type StringReference there is a trick you can do. You can pass a wchar_t* or wstring as an argument to a WinRT function expecting a platform string, in which case it will create a lightweight facade. However, there are some caveats,
- The string must be null terminated or an error will be thrown.
- If anything outside the function alters the string the results are indeterminate.
- If anything inside the function takes a reference to the string, a full copy will be made anyways.
Since #1 is easy to verify and #2 should only occur when you have thread safety issues, this should be a useful technique under most circumstances.
Library Authors: To ensure that the above scenario is actually possible, try to avoid taking that first reference to strings you get as StringReference parameters. Since subsequent references don’t introduce additional copying, don’t worry about taking a second reference.
Compared to normal collections in C++, WinRT collections are expensive. Like observable collections in .NET, every alteration to a WinRT collection raises a notification. This notification is primarily used by XAML data binding to update the UI.
One way to avoid this cost during initialization is to first create and populate a standard vector on the stack, then use the move function to initialize a platform vector. You are allowed to do this because the standard vector would have been destroyed and its dynamic memory freed anyways.
When updating a lot of elements, consider using the ReplaceAll method. This will fire a single notification instead of one per item. (A comparable method is not available in WPF or Silverlight, as those UI stacks don’t support inserting or removing multiple items at once.)
Another cost for WinRT collections is the reading of elements. WinRT collections are exposed as interfaces and thus virtual, which in turn means the methods cannot be inlined like they would for a normal function. Furthermore, every read requires a range check. So if you need to read the same value multiple times, consider coping it to a local variable instead of rereading it from the collection. (The downside of the copy is that you have to actually either copy the value or increment the reference count on the object, an interlocked operation.)
A way to completely avoid this cost is to copy the collection before iterating over it. Allocate a local vector of the correct size, then use GetMany function in conjunction with an ArrayReference. When used in conjunction with ReplaceAll, you can iterate over the collection several times and make a series of complex modifications while only crossing the WinRT boundary three times.
Interfaces in WinRT
In WinRT, like in traditional COM, members of an object are exposed only via interfaces. You can never access the object directly. C++/CX hides this from you by making implicit casts as necessary. A common reason this would be necessary is when you need to invoke methods on non-default interfaces.
Casting in WinRT is not cheap. It requires a call to QueryInterface, a virtual method, and an interlocked operation to increase the reference count. And once the call on the non-default interface is complete, the reference count needs to be decremented with another interlocked operation.
Library Authors: Make sure all of your class’s commonly used methods are available on the default interface so that casts to other interfaces are not needed. Also make sure you document which is the default interface, as it won’t be displayed in the IDE.
If making multiple calls to the same non-default interface, create a local variable of that interface type. That way only a single cast will be required instead of one per method call.
Whenever possible, you should use stack allocated or unique_ptr classes. This will give you the best performance of all your options.
When you do need a complex lifecycle, your next choice is normal C++ class accessed via a shared_ptr. The difference between this and the above options can primarily be found in the overhead needed for reference counting.
You choice of last resort should be the ref class. A ref class has reference counting semantics like the shared_ptr, but can bring with it other WinRT-based overhead. So only use a ref class when the class needs to be passed to a WinRT function or be data-bound in XAML.
When using ref classes, try to keep the inheritance hierarchy shallow. WinRT inheritance is not like C++ inheritance and it has an additional overhead.
XAML Data Binding
In WinRT+XAML data binding you should avoid implementing INotifyPropertyChanged unless you actually expect the properties to change after it is populated. Likewise, don’t expose public setters unless the UI actually needs to modify data.
Getters called by XAML should be cheap. Not only are they called on the UI thread, they may be called multiple times. So don’t allocation memory or perform expensive calculations in the getter.
An important bit of advice for all XAML-based UIs (WPF, Silverlight, WinRT+XAML) is to keep the data hierarchy shallow. Each dot in a binding expression represents one more property changed event that the data binding engine has to listen for in order to keep the screen up to date.
About the Author
Jonathan Allen has been writing news report for InfoQ since 2006 and is currently the lead editor for the .NET queue. If you are interested in writing news or educational articles for InfoQ please contact him at email@example.com.
Brandon Holt, Preston Briggs, Luis Ceze, Mark Oskin May 21, 2015
Kai Kreuzer, Olaf Weinmann May 21, 2015