8

I have the following setup:

  1. A Delphi command line application written in Delphi XE5 and built in Debug 64 bit.
  2. A C dll written in Microsoft Visual Studio 2013 and built in Release 64 bit.
  3. The Delphi command line application calls a function in the C dll.

What's unexpected:

  1. When debugging the Delphi command line application within the Delphi XE5 IDE the C dll function call takes 15 s.
  2. When launching the same Delphi command line application directly (without IDE, without Debugger) then the C dll function call takes 0.16 s.

Delphi command line application source code:

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 source code:

#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 soure code: DelphiCpplibraryCall.zip

Runtime comparison:

  • First console: When debugging in IDE
  • Second console: When launching without IDE

Runtime comparison

For some reason the the Delphi Debugger seems to slow down the C dll function call a lot which makes debugging nearly impossible.

Has anyone a clue what could case this issue or how to avoid it? Many thanks.

Edit: I can confirm now that the described behavior is not restricted to the Delphi IDE and Debugger at all. This problem also happens if I:

  1. I build the C dll in Microsoft Visual Studio 2013 in Release.
  2. And build and debug the command line executable that calls the C dll in Visual Studio 2013.

This means that the C dll release build function execution time changes depending on whether there is a debugger attached or not.

I can also confirm that it is the deletion of the unordered_map (delete values;) that takes that long as soon as there is a debugger present.

denim
  • 1,329
  • 1
  • 15
  • 24
  • Your DLL is actually implemented in C++. My guess is that the C++ runtime detects the presence of a debugger and adds extra self checking of memory access, bounds checking etc. Have you tried a release build of the DLL? Your C++ code is also very inefficient. – David Heffernan Aug 27 '15 at 13:33
  • Actually, I see that you are compiling release build of DLL. That probably makes this a Delphi problem. The delphi x64 debugger is terrible. I personally have given up on it because it is so useless. Debug the code in 32 bit. – David Heffernan Aug 27 '15 at 13:36
  • @DavidHeffernan As described the C dll is already a release build. The C dll function implementation perfectly shows the runtime difference please ignore the inefficient code. – denim Aug 27 '15 at 13:38
  • @DavidHeffernan I should have mentioned that I have already tried the Delphi 32 bit debugger. You are right the problem is not that bad with the 32 bit Delphi debugger but still the runtime is terrible long. – denim Aug 27 '15 at 13:40
  • I'll try and repro. See if that sheds any light. Presumably you've cut this down from a real project and isolated unordered_map. And btw, great work asking the question. Good to see such a clean example. – David Heffernan Aug 27 '15 at 13:41
  • @DavidHeffernan Thanks a lot! Yes it is cut down from a real project which is indeed much more complex. But I could easily reproduce the problem with this small sample. – denim Aug 27 '15 at 13:44

2 Answers2

6

If the delay comes from the delete call then it may be the memory manager (malloc) that uses the Windows heap memory manager. The heap memory manager executes additional extensive checks when memory is released and a debugger is attached.

These additional checks can be disabled by setting the environment variable _NO_DEBUG_HEAP to 1 (starts with an underscore).

You can do this in Delphi for a specific project under Project/Options.../Debugger/Environment Block or you can add _NO_DEBUG_HEAP to the user's environment block under Control Panel/System Properties/Advanced System Settings/Environment Variables/System Variables so it works for all projects and all applications. But then you may need to logout to apply the changes or at least restart the IDE.

Delphi Project Options Environment Block

Andreas Hausladen
  • 8,081
  • 1
  • 41
  • 44
  • Thank you for your detailed answer. This actually solved the problem for me. The Release build of my C dll now also runs fast when debugging the command line application inside the Delphi IDE. But I set the variable in the Windows environment variable settings. Furthermore I also found [The Windows Heap Is Slow When Launched from the Debugger](http://preshing.com/20110717/the-windows-heap-is-slow-when-launched-from-the-debugger/) which exactly describes the problem. – denim Aug 28 '15 at 06:08
3

This appears to be an issue with the MSVC implementation of this STL container. Exactly the same behaviour can be seen when you use the Visual Studio debugger. The MSVC runtime is switching behaviour when it detects a debugger.

I've found the following links that relate to this issue:

A large part of the problem appears to be the performance when clearing the map. Simply remove the delete line from your C++ code to see much improved performance.

I cannot find a viable work around to this problem.

Community
  • 1
  • 1
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • While it is a terrible nuisance for the developer, fortunately this does not happen in the Release build. – Rudy Velthuis Aug 27 '15 at 16:24
  • 1
    @Rudy Yes it does happen in the release build. The behaviour changes when a debugger is attached. At least that's what I observe. – David Heffernan Aug 27 '15 at 16:31
  • 1
    OK, I read the Dr.Dobbs article and saw the differences between VC++ and GC++ and also between VC++'s release and debug mode. Much more than a nuisance. 10 minutes vs. a few milliseconds. – Rudy Velthuis Aug 27 '15 at 16:36
  • FWIW, I can't test this myself. No VC++. But I trust the numbers in the Dr.Dobbs article. – Rudy Velthuis Aug 27 '15 at 16:38
  • If it is the `delete` then it may help to set the environment variable "_NO_DEBUG_HEAP" = "1" so that the heap memory manager doesn't do extra checks. – Andreas Hausladen Aug 27 '15 at 16:58
  • @AndreasHausladen I think it's more to do with the particular collection, the unordered map, than the actual memory management. That's the sense I get anyway. – David Heffernan Aug 27 '15 at 17:03
  • @DavidHeffernan Thank you for investigating the problem and for all the links. I can definitely confirm your findings. – denim Aug 27 '15 at 19:16
  • @AndreasHausladen What do you mean by setting the environment variable "_NO_DEBUG_HEAP" = "1". Where am I supposed to set it? Can you please provide any reference or further details? – denim Aug 27 '15 at 19:19
  • @andreas surely that's a compiler define? Or is it really an env var? – David Heffernan Aug 27 '15 at 20:18
  • 1
    @DavidHeffernan It's an environment variable. The Windows heap manager enables checks if it isn't set to 1 and a debugger is attached.I ran into this years ago when I used a lot of WideStrings and the application took ages to terminate when started with an attached debugger and terminated instantaneous if no debugger was attached. – Andreas Hausladen Aug 27 '15 at 21:17
  • @denim You can either set it in the user or global environment list (Control Panel->System Properties->Advanced System Settings->Environment Variables->System Variables) or you can set it in the Delphi project options under "Debugger - Environment variables". If you choose the first, you may need to logout and login to apply the change. – Andreas Hausladen Aug 27 '15 at 21:23