Visual Studio 性能分析:Application.DoEvents()
Posted
技术标签:
【中文标题】Visual Studio 性能分析:Application.DoEvents()【英文标题】:Visual Studio Performance Profiling: Application.DoEvents() 【发布时间】:2011-02-07 16:21:28 【问题描述】:当我在 Visual Studio 2010 中分析我的 C# 应用程序时,在 Line View 中,耗时第二高的函数被列为 System.Windows.Forms.Application.DoEvents()。列表中的第 7 位是 System.Windows.Forms.Form.ShowDialog()。这两个消耗了大约 8% 和 2.5% 的独家样品总数。
该程序没有太多的用户交互。用户单击一个按钮,应用程序启动并运行其算法大约一分钟,然后停止。在此期间,没有用户交互,但是 CPU 和 IO 使用量很大。
我不确定我是否理解上述两个函数(DoEvents 和 ShowDialog)为何会捕获如此多的独家样本。这两个有什么可以做的吗?
为澄清而编辑:该应用程序有 4 个不同的线程。一个线程从外部设备读取数据并将其放入队列中。另一个线程,从队列中读取数据,并执行数据操作。这个 CPU 密集型线程将处理过的数据放入另一个队列中。第三个线程读取此队列并定期将数据写入磁盘。所有线程都实现为 backgroundWorker。最后(第 4 个)线程是应用程序主 Form() 本身。它实际上在整个过程中是不活动的。
【问题讨论】:
绘画和窗口更新事件可以在 DoEvents 调用中处理。 Windows 绘画很慢,但应该不用担心。 【参考方案1】:我假设您真正想知道的是 - 是什么让应用程序变慢,对吧? 如果问题只是出于好奇,请忽略此答案。
当它花费时间时,只需点击暂停按钮,然后检查每个线程中的堆栈。
多做几次。您将确切地看到问题所在。
8% 和 2.5% 的独占时间纯属无用的胡言乱语。您的代码中的某些调用(不是函数,函数 call)** 在这些线程的堆栈中占了 large% 的时间。这是你的瓶颈,你会看到它。
这就是random-pausing 技术,它确实有效。
** 有时会错过这一点。函数和函数调用之间的区别就像手提箱和握住把手的手之间的区别。瓶颈不是函数,而是调用函数的一行代码(即使只是微代码)。调用该函数的其他代码行可能不是瓶颈。
【讨论】:
有趣的想法,但不适用于我们的应用程序。原因是我们在应用程序运行时从外部设备读取数据。当我们点击暂停时,我们开始丢失来自外部设备的传入数据(无法“暂停”外部设备),这导致数据流的完整性失败,即数据变得毫无意义,应用程序无法继续.不过谢谢,这个链接很有趣。 @SomethingBetter:不客气。事实上,我认为你可以做到。只是每一次的停顿都是“牺牲的”。你开始正常运行整个事情,中断它,并记录堆栈。然后你杀死它并重新开始。这就像我的一个同事正在研究大鼠麻醉剂的时间过程。他会注射药物,稍等片刻,将老鼠斩首并快速冷冻,然后看看药物去了哪里。让他成为素食主义者 :) 无论如何,这就是方法。 @SomethingBetter:不清楚,您不需要太多样本。最初的问题通常要花费 50% 或更多,这是每次看到它的机会,并且是通过修复它可以节省多少时间。换一种说法:如果您采集 n=3 个样本,并且看到可以修复其中 s=2 个样本的内容,则修复该内容可以为您节省 (s+1)/(n+2)=60% 的预期时间. (统计意义上的预期。) @Mike:是的,我想我可以(start-pause-examinestack-kill)!整个应用程序。我们的预期,通过观察配置文件数据,您的示例中的 n 和 s 将是,n=~20, s~2,即消耗大量周期的操作不会消耗超过所有 cpu 周期的 10%。正如您在评论中指出的那样,它曾经更糟糕。在我们开始优化之前,有 2 个函数,实际上大约 5-6 行代码消耗了 %40 的 cpu 周期。这种方法会很有效。但是现在热块更好地分散了。因此,抽取 3 个样本可能无济于事,但值得一试。 @SomethingBetter:问题是,DoEvents 中 8% 的时间是排他性的,这意味着程序计数器在该例程中。这意味着它正在处理大量的事件。其中许多事件很可能是多余的。此外,VS 采样器对 I/O 时间是盲目的,因此无论 I/O 有多大,它都不会看到过多的 I/O。更重要的是,每个线程都追踪一个调用树,排他采样只告诉叶子,而不是关于不必要的分支或水果(I/O)。暂停不会遇到这些问题。如果它找不到它,什么都不会。【参考方案2】:System.Windows.Forms.Application.DoEvents()
几乎就像在说“做所有的 GUI 逻辑”。不推荐使用 DoEvents,甚至可能被认为是危险的,因为它在许多 GUI 案例中引入了竞争条件和未指定的行为。
【讨论】:
我没有在代码中的任何地方显式调用 Application.DoEvents()。 我会说它很危险。最好的例子是一个 Windows 计时器,它的计时不会自行禁用,然后执行一个比计时器计时更长的操作,然后调用 Application.DoEvents。这最终导致堆栈溢出! (还有真实世界的故事!)【参考方案3】:DoEvents 用于处理 Windows 消息队列中的所有消息。您最好使用TPL 或异步处理来执行长时间运行的任务。
此外,ShowDialog
会阻塞直到表单关闭。这个问题Is it possible to use ShowDialog without blocking all forms? 比我能解释得更好。
所有这些 WinForms GUI 事件和处理都相当占用 CPU,而分析器非常清楚地说明了这一点。如果长时间运行的任务是通过线程完成的,您可能无需担心。
【讨论】:
以上是关于Visual Studio 性能分析:Application.DoEvents()的主要内容,如果未能解决你的问题,请参考以下文章