Welcome

首页 / 软件开发 / C++ / C++/CX的性能陷阱

C++/CX的性能陷阱2014-11-02 infoq 译:孙镜涛使用C++/CX编写应用程序和编写正常的C++应用程序不一样。纯C++代码和Windows运行时(WinRT)之间的互操作性出奇的昂贵。基于Sridhar Madhugiri的视频 C++/CX 最佳实战中的内容,我们在本文中列举了一些在Windows 8开发中避免性能问题的方式。

边界

在应用程序的边界上会产生多种性能障碍。

数据转换就是其中的一个例子。考虑一下一个Web服务客户端和应用程序剩余部分之间的典型边界。大多数Web服务是使用UTF-8编码的,而大多数Windows应用程序的内部则是使用UTF-16编码的。在Windows中UTF-16编码是如此的流行以致于人们有时会将它错误地称为“Unicode”编码。数据转换的成本可能是确定的,也可能广泛变化,这依赖于它在数据本身中的特定值。

下一种性能消耗来自于类型转换。例如,你可能需要一个wstring,但是却有一个wchar_t *。尽管在内存中每种类型所包含的数据看起来是一样的,但是将这些内容从一个数据结构复制到另一个数据结构依然是有性能成本的。

最后一种性能消耗来自于数据复制操作。有时候你必须为边界处的数据复制付出代价,哪怕它们并不需要数据转换和类型转换。

我们为什么要在现在讨论这些内容呢?原因是WinRT本身就是应用程序和操作系统其余部分之间的边界。编写高性能C++/CX应用程序的本质就是识别边界并在可能的情况下避免跨越边界。

如果跨越WinRT边界的操作无法避免,那么就寻找一些方式减少数据复制、类型转换和数据转换操作的数量。例如,如果数据源和目标都使用UTF-8编码,那么就没必要将数据转换为UTF-16,因为你最终还是需要将其再转换回来。

字符串

在大多数应用程序中字符串都是主要的数据类型。文件系统、Web服务、UI、消息、符文和契约等领域对字符串的依赖性日益加深。不幸的是人们所使用的字符串类型非常多。

在内部,大多数应用程序可能会使用std::wstring或者std::wchar_t*,你所依赖的大多数第三方类库也是如此。但是在与WinRT类库进行通信的时候你需要切换到Platform::String^。每一次转换都需要一次内存分配和一次数据复制操作。

String^和本地C++版本之间的一个关键区别是:String^是不可变的。WinRT运行时对不可变字符串的这种强调可能来自于.NET和CLR。正如^符号所表示的,String^也是引用计数。

人们可能会对可变和不可变字符串相关的优点争论一整天,但是最终只有一个事实。因为C++标准类库只理解可变字符串,而WinRT仅理解不可变字符串,所以对这两者你都必须进行处理。正如前面所提到的,这意味着需要对字符串进行复制。

类库作者:如果你正在构建一个一般用途的类库供他人使用,那么你应该考虑提供多个不同版本的API,为每种字符串类型提供一个API。这样你就不需要猜测API的使用者在调用类库的时候使用的是哪种字符串类型了。

很多基于字符串的操作实际上并不需要使用字符串,但是开发者宁可选择使用字符串迭代器。因为可变和不可变数据结构的迭代操作是一样的,你可以在使用常规xxx_iterator( begin(string), end(string), …)语法的字符串平台上直接创建STL样式的迭代器。

另外,首先要查找直接返回wchar_t*的API,而不是将它封装成一个wstring。如果你找到了这样的API,那么你就能够通过数组中第一个元素的地址以及数组的长度创建一个新的platform string。这样就不需要创建一个在匹配的platform string被创建之后立即就会被废弃的wstring。

调用带有字符串引用(StringReference )类型输入参数的WinRT API时有一个小窍门。你可以向一个参数类型为platform string的WinRT函数传递一个wchar_t* 或者wstring参数,这种情况下将创建一个轻量级外观。无论如何,这里有一些需要注意的地方。

字符串必须是空终止否则将会抛出一个错误。

如果字符串在函数之外的任何地方发生了变化,那么结果将无法确定。

如果函数之内有任何字符串的引用,那么无论如何都会生成一个完整副本。

上面的第1条内容很容易验证,第2条则仅会在碰见线程安全问题的时候发生。在大多数环境下这应该是一个非常有用的技巧。

类库作者:为了确保上面的方案是真实可能的,首先尽量避免让它引用你以StringReference参数的方式获取到的字符串。因为随后的引用并不会引入额外的复制,所以不要担心使用第二个引用。