【问题标题】:Overhead associated with OutputDebugString in release build发布版本中与 OutputDebugString 关联的开销
【发布时间】:2009-04-08 10:47:56
【问题描述】:

在发布版本中调用 OutputDebugString 是否存在重大开销?

【问题讨论】:

    标签: windows performance winapi logging


    【解决方案1】:

    实测 - 1000 万次调用大约需要 50 秒。我认为这对于未使用的功能来说是很大的开销。

    使用宏可以帮助在发布版本中摆脱这种情况:

    #ifdef _DEBUG
        #define LOGMESSAGE( str ) OutputDebugString( str );
    #else
        #define LOGMESSAGE( str )
    #endif
    

    不仅删除了调用,还完全删除了参数评估和文本字符串,您不会在二进制文件中看到它们。

    【讨论】:

      【解决方案2】:

      我在回答这个问题很久之后才写这篇文章,但给出的答案错过了某个方面:

      当没有人监听它的输出时,OutputDebugString 可以非常快。然而,在后台运行一个监听器(无论是 DbgView、DBWin32、Visual Studio 等)可以使它慢 10 倍以上(在 MT 环境中要慢得多)。原因是这些侦听器挂钩了报告事件,并且它们对事件的处理是在 OutputDebugString 调用的范围内完成的。此外,如果多个线程同时调用 OutputDebugString,它们将被同步。更多信息,请参阅Watch out: DebugView (OutputDebugString) & Performance。

      作为旁注,我认为除非您正在运行实时应用程序,否则您不应该担心需要 50 秒才能运行 1000 万次调用的设施。如果您的日志包含 10M 条目,那么浪费的 50 秒是您的问题中最少的,因为您必须以某种方式分析野兽。 10K 的日志听起来要合理得多,根据Sharptooth 的测量,创建它只需0.05 秒。

      因此,如果您的输出在合理的大小范围内,使用 OutputDebugString 应该不会对您造成太大伤害。但是,请记住,一旦系统上有人开始收听此输出,就会出现减速。

      【讨论】:

      • 我目前正在追逐 OutputDebugString 的性能,而您对围绕它进行同步的并发线程所做的注释确实对事情有所启发。谢谢你说清楚。
      【解决方案3】:

      我在一篇文章中读到 OutPutDebugString 在内部做了一些有趣的事情:

      1. 创建\打开互斥体并无限等待,直到获得互斥体。
      2. 在应用程序和调试器之间传递数据是通过一个 4kbyte 的共享内存块完成的,其中一个 Mutex 和两个 Event 对象保护对它的访问。

      即使没有附加调试器(在发布模式下),在使用 OutputDebugstring 和各种内核对象时也会产生大量成本。

      如果您编写示例代码并进行测试,性能会非常明显。

      【讨论】:

        【解决方案4】:

        多年来,我在数十个服务器端发布模式应用程序中没有发现任何问题,所有这些应用程序都具有内置指标。您可能会得到印象,因为您可以找到的大多数调试捕获应用程序(DBWIN32 等)在将数据投射到屏幕上时都非常缓慢,这给人一种滞后的印象。

        当然,我们所有的应用程序都默认禁用此输出,但能够在现场打开它很有用,因为您可以查看多个应用程序的调试输出,序列化在 DBWin32 之类的东西中。对于涉及通信应用程序的错误,这可能是一种非常有用的调试技术。

        【讨论】:

          【解决方案5】:

          永远不要在发布版本中留下 OutputDebugString() 调用。始终使用#ifdef 语句删除它们,或者提供另一个开关来关闭它们。

          如果您将它们保留在其中,则默认禁用它们并仅在请求时激活它们,否则您的应用将难以调试其他表现良好的应用(即仅在请求时输出调试数据)。

          DebugView 可以捕获应用程序的输出,但如果不是每个应用程序都无缘无故地聊天,那当然是好事。

          【讨论】:

            【解决方案6】:

            为什么不自己测量呢?编译以下代码,运行它并计时。然后去掉对OutputDebugString的调用,重新编译重新运行。大约需要你三分钟的时间。

               #include <windows.h>
            
                int main() {
                    const int COUNT = 1000000;
                    int z = 0;    
                    for ( unsigned int i = 0; i < COUNT; i++ ) {
                        z += i;
                        OutputDebugString( "foo" );
                    }
                    return z;
                }
            

            【讨论】:

            • 请注意,仅运行此代码并不是全部。性能在很大程度上取决于监视调试输出的内容(DebugView、Visual Studio 调试器等),并且可能会根据您运行的 Windows 版本而有所不同,因此您需要在许多不同的情况下进行测试。这将花费一个人的时间超过三分钟。
            • 我已经用穷人的时间(NULL)测试了上面的代码sn-p - 开始。如果打开OutputDebugString,但没有附加调试器,则1000,000次需要13秒,如果打开DbgViewer捕获输出,时间为169秒。如果关闭OutputDebugString,则测量时间为0秒。
            【解决方案7】:

            我对这个话题很好奇,所以我做了一些研究。

            我已经发布了结果、源代码和项目文件,以便您可以为您的设置重复测试。涵盖了在不监视 OutputDebugString 的情况下运行发布模式应用程序,然后使用 Visual Studio 6、Visual Studio 2005 和 Visual Studio 2010 监视 OutputDebugString 以查看每个 Visual Studio 版本的性能差异。

            有趣的结果,Visual Studio 2010 处理 OutputDebugString 信息的速度比 Visual Studio 6 慢 7 倍。

            全文在这里:Whats the cost of OutputDebugString?

            【讨论】:

              【解决方案8】:

              现有答案可以追溯到 2009 / 2010 年。

              虽然被遗忘的 OutputDebugString() 的性能可能没有太大变化,但我可以看出该工具有很大的不同。

              • 无工具:30 µs/调用 (x86) 10 µs/调用 (x64)
              • DebugView(完全禁用):64 µs/调用 (x86) 37 µs/调用 (x64)
              • DebugView++(暂停):67 µs (x86)
              • DebugView(自动滚动已停用):735 µs/调用 (x86) 703 µs/调用 (x64)
              • DebugView++(禁用自动滚动):67 µs/调用
              • DebugView(自动滚动激活):936 µs (x86) 799 µs/调用 (x64)
              • DebugView++(自动滚动激活):67µs/调用
              • VS 2019:81 µs/调用
              • DebugView(同步 2 个实例):最高 1736 µs/调用
              • DebugView++(同步 2 个实例):最高 102µs/调用

              结论:

              原来的 DebugView 太慢了,我减少了样本的数量以便在某个时候真正完成。

              DebugView++ 做得非常好。

              VS 2019 似乎比旧版 Visual Studio 做得更好,提到 in this answer。我无法比较自己,但它与 DebugView++ 非常接近,我认为它非常好。


              测量结果:在单个 for 循环中调用了 100.000 次 OutPutDebugStringW。发布模式下的所有构建。 Intel i7-6820HQ,2.7 GHz,限制为 99% 以防止 Turbo Boosting。使用std::chrono::high_resolution_clock::now() 在 100.000 次调用之前和之后进行测量。

              【讨论】:

              • 这里是Debugview++的作者;感谢您的赞美 Thomas ;) OutputDebugString 导致减速的主要原因是是否有人在收听消息(例如,如果调试器已附加或 debugview++ 正在运行和收听。发送方的线程被有效阻止,直到收到确认阅读了该消息。Debugview++ 专门针对此功能进行了优化,但大多数其他工具不这样做。
              • 减速是否“显着”取决于您的应用程序。正如其他人所说:测量它:)
              • @JanWilmans:感谢您编写 DebugView++。我能够用它解决一个错误。由于时间问题,无法选择调试和 DebugView。
              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2017-08-21
              • 2012-07-12
              • 2010-10-27
              • 2020-08-02
              • 1970-01-01
              相关资源
              最近更新 更多