Windows 运行时组件和优化的互操作

创建使用 Windows 运行时组件的 Windows 应用并在本机和托管类型之间进行互操作,同时避免互操作性能问题。

Windows 运行时组件互操作性的最佳做法

如果不够细心,使用 Windows 运行时组件可对应用性能造成很大的影响。 本章节讨论了在应用使用 Windows 运行时组件时如何获得良好的性能。

简介

互操作性 可对性能造成很大的影响,你可能在使用互操作性的时甚至都未意识到你正在使用它。 Windows 运行时将为你处理大量互操作,以便你提高效率和重复使用以其他语言编写的代码。 我们鼓励利用 Windows 运行时为你执行操作,但请注意,这可能会影响性能。 本节内容讨论了你为减少互操作对应用性能的影响而采取的行动。

Windows 运行时具有一个类型库,可通过编写通用 Windows 平台应用的任何语言来访问这些类型。 可在 C# 或 Microsoft Visual Basic 中以使用 .NET 对象的相同方法来使用 Windows 运行时类型。 无需执行平台调用方法调用即可访问 Windows 运行时组件。 这会让编写应用变得没那么复杂,但请务必知道,可能会发生比你预期更多的互操作。 如果使用 C# 或 Visual Basic 以外的语言来编写 Windows 运行时组件,在使用此组件时可跨越互操作性边界。 跨域互操作性边界可影响应用的性能。

当使用 C# 或 Visual Basic 开发通用 Windows 平台应用时,两个最常见的 API 组合是 Windows 运行时 API 和适用于 UWP 应用的 .NET API。 通常,Windows 提供的类型在以“Windows”开头的命名空间中的 Windows 运行时上进行构建,而 .NET 类型在以“System”开头的命名空间中构建。不过也有例外。 UWP 应用的 .NET 中的类型在使用时不要求互操作性。 如果发现使用 Windows 运行时的区域出现较差性能,则可以使用适用于 UWP 应用的 .NET 来获得更佳性能。

注意 大多数随 Windows 10 提供的 Windows 运行时组件是使用 C++ 实现的,因此从 C# 或 Visual Basic 中使用它们时将跨越互操作性边界。 如往常一样,在更改代码前,请务必监测应用,确认使用 Windows 运行时是否影响应用性能。

在本主题中,当提及“Windows 运行时组件”时,我们是指以 C# 或 Visual Basic 以外的语言编写的 Windows 运行时组件。

 

每次访问 Windows 运行时组件的属性或调用其方法时,都会产生互操作成本。 实际上,创建 Windows 运行时组件的成本比创建 .NET 对象的成本更高。 原因是 Windows 运行时必须执行可从你的应用语言转换为组件语言的代码。 此外,如果你将数据传递给该组件,数据必须在已管理和未管理类型中转换。

高效使用 Windows 运行时组件

如果发现需要获得更佳性能,可确保代码尽可能高效地使用 Windows 运行时组件。 本节内容讨论了在使用 Windows 运行时组件时的一些改善性能的提示。

要发现性能影响,需在短时间内产生大量调用。 一个设计完好的应用程序(封装了业务逻辑和其他托管代码中对 Windows 运行时组件的调用)不应产生巨额的互操作成本。 但如果测试表明,使用 Windows 运行时组件会影响应用性能,本节内容所讨论的提示将有助于改善性能。

考虑使用适用于 UWP 应用的 .NET 提供的类型

在某些情况下,既可以使用 Windows 运行时类型,也可以使用适用于 UWP 应用的 .NET 提供的类型来完成任务。 最好不要尝试混合使用 .NET 类型和 Windows 运行时类型。 尽量坚持使用其中一种类型或另一种类型。 例如,既可使用 Windows.Data.Xml.Dom.XmlDocument 类型(Windows 运行时类型),也可使用 System.Xml.XmlReader 类型(.NET 类型)来分析 xml 流 。 使用来自与流相同的技术的 API。 例如,如果你读取来自 MemoryStream 的 xml,请使用 System.Xml.XmlReader 类型,因为两种类型均为 .NET 类型。 如果从文件读取,请使用 Windows.Data.Xml.Dom.XmlDocument 类型,因为文件 API 和 XmlDocument 均在本机 Windows 运行时组件中实现 。

将 Window 运行时对象复制至 .NET 类型

当 Windows 运行时组件返回 Windows 运行时对象时,将该返回的对象复制到 .NET 对象中可能很有益处。 尤其重要的两个位置就是你在处理集合和流时的两个位置。

如果调用返回集合的 Windows 运行时 API,然后多次保存和访问该集合,则将该集合复制到 .NET 集合并从此使用 .NET 版本可能很有利。

缓存对 Windows 运行时组件的调用结果以供以后使用

通过将值保存到本地变量(而非多次访问 Windows 运行时类型),可能可以获得更佳的性能。 如果你使用循环内的值,这会更加有利。 监测你的应用,以确认使用本地变量是否可改善你的应用性能。 使用已缓存的值可提高应用的速度,因为这会花费更少的互操作性时间。

将调用合并到 Windows 运行时组件中

尽量尝试以最少次数调用 UWP 对象来完成任务。 例如,读取大量流数据通常要优于一次读取少量数据。

使用尽可能以较少调用来捆绑工作的 API,而不使用处理更少工作而要求更多调用的 API。 例如,首选通过调用可一次初始化多个属性的构造函数来创建对象,而非调用默认构造函数和每次指派一个属性。

构建 Windows 运行时组件

如果编写可为以 C++ 或 JavaScript 编写的应用所使用的 Windows 运行时组件,请确保组件的设计可提供良好性能。 所有在应用中获取良好性能的建议都适用于在组件中获取良好性能。 监测你的组件,以发现哪个 API 拥有较高通信模式,且在这些区域考虑提供可让用户使用较少调用来处理工作的 API。

在通过托管代码使用互操作时,使应用保持高速

使用 Windows 运行时可以在本机代码和托管代码之间轻松地互操作,但是如果你不小心,则可能会产生性能成本。 下面,我们向你展示当在托管的 UWP 应用中使用互操作时,如何获得良好性能。

借助每种语言中提供的 Windows 运行时 API 投影,Windows 运行时允许开发人员配合使用 XAML 和他们自己选择的语言来编写应用。 使用 C# 或 Visual Basic 编写应用时,此便利会带来互操作成本,因为 Windows 运行时 API 通常是使用本机代码实现的,且从 C# 或 Visual Basic 进行的任何 Windows 运行时调用都要求从托管堆栈帧到本机堆栈帧进行 CLR 过渡以及从编组函数参数到可由本机代码访问的表示进行 CLR 过渡。 对于大多数应用来说,此开销是微不足道的。 但是,如果在应用的关键路径中多次(几十万到几百万)调用 Windows 运行时 API,则此成本可能会显著增加。 通常你会希望确保在语言转换上花费的时间相对少于其余代码的执行时间。 下面图表对此进行了展示。

Interop transitions should not dominate the program execution time.

从 C# 或 Visual Basic 使用时,在 .NET for Windows apps 上列出的类型不会产生此互操作成本。 根据经验,你可以假定命名空间中以“Windows.”开头的类型 是 Windows 提供的 Windows 运行时 API 集的一部分,并且是以“System”开头的命名空间中的类型。 属于 .NET 类型。 请记住,即使简单使用 Windows 运行时类型(例如,分配或属性访问)也会产生互操作成本。

你应衡量你的应用并在优化你的互操作成本前确定互操作是否占据你的应用执行时间的一大部分时间。 当使用 Visual Studio 分析应用性能时,可以通过使用“函数”视图并查看调用 Windows 运行时的方法所花费的包含时间,来轻松获取互操作成本上限。

如果互操作开销拖慢了应用的运行速度,可以通过减少对热门代码路径上的 Windows 运行时 API 的调用来提高应用的性能。 例如,通过定期查询 UIElements 的位置和维数来执行大量物理计算的某个游戏引擎可以通过以下操作节省大量时间:存储从 UIElements 到本地变量的必要信息、对这些已缓存值执行计算,以及在完成计算后将最终结果分配回 UIElements。 另一个示例:如果 C# 或 Visual Basic 代码频繁访问某个集合,则从 System.Collections 命名空间使用该集合比从 Windows.Foundation.Collections 命名空间使用该集合更加有效。 还可考虑组合调用 Windows 运行时组件;一个可行的示例是使用 Windows.Storage.BulkAccess API。

生成 UWP 组件

如果要编写可用于以 C++ 或 JavaScript 编写的应用的 Windows 运行时组件,请确保组件的设计可提供良好性能。 你的 API 表面定义了你的互操作边界,也定义了用户必须考虑本主题中指导原则的程度。 如果你向其他方分发你的组件,则这些原则变得尤为重要。

所有在应用中获取良好性能的建议都适用于在组件中获取良好性能。 衡量你的组件,查明哪些 API 具有较高通信模式,并从这些方面考虑提供可让用户使用较少调用来处理工作的 API。 设计 Windows 运行时时投入了极大的努力,以便使应用可使用该运行时,同时无需频繁地超越互操作边界。