集合与C++中常见的集合相比,WinRT中的集合是非常昂贵的。和.NET中可观察的集合一样,对WinRT集合的每一次修改都会产生一个通知。该通知主要用于XAML数据绑定以便于更新UI。 在初始化期间避免这种损失的一种方式是,首先在堆栈上创建并填充一个标准的vector,然后使用move函数初始化一个platform vector。你能够这样做,因为标准的vector将会被销毁,同时它的动态内存无论如何都会被释放。 在更新很多元素的时候,考虑使用ReplaceAll方法。这仅会触发一个通知而不是每一条记录一个通知。在WPF和Silverlight中没有与之相对应的方法,因为这些UI堆栈本身不支持一次性插入或者移除多个条目。 WinRT集合中的另一种性能消耗来自于元素的读取。WinRT集合是以接口的形式暴露的,因此它们是虚的,这就意味着它们并不能像普通的函数那样被内联。此外,每一次读取都需要进行范围检查。所以如果你需要多次读取同一个值,考虑将它复制到一个局部变量中,不要每次都从集合中读取。实际上复制的缺点是,你必须复制值或者增加对象上的引用数,这是一个连锁操作。 完全避免这种消耗的一种方式是在迭代集合之前复制它。分配一个正确大小的局部vector,然后在ArrayReference上使用GetMany函数。然后结合使用ReplaceAll方法,你就能够对集合进行几次迭代,仅需要跨越WinRT边界三次就能够做一系列复杂的修改。 WinRT接口和传统的COM一样,在WinRT中一个对象的成员仅会通过接口暴露。你永远都不可能直接访问对象。C++/CX通过做必要的隐式转换对你隐藏了这些细节。这样做之所以必要的一个常见原因是,可以满足调用非默认接口上的方法时的需要。 WinRT中的转换不是廉价的。它需要调用QueryInterface这个虚方法,同时有一个增加引用数的连锁操作。一旦完成了对非默认接口的调用,还需要另一个减少引用数的连锁操作。 类库作者: 确保类中所有的常用方法在默认接口上都是可用的,这样就不需要转换成另一个接口了。 如果要对同一个非默认接口进行多次调用,那么创建一个该接口类型的局部变量。这样仅需要执行一次转换,而不是每次方法调用时都做一次转换。 类在任何可能的时候你都应该使用堆栈分配或者unique_ptr类。因为这样你将获得所有选项的最好性能。 在你确实需要一个复杂生命周期的时候,你的下一个选择是通过一个shared_ptr访问的普通C++类。这种方式和上面选项之间的主要区别在于引用数开销。 你选择的最后一种手段应该是ref类。一个ref类拥有和shared_ptr相似的引用数语义,但是能够带来其他基于WinRT的开销。所以仅在需要将类传递到一个WinRT函数或者在XAML的数据绑定中使用ref类。 在使用ref类的时候,尽量保持较浅的继承层次。WinRT继承和C++继承不一样,它有额外的开销。 XAML数据绑定在WinRT+XAML数据绑定中你应该避免实现INotifyPropertyChanged,除非你确实希望在属性被填充之后发生改变。同样的,不要暴露公共set函数,除非UI确实需要修改数据。 XAML所调用的get函数应该是廉价的。不仅仅是因为它们是在UI线程上被调用的,还因为我们可能会调用它们多次。所以不要在get函数中分配内存或者执行昂贵的计算。 对于所有基于XAML的UI(WPF、Silverlight、WinRT+XAML)而言,一点非常重要的建议是保持较浅的数据层次。绑定表达式中的每一个点都代表了一次属性改变事件,而为了保持屏幕及时更新数据绑定引擎必须监听这些事件。 关于作者
查看英文原文:C++/CX Performance Pitfalls |