14

我有一个用 VB.NET 为 Framework 4.5 编写的 WinForms 应用程序。我注意到应用程序的启动时间异常长(我编写的其他在启动时做更多工作的应用程序几乎立即启动,这个应用程序需要> 5秒)多次启动后启动时间不会改变,所以我猜这不是应用程序首次启动期间未缓存的 CLR 代码的情况。

我通过在启动期间记下时间进行了一些测试:

Module modMain
    Public MyLog As System.Text.StringBuilder

    <STAThread>
    Public Sub Main()
        MyLog = New System.Text.StringBuilder

        Application.EnableVisualStyles()
        Application.SetCompatibleTextRenderingDefault(False)
        MyLog.AppendLine("Before run: " & Date.Now.ToString & "," & Date.Now.Millisecond.ToString)
        Application.Run(frmMain)
    End Sub
End Module

Sub Main()是应用程序的入口点。它运行frmMain,我可以控制的第一件事是Sub InitializeComponent()由设计师生成:

<System.Diagnostics.DebuggerStepThrough()> _
Private Sub InitializeComponent()
    MyLog.AppendLine("Init Start: " & Date.Now.ToString & "," & Date.Now.Millisecond.ToString)
    'All the control initializations
    MyLog.AppendLine("Init End: " & Date.Now.ToString & "," & Date.Now.Millisecond.ToString)
End Sub

最后我到达了Form.Load活动

Private Sub frmMain_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    MyLog.AppendLine("Form_Load Start: " & Date.Now.ToString & "," & Date.Now.Millisecond.ToString)
    '...
    MyLog.AppendLine("Form_Load End: " & Date.Now.ToString & "," & Date.Now.Millisecond.ToString)
End Sub

现在,MyLog 的输出如下:

Before run: 15.12.2014 19:56:47,579
Init Start: 15.12.2014 19:56:51,451
Init End: 15.12.2014 19:56:51,521
Form_Load Start: 15.12.2014 19:56:51,544
Form_Load End: 15.12.2014 19:56:51,547

您可以看到,主要停顿发生在Application.Run()和之间Sub InitializeComponent()。我从其他问题中知道,GUI 线程启动了一个消息循环,但我不知道为什么这个应用程序比其他应用程序慢得多。

所以我的问题是:Application.Run 和我重新控制代码之间到底发生了什么,我可以做些什么来加快它的速度吗?在那里完成的工作是否与表单上的组件相关?

我已经尝试过使用frmMain.ShowDialog()而不是Application.Run(frmMain),但这导致了相同的结果。我正在使用 Visual Studio Express,所以很遗憾我不能使用更深入的性能分析器。

将其标记为 C# 和 VB.NET,因为两种语言的答案都非常受欢迎。

编辑
我做了更多的测试,包括 SLaks answer 中提出的解决方案。使用NGEN预编译程序集似乎没有任何明显的效果。所以我猜这不是代码的JIT编译InitializeComponent

然而,我注意到,在其他系统上,该程序几乎可以立即启动(快 10 倍以上),即使所讨论的计算机在所有方面都较慢。计算机之间的区别在于操作系统:

Windows 7: Slow start
Windows 8.1: Fast start
Windows Server 2008: Fast start

这些只是更多的线索,我真的不知道它是否有助于回答。

编辑 2
在启动期间查看 ProcMon 我发现执行挂起在以下几行:

"15:56:29.3547260","Electrochemical Calculator.exe","5972","CreateFile","C:\Users\Jens\Desktop\Electrochemical Calculator Barebone\Electrochemical Calculator\bin\Release\de\Electrochemical Calculator.resources.dll","PATH NOT FOUND","Desired Access: Read Attributes, Disposition: Open, Options: Open Reparse Point, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a"
"15:56:29.3548019","Electrochemical Calculator.exe","5972","CreateFile","C:\Users\Jens\Desktop\Electrochemical Calculator Barebone\Electrochemical Calculator\bin\Release\de\Electrochemical Calculator.resources\Electrochemical Calculator.resources.dll","PATH NOT FOUND","Desired Access: Read Attributes, Disposition: Open, Options: Open Reparse Point, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a"
"15:56:29.3548612","Electrochemical Calculator.exe","5972","CreateFile","C:\Users\Jens\Desktop\Electrochemical Calculator Barebone\Electrochemical Calculator\bin\Release\de\Electrochemical Calculator.resources.exe","PATH NOT FOUND","Desired Access: Read Attributes, Disposition: Open, Options: Open Reparse Point, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a"
"15:56:29.3549519","Electrochemical Calculator.exe","5972","CreateFile","C:\Users\Jens\Desktop\Electrochemical Calculator Barebone\Electrochemical Calculator\bin\Release\de\Electrochemical Calculator.resources\Electrochemical Calculator.resources.exe","PATH NOT FOUND","Desired Access: Read Attributes, Disposition: Open, Options: Open Reparse Point, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a"
"15:56:32.8796760","Electrochemical Calculator.exe","5972","CreateFile","C:\Windows\Fonts\StaticCache.dat","SUCCESS","Desired Access: Generic Read, Disposition: Open, Options: Synchronous IO Non-Alert, Non-Directory File, Attributes: n/a, ShareMode: Read, Delete, AllocationSize: n/a, OpenResult: Opened"
"15:56:32.8797088","Electrochemical Calculator.exe","5972","QueryStandardInformationFile","C:\Windows\Fonts\StaticCache.dat","SUCCESS","AllocationSize: 9,633,792, EndOfFile: 9,633,792, NumberOfLinks: 1, DeletePending: False, Directory: False"
"15:56:32.8797218","Electrochemical Calculator.exe","5972","ReadFile","C:\Windows\Fonts\StaticCache.dat","SUCCESS","Offset: 0, Length: 60, Priority: Normal"
"15:56:32.8797429","Electrochemical Calculator.exe","5972","CreateFileMapping","C:\Windows\Fonts\StaticCache.dat","FILE LOCKED WITH ONLY READERS","SyncType: SyncTypeCreateSection, PageProtection: "

该问题仅在发布版本中进一步出现,并且仅在我直接从 Windows 资源管理器启动程序时出现。调试版本会立即启动(0.3 秒,而 5-10 秒),从 Visual Studio 启动的发布版本也是如此。

4

3 回答 3

12

好吧,您消除了所有导致启动延迟的正常来源。它绝对与 Application.Run() 没有任何关系,直到表单的 InitializeComponent() 方法完成运行后才会开始。并且您通过使用 Ngen.exe 消除了抖动开销。一定要区分冷启动延迟和热启动延迟,如果只是第一次启动程序时很慢那么这是硬件问题,你需要一个更快的磁盘。仅在某些机器上速度很慢这一事实强烈表明存在环境问题。

由在 InitializeComponent() 中运行的代码触发。换句话说,表单上控件的所有构造函数和属性设置器。这通常需要很少的时间,但周围肯定有麻烦制造者。您需要寻找一个重要的控件,尤其是在底层使用 COM(又名 ActiveX)的那种。像浏览器一样。还有更多的可能性,任何类名以“Ax”开头的东西。此类控件可以加载许多其他 DLL,并且很容易受到安全软件的关注。

一些调试技巧:

  • 确保您有一个良好的备份并开始从表单中删除控件,从非平凡的开始。
  • 单步执行 InitializeComponent() 方法时,5 秒的延迟足以引起注意。立即告诉您哪个特定控件和语句导致延迟。
  • 将调试器切换到非托管模式可以告诉您更多有关将哪些其他 DLL 加载到您的程序中的信息。项目+属性,调试选项卡,勾选“启用本机代码调试”选项。调试时请留意输出窗口,您将看到加载任何非托管 DLL 的加载通知。这可以查明导致延迟的特定 DLL。
  • 如果可能,请禁用反恶意软件,这样您就可以消除由于不适当的 DLL 扫描而导致的延迟。
  • SysInternals的TcpView实用程序非常适合检测网络延迟。当您看到您的程序联系 CRL 服务器时要小心,证书吊销列表查询可能会很慢。
  • SysInternals 的 Process Monitor实用程序非常适合查看由于大多数其他原因导致的延迟。跟踪可能非常大,如果您需要另一组眼睛,请将其保存并发布在文件共享站点上。
于 2014-12-18T11:24:45.143 回答
2

这段时间用于加载表单控件使用的每个程序集,并 JIT 处理 InitializeComponent 方法。

于 2014-12-15T19:09:43.837 回答
2

就像汉斯说的那样,你已经消除了明显的问题,对我来说,问题似乎是环境问题......那么在极端条件下你如何解决这个问题......

由于该过程始终需要 5 秒或更长时间(从资源管理器启动时),因此这是我在这种情况下会考虑采取的一些方法 -

注意- 由于可能存在时间问题,您可能需要重复实验几次才能获得不错的结果。显然,我仍然很难保证结果......如果我亲自做,如果这些高水平的想法不能 100% 奏效,我希望即兴发挥。但我肯定会给他们一个机会。

  • 使用Procdump启动进程,并在进程的性能计数器达到某个值(如 2、3 或 4)后进行n 完整转储。Elapsed Time然后,您可以在 windbg(大多数信息,难以使用)或 VS.Net(更易于使用,但可能会或可能不会显示您想要查找的内容,即使它存在 - 请使用混合模式调试时打开这些转储打开转储)。

  • 在类似的行上,但功能较弱,更难正确。配置ProcessExplorer以在查看进程属性时显示本机堆栈跟踪...一旦启动进程,切换到 ProcessExplorer,并查看堆栈跟踪对于进程中的各种线程..这更棘手,因为根据有多少线程,手动检查它们的堆栈跟踪可能根本无法正常工作..可能值得一两次,以防它确实使用最少的努力显而易见的问题。

无论哪种情况,请务必将您的符号配置为 Microsoft 的公共符号服务器。这样您就可以从本机堆栈跟踪中获取大部分信息。

总而言之,这个想法是......在这种情况下,如果某些高级功能需要几秒钟才能完成,那么它应该很有可能在堆栈跟踪中显示。

注意- 一旦你知道了高级功能,它可能是故事的结尾,或者可能仍然需要将它与其他系统级进程相关联..(再次作为典型的反病毒示例)..但是你当然可以期望有更多的线索来帮助支持任何这样的假设。

于 2014-12-19T08:41:18.310 回答