【发布时间】:2017-06-22 16:52:35
【问题描述】:
我注意到 WPF 应用程序启动有时很慢。有人知道原因是元素初始化还是 DLL 加载还是其他原因?
【问题讨论】:
我注意到 WPF 应用程序启动有时很慢。有人知道原因是元素初始化还是 DLL 加载还是其他原因?
【问题讨论】:
以下文字摘自this MSDN article on Improving WPF applications startup time(编辑:现已合并到WPF Application Startup Time)
启动 WPF 应用程序所需的时间量可能会有很大差异。本主题介绍了用于减少 Windows Presentation Foundation (WPF) 应用程序的感知启动时间和实际启动时间的各种技术。
了解冷启动和热启动
当您的应用程序在系统重新启动后首次启动时,或者当您启动应用程序时,将其关闭,然后在很长一段时间后再次启动时,就会发生冷启动。当应用程序启动时,如果 Windows 内存管理器的备用列表中不存在所需的页面(代码、静态数据、注册表等),则会发生页面错误。需要磁盘访问才能将页面放入内存。
当主要公共语言运行时 (CLR) 组件的大部分页面已经加载到内存中时,就会发生热启动,这可以节省昂贵的磁盘访问时间。这就是托管应用程序在第二次运行时启动速度更快的原因。
实现启动画面
如果在启动应用程序和显示第一个 UI 之间存在明显的、不可避免的延迟,请使用 启动画面 优化感知的启动时间。这种方法几乎在用户启动应用程序后立即显示图像。当应用程序准备好显示它的第一个 UI 时,启动屏幕会消失。从 .NET Framework 3.5 SP1 开始,您可以使用 SplashScreen 类来实现启动画面。如需更多信息,请参阅How to: Add a Splash Screen to a WPF Application。
您还可以使用本机 Win32 图形来实现自己的启动画面。在调用 Run 方法之前显示您的实现。
分析启动代码
确定冷启动缓慢的原因。磁盘 I/O 可能是原因,但情况并非总是如此。一般来说,您应该尽量减少对外部资源的使用,例如网络、Web 服务或磁盘。
在测试之前,请确认没有其他正在运行的应用程序或服务使用托管代码或 WPF 代码。
重新启动后立即启动您的 WPF 应用程序,并确定显示所需的时间。如果您的应用程序的所有后续启动(热启动)都快得多,那么您的冷启动问题很可能是由 I/O 引起的。
如果您的应用程序的冷启动问题与 I/O 无关,则可能是您的应用程序执行了一些冗长的初始化或计算,等待某个事件完成,或者在启动时需要大量 JIT 编译。以下部分更详细地描述了其中一些情况。
优化模块加载
使用 Process Explorer (Procexp.exe) 和 Tlist.exe 等工具来确定您的应用程序加载哪些模块。命令 Tlist
例如,如果您没有连接到 Web,并且您看到 System.Web.dll 已加载,那么您的应用程序中有一个模块引用了此程序集。检查以确保引用是必要的。
如果您的应用程序有多个模块,请将它们合并到一个模块中。这种方法需要较少的 CLR 程序集加载开销。更少的程序集也意味着 CLR 维护的状态更少。
延迟初始化操作
考虑将初始化代码推迟到主应用程序窗口呈现之后。
请注意,初始化可能在类构造函数内部执行,如果初始化代码引用其他类,可能会导致执行许多类构造函数的级联效应。
避免应用程序配置
考虑避免应用程序配置。例如,如果应用程序具有简单的配置要求并且具有严格的启动时间目标,则注册表项或简单的 INI 文件可能是更快的启动替代方案。
利用 GAC
如果程序集未安装在全局程序集缓存 (GAC) 中,则强名称程序集的哈希验证和 Ngen 图像验证会导致延迟(如果计算机上有该程序集的本机映像)。 GAC 中安装的所有程序集都会跳过强名称验证。如需更多信息,请参阅Gacutil.exe (Global Assembly Cache Tool)。
使用 Ngen.exe
考虑在您的应用程序上使用本机图像生成器 (Ngen.exe)。使用 Ngen.exe 意味着用 CPU 消耗换取更多磁盘访问,因为 Ngen.exe 生成的本机映像可能大于 MSIL 映像。
为了缩短热启动时间,您应该始终在您的应用程序上使用 Ngen.exe,因为这样可以避免 JIT 编译应用程序代码的 CPU 成本。
在一些冷启动情况下,使用 Ngen.exe 也很有帮助。这是因为不需要加载 JIT 编译器 (mscorjit.dll)。
同时拥有 Ngen 和 JIT 模块可能会产生最坏的影响。这是因为必须加载 mscorjit.dll,并且当 JIT 编译器处理您的代码时,当 JIT 编译器读取程序集的元数据时,必须访问 Ngen 图像中的许多页面。
Ngen 和 ClickOnce
您计划部署应用程序的方式也会影响加载时间。 ClickOnce 应用程序部署不支持 Ngen。如果您决定将 Ngen.exe 用于您的应用程序,则必须使用其他部署机制,例如 Windows Installer。
如需更多信息,请参阅Ngen.exe (Native Image Generator)。
变基和 DLL 地址冲突
如果您使用 Ngen.exe,请注意,在内存中加载本机图像时可能会发生变基。如果由于地址范围已分配而未在其首选基地址加载 DLL,Windows 加载程序将在另一个地址加载它,这可能是一个耗时的操作。
您可以使用虚拟地址转储 (Vadump.exe) 工具检查是否存在所有页面都是私有的模块。如果是这种情况,则模块可能已重新定位到不同的地址。因此,它的页面无法共享。
有关如何设置基地址的更多信息,请参阅Ngen.exe (Native Image Generator)。
优化验证码
验证码验证会增加启动时间。 Authenticode 签名的程序集必须通过证书颁发机构 (CA) 进行验证。此验证可能很耗时,因为它可能需要多次连接到网络才能下载当前的证书吊销列表。它还确保在通往受信任根的路径上有完整的有效证书链。在加载程序集时,这可能会导致几秒钟的延迟。
考虑在客户端计算机上安装 CA 证书,或尽可能避免使用 Authenticode。如果您知道您的应用程序不需要发布者证据,则您无需支付签名验证的费用。
从 .NET Framework 3.5 开始,有一个配置选项允许绕过 Authenticode 验证。为此,请将以下设置添加到 app.exe.config 文件:
<configuration>
<runtime>
<generatePublisherEvidence enabled="false"/>
</runtime>
</configuration>
在 Windows Vista 上比较性能
Windows Vista 中的内存管理器有一项称为 SuperFetch 的技术。 SuperFetch 分析一段时间内的内存使用模式,以确定特定用户的最佳内存内容。它不断工作以始终保持该内容。
这种方法不同于 Windows XP 中使用的预取技术,后者将数据预加载到内存中而不分析使用模式。随着时间的推移,如果用户在 Windows Vista 上频繁使用您的 WPF 应用程序,您的应用程序的冷启动时间可能会有所改善。
高效使用 AppDomains
如果可能,将程序集加载到与域无关的代码区域,以确保在应用程序中创建的所有 AppDomain 中使用本机映像(如果存在)。
为了获得最佳性能,请通过减少跨域调用来强制执行高效的跨域通信。如果可能,请使用不带参数或带有原始类型参数的调用。
使用 NeutralResourcesLanguage 属性
使用NeutralResourcesLanguageAttribute 指定ResourceManager 的中性文化。这种方法避免了不成功的程序集查找。
使用 BinaryFormatter 类进行序列化
如果必须使用序列化,请使用 BinaryFormatter 类而不是 XmlSerializer 类。 BinaryFormatter 类在 mscorlib.dll 程序集中的基类库 (BCL) 中实现。 XmlSerializer 在 System.Xml.dll 程序集中实现,它可能是要加载的附加 DLL。
如果你必须使用XmlSerializer类,如果你预先生成序列化程序集,你可以获得更好的性能。
配置 ClickOnce 以在启动后检查更新
如果您的应用程序使用 ClickOnce,请通过将 ClickOnce 配置为在应用程序启动后检查部署站点是否有更新,从而避免在启动时访问网络。
如果您使用 XAML 浏览器应用程序 (XBAP) 模型,请记住即使 XBAP 已经在 ClickOnce 缓存中,ClickOnce 也会检查部署站点的更新。如需更多信息,请参阅ClickOnce Security and Deployment。
将 PresentationFontCache 服务配置为自动启动
重新启动后运行的第一个 WPF 应用程序是 PresentationFontCache 服务。该服务缓存系统字体,改进字体访问,并提高整体性能。启动服务存在开销,在某些受控环境中,请考虑将服务配置为在系统重启时自动启动。
以编程方式设置数据绑定
不要使用 XAML 以声明方式为主窗口设置 DataContext,而是考虑在 OnActivated 方法中以编程方式设置它。
【讨论】:
我见过的关于修复 WPF 启动性能的最有用的建议是 in this other question:在每个框架文件夹中运行“ngen update”。
Microsoft 似乎无法让他们的 ngen 缓存保持最新,这导致您的应用程序几乎每次启动都会重新编译一半的 .NET 框架。
难以置信,但似乎是真的。
【讨论】:
如果您使用 Framework 3.51 而不是 3.5 或 3.0,WPF 应用程序的启动时间会快很多。 3.51 确实是一个改进。
【讨论】:
这是一个旧线程,但我在尝试修复我的 Win10 系统上的 WPF 应用程序的启动性能问题时已经多次来到这里,所以我想我会陈述一个可能对其他人有帮助的答案 - 一个答案对于这个系统上的所有 WPF 应用程序来说,这需要可怕的 5 秒启动时间,而只有几毫秒。 删除 nVidia“3d Vision”驱动程序。我有一个 GeForce GTX 650 卡,“3d Vision”驱动程序似乎没有提供任何有用的东西,所以删除它对我来说没有问题。 VisualStudio2015 性能分析工具最终帮助表明,在通过 nvapi64.dll(nVidia 驱动程序)调用后,几乎所有的 5 秒启动时间都在 IDLE 中度过。哇。
【讨论】:
Stuart 链接到的优秀文章对我帮助最大的是 XmlSerializer 技巧。这真的剃了好几秒钟。此外,不要低估对 HD 进行碎片整理 :-)
【讨论】: