Delphi:使用调试器调用 C dll 函数需要 15 秒,而没有调试器需要 0.16 秒。为啥?

Posted

技术标签:

【中文标题】Delphi:使用调试器调用 C dll 函数需要 15 秒,而没有调试器需要 0.16 秒。为啥?【英文标题】:Delphi: Calling a C dll function with Debugger takes 15 s without debugger 0.16 s. Why?Delphi:使用调试器调用 C dll 函数需要 15 秒,而没有调试器需要 0.16 秒。为什么? 【发布时间】:2015-08-27 13:23:15 【问题描述】:

我有以下设置:

    用 Delphi XE5 编写并内置 Debug 64 位的 Delphi 命令行应用程序。 使用 Microsoft Visual Studio 2013 编写并内置于 64 位版本的 C dll。 Delphi 命令行应用程序调用 C dll 中的函数。

出乎意料:

    在 Delphi XE5 IDE 中调试 Delphi 命令行应用程序时,C dll 函数调用需要 15 秒。 直接启动同一个 Delphi 命令行应用程序(没有 IDE,没有调试器)时,C dll 函数调用需要 0.16 秒。

Delphi命令行应用源码:

program DelphiCpplibraryCall;
$APPTYPE CONSOLE
$R *.res
uses
  System.SysUtils,
  Windows;

type
  TWork = function(Count : Integer) : Integer; cdecl;
var
  Handle : THandle;
  Work   : TWork;
  Result : Integer;
  Freq   : Int64;
  Start  : Int64;
  Stop   : Int64;
begin
  try
    Handle := LoadLibraryEx('worker.dll', 0, LOAD_WITH_ALTERED_SEARCH_PATH);
    Work := GetProcAddress(Handle, 'work');

    QueryPerformanceFrequency(Freq);
    QueryPerformanceCounter(Start);
    Result := Work(500000);
    QueryPerformanceCounter(Stop);
    Writeln(Format('Result: %d Time: %.6f s', [Result, (Stop-Start) / Freq]));

    FreeLibrary(Handle);

    Readln;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

C dll源代码:

#include "worker.h"
#include <unordered_map>

class Item

public:
    Item(const int value = 0) : _value(value) 
    virtual ~Item(void) 
private:
    int _value;
;

int work(int count)

    typedef std::unordered_map<int, Item> Values;
    Values* values = new Values;
    int k = 0;
    for (size_t i = 0; i < count; i++)
    
        (*values)[i] = Item(i);
        k++;
    
    delete values;
    return k;

Delphi + C dll 源代码:DelphiCpplibraryCall.zip

运行时比较:

第一个控制台:在 IDE 中调试时 第二个控制台:在没有 IDE 的情况下启动时

由于某种原因,Delphi 调试器似乎大大减慢了 C dll 函数调用的速度,这使得调试几乎不可能。

有没有人知道什么可能会导致这个问题或如何避免它?非常感谢。

编辑:我现在可以确认,所描述的行为根本不限于 Delphi IDE 和调试器。如果我:

    我在 Microsoft Visual Studio 2013 发布版中构建了 C dll。 并在 Visual Studio 2013 中构建和调试调用 C dll 的命令行可执行文件。

这意味着C dll发布构建函数执行时间会根据是否附加调试器而变化。

我还可以确认,只要存在调试器,删除 unordered_map (delete values;) 就需要这么长时间。

【问题讨论】:

您的 DLL 实际上是用 C++ 实现的。我的猜测是 C++ 运行时检测到调试器的存在并添加了额外的内存访问自我检查、边界检查等。您是否尝试过 DLL 的发布版本?你的 C++ 代码效率也很低。 实际上,我看到您正在编译 DLL 的发布版本。这可能使这成为德尔福问题。 delphi x64 调试器很糟糕。我个人已经放弃了它,因为它太没用了。调试 32 位代码。 @DavidHeffernan 如上所述,C dll 已经是发布版本。 C dll函数实现完美展现运行时差异请忽略低效代码。 @DavidHeffernan 我应该提到我已经尝试过 Delphi 32 位调试器。你是对的,32 位 Delphi 调试器的问题并没有那么糟糕,但运行时仍然很长。 我会尝试复制。看看这是否能说明问题。大概你已经从一个真实的项目和孤立的 unordered_map 中删除了它。顺便说一句,提出这个问题的工作做得很好。很高兴看到这样一个干净的例子。 【参考方案1】:

如果延迟来自delete 调用,那么它可能是使用 Windows 堆内存管理器的内存管理器 (malloc)。当释放内存并附加调试器时,堆内存管理器会执行额外的广泛检查。

可以通过将环境变量 _NO_DEBUG_HEAP 设置为 1(以下划线开头)来禁用这些额外的检查。

您可以在 Delphi 中为Project/Options.../Debugger/Environment Block 下的特定项目执行此操作,或者您可以将_NO_DEBUG_HEAP 添加到Control Panel/System Properties/Advanced System Settings/Environment Variables/System Variables 下的用户环境块中,以便它适用于所有项目和所有应用程序。但是您可能需要注销以应用更改或至少重新启动 IDE。

【讨论】:

感谢您的详细解答。这实际上为我解决了这个问题。现在,当在 Delphi IDE 中调试命令行应用程序时,我的 C dll 的发布版本也可以快速运行。但是我在 Windows 环境变量设置中设置了变量。此外,我还找到了准确描述问题的The Windows Heap Is Slow When Launched from the Debugger。【参考方案2】:

这似乎是此 STL 容器的 MSVC 实现的问题。当您使用 Visual Studio 调试器时,可以看到完全相同的行为。 MSVC 运行时在检测到调试器时会切换行为。

我找到了与此问题相关的以下链接:

Why hash_map and unordered_map on my machine are extremely slow? http://www.drdobbs.com/cpp/c11-hash-containers-and-debug-mode/232200410 http://marknelson.us/2011/11/28/vc-10-hash-table-performance-problems/ https://connect.microsoft.com/VisualStudio/feedback/details/757277/c-hash-map-destructor-is-much-slower-in-vc10-than-vc9 http://w3facility.org/question/very-slow-unordered_map-clearing/

问题的很大一部分似乎是清除地图时的性能。只需从您的 C++ 代码中删除 delete 行即可看到性能大大提高。

我找不到解决此问题的可行方法。

【讨论】:

虽然这对开发人员来说是一个可怕的麻烦,但幸运的是这在发布版本中不会发生。 @Rudy 是的,它确实发生在发布版本中。附加调试器时行为会发生变化。至少这是我观察到的。 好的,我阅读了 Dr.Dobbs 的文章,看到了 VC++ 和 GC++ 之间的差异,以及 VC++ 的发布和调试模式之间的差异。不仅仅是麻烦。 10 分钟与几毫秒。 FWIW,我自己无法测试。没有VC++。但我相信 Dr.Dobbs 文章中的数字。 @DavidHeffernan 这是一个环境变量。 Windows 堆管理器启用检查它是否未设置为 1 并且附加了调试器。我在几年前遇到过这种情况,当时我使用了很多 WideStrings 并且应用程序在使用附加的调试器启动时需要很长时间才能终止,并且在以下情况下立即终止没有附加调试器。

以上是关于Delphi:使用调试器调用 C dll 函数需要 15 秒,而没有调试器需要 0.16 秒。为啥?的主要内容,如果未能解决你的问题,请参考以下文章

要从 Delphi 调用的 C++ dll 函数 - 数组参数

delphi 调用 c语言 dll

如何在 Delphi 中调试从 Java 调用的 DLL?

c ++指针将结构新数组指向delphi到DLL函数

DELPHI调用DLL时的回调函数问题

delphi如何调用dll函数