(or the hack that wasn't). You may be getting less than you bargained for in your Delphi 8 compiled assemblies and executables, due to the seemingly hard-coded setting for the Debuggable attribute in the dccil compiler.
.NET and the CLR define a DebuggableAttribute with two boolean constructor parameters and corresponding properties, named IsJITTrackingEnabled and IsJITOptimizerDisabled. When present in an assembly the CLR's JITer will modify its machine code generation strategies to better suit debugging needs.
IsJITTrackingEnabled = True means that the JITer spends time and memory during the just-in-time compilation process to generate tables that makes it easier for a debugger (mapping code-addresses back to source line number information, for instance).
IsJITOptimizerDisabled = True means, well, just that - you'll get easier-to-debug machine code, but the code will perform worse and/or be larger than the JIT optimizer otherwise could produce.
The trouble with D8 is that dccil seems to be hard-coded to always emit the DebuggableAttribute with the isJITOptimizerDisabled parameter = True. If you compile from the IDE, and turn off the integrated debugger and turn off output of the .PDB file from the Linker options, dccil can be persuaded to output DebuggableAttribute(False, True). But no matter how many different compiler options or command line tweaks I’ve tried, I’ve not been able to make the compiler output DebuggableAttribute(False, False) or no attribute at all.
IMO, the setting of the compiler Optimization checkbox should control the isJITOptimizerDisabled parameter, but it currently doesn’t. To see the parameter values of this attribute in your assembly or EXE file, use Lutz’ Reflector instead of ILDAsm. ILDAsm doesn't show the actual value of this attribute, due to the way ILAsm works when applying the attribute (from command line parameters). Reflector gives a better picture.
We need a little piece of test code to see how the settings of this attribute actually influence the runtime behaviour of CPU-intensive code. Thanks to pp-2ch from the newsgroups for the initial version of this mock-up code:
program TestDebuggable;
{$APPTYPE CONSOLE}
function QueryPerformanceCounter(out lpPerformanceCount: Int64): LongBool; external 'kernel32.dll';
var
StartCount, EndCount: Int64;
i, j, k: Integer;
begin
QueryPerformanceCounter(StartCount);
i := 0;
while i < 1000000 do
begin
k := ((i*2)+2);
i := k div 2;
dec(i);
j := i;
inc(j);
i := j;
end;
QueryPerformanceCounter(EndCount);
Writeln(Convert.ToString(EndCount-StartCount));
Readln;
end.
Compile this from the IDE or command line with default settings, and load the assembly in Reflector. Enable the display of Custom Attributes. Look at the TestDebuggable node and notice the value of the Debuggable attribute in the lower pane. It says Debuggable(true, true). Now run the test project and notice the runtime ticks.
On my machine it clocks in at around 42000 ticks. The initial (but less than ideal) hack-around would be to explicitly include the Debuggable attribute in the project file. DebuggableAttribute is defined in the System.Diagnostics namespace, se we need to add that to the uses clause. And it can only be applied to assemblies and modules, so we must prefix it with an attribute target specifier, like this:
uses
System.Diagnostics;
[assembly: Debuggable(false, false)]
If you compile and run this from the IDE, make sure you turn off the integrated debugging (Tools | Options | Debugger Options | [ ] Integrated Debugging). Refresh the list of assemblies in Reflector, just press F5 (I love this new feature). Now you will see two Debuggable attributes, and depending on how (un)lucky you are, you will see them in this order: Debuggable(false, false), Debuggable(true, true).
Run the test code again. On my machine, it now clocks in at an impressing 13000, shaving off 70% of the original run time, so I was lucky
If we look closely at the documentation for DebuggableAttribute and specifically for the IsJITOptimizerDisabled parameter, we’ll see that they say:
“The runtime … might disable certain optimizations based on the values contained within this attribute.”Notice the word “might”. A little more experimentation has shown that (in the current V1.1 CLR) you will in fact get the JIT optimized code we’re after, if you:
- Don’t run from a debugger (disable the IDE debugger or run from the command line)
- Use Debuggable(false, true) (turn off the generation of the .PDB file)
So just make sure you compile without enabling the PDB file (Project | Options | Linker | [ ] Generate .PDB debug info file), or from the command-line use the –V- option. Now the compiler will emit Debuggable (False, True). Now Reflector only shows one instance of the Debuggable attribute (assuming you reverted the failed hack above). And the code runs fast from the IDE (with debugging disabled) and from the command line.
Even if you compile with the IDE debugger enabled, the code will still run fast when running standalone. So the hack wasn’t really needed – just use the right options before compiling the release version (turning off the external debug file), and you will get optimized JITed code. Notice that deleting the .PDB file after compiling has no effect. It is the value of the first parameter to DebuggableAttribute that matters, and the same compiler option controls both.
Hallvard, great article.
ReplyDeleteI'm sure that was in response to some article we've read somewhere together. ;o)
Chee Wee.
There is actually no need to disable debugging in the IDE. You can simply run the executable from the
ReplyDeleteRun | Run without debugging
menu item, and it will not debug either, and thus give you the same speed benefit. The inconvenience is, that there seems to be no shortcut for this item (I would propose Alt+F9).
You must still disable the generation of the .PDB file though, in the Linker options.
Rudy Velthuis
Thanks, Rudi.
ReplyDeleteYou're right - I forgot about that menu option.
> You must still disable the generation of the .PDB file
Yes, this is the critical part.
Aaargh! You did it again. You called me Rudi. Now I'll have to call you Halvard.
ReplyDelete<vbg>
--
Rudy Velthuis
I wonder if Borland did this when writing the IDE...?
ReplyDeleteGood question!
ReplyDeleteThe main IDE executable, bds.exe, is in fact a plain unmanaged, native Win32 executable, compiled using an internal version of Delphi 7. Most of the design-time packages are also native. The IDE does load and host the CLR in its process, and loads a number of managed .NET assemblies in one or more AppDomains. I looked at one of these assemblies (Borland.Vcl.Design.Standard) with Reflector and it did have the Debuggable(False, True) attribute, effectivly enabling the JIT Optimizer.