Delphi’s 64-bit Compiler Fails to Perform Even the Most Basic Optimizations

Ever wonder why your 64-bit Delphi Apps run so slowly?  I’ve found that my 64-bit performance is roughly half what my 32-bit performance is.

Delphi’s 64-bit compiler basically performs no optimizations at all in many or even most situations.     

This blog has come together in a round-about sort of way.  I had originally posted it making a claim that the Delphi 64-bit compiler performs no optimizations at all, but comments to the contrary led me to find that the real problem was far more complex and finicky than I originally had observed.  I removed the original blog post while I investigated why my findings didn’t match up with some of my readers’ comments and now I have reposted it with some almost equally shocking findings.

I have a bit of code that needs to be 64-bit (allocates many GB of RAM) and also needs to be super fast and optimized.  I was shocked yesterday when I set out to try and figure out why my throughput was settling around 60MB/sec on average when I expected it to run around 200MB/sec or more.    I concentrated on two functions that 99% of all the data went through, the “WriteData” and “ReadData” requests.   These two functions were fairly complex and I wanted to see if I could change the orders of some calls, introduce new local variables, and triple check that there was appropriate use of inline functions and assembler when beneficial.

I cracked it open in disassembly mode.  I was immediately shocked to find all kinds of redundant instructions, sometimes repeatedly just loading the same constant into the same register over and over for no real reason.

I did more investigation to find that the EXE generated with $O+ was identical in size to the EXE that is created with $O-.

I did some more prodding and added instructions into my project that served no purpose whatsoever.

var
  useless: nativeint;
  //...etc...
begin
  useless := 0;
  //...etc...
end;

I did this with the purpose of determining whether the optimizer was running at all and to my shock, initially it appeared as if there was basically no optimization in my 64-bit application.

I posted a blog with my findings in anger, showing all the evidence and screen-captures, until one reader responded that he was not seeing the behavior that I was reporting.

I immediately went searching for answers.  Was it a difference between XE5 and XE6?  Were my project settings corrupt?  Did I need to rebuild my dproj from scratch?   What if I start a new, simple app with just a few lines of code, would it optimize then?

Eventually, through some kicking and prodding, I found out the secret formula and, not to sound like a BuzzFeed post, the answer may shock you.

The following code does NOT optimize.

procedure TForm1.Button1Click(Sender: TObject);
var
  i,a,b,c,d,e,f,g,h: nativeint;
begin
  a := 2;
  b := 3;
  c := 4;
  d := 5;
  e := 6;
  f := 7;
  g := 8;
  h := 9;
  i := 0;
  i := 666;
  try
    TButton(sender).caption := inttostr(i);
  finally
    application.Title := 'stupid delphi';
  end;

end;

…yet… the following code DOES….

procedure TForm1.Button1Click(Sender: TObject);
var
  i,a,b,c,d,e,f,g,h: nativeint;
begin
  a := 2;
  b := 3;
  c := 4;
  d := 5;
  e := 6;
  f := 7;
  g := 8;
  h := 9;
  i := 0;
  i := 666;
//  try
    TButton(sender).caption := inttostr(i);
//  finally
    application.Title := 'stupid delphi';
//  end;

end;

At least I found that it supports the “inline” keyword and uses it… but that’s hardly a consolation prize at this point.   This is another shameful stain on the Delphi product line!

I think it truly is time to get away from this language.  I intend to build a Delphi to C++ compiler that takes Delphi code and generates a single C++ file that can be compiled with all the optimization your heart desires.  If you want to join my project… get in touch with me.

 

4 Replies to “Delphi’s 64-bit Compiler Fails to Perform Even the Most Basic Optimizations”

  1. The Delphi compiler does everything right and acts in this case exactly as it should act!

    You just confused TRY/Finally and TRY/Except! )))
    So You must write ‘stupid Tundra’ insead of ‘stupid delphi’!

    For example:

    procedure TForm1.Button1Click(Sender: TObject);
    var
    i : integer;
    begin
    i := 666;
    try
    (Sender as TButton).Caption := IntToStr(i div (i-666)); // or …div (i-665)
    except
    Caption := ‘stupid Tundra’ + IntToStr(i);
    end;

    1. Not sure what you’re talking about. There is no exception thrown.

      The Compiler loads values into variables that are never even used. Of all the variables that were initialized, the only one that was NOT dead code was i := 666;
      – i := 0; is a value that was never used.
      – a – h are variables that are never used.
      All that code should be eliminated by the compiler.

      The 32-bit compiler (unlike the 64-bit compiler) will generate hints for these things and eliminate them automatically. Is it just bad practice for them to be there in the first place? Well, yes, maybe, most of the time…. But there are situations where I used to rely on the optimizer to throw them out, usually when they’re used for purposes of things like debug logs and Delphi has no MACROS.

      I’ve also found that the Delphi compiler loves to load the same value into the same register over and over and over and over… particularly the value of “self”. IMO the compiler should already know the value of SELF and what register it resides, and whether that register is dirty… therefore loading “SELF” into a register where SELF already resides is a complete waste of CPU. Might as well just put NOOPs everywhere. I don’t think you actually disassembled the code sir. Do you work for Embarcadero QA?

    1. I think Ada Lovelace has a point with the compiler inefficiencies, though. Optimization isn’t just about managing exceptions—it’s about clean, efficient code generation. The fact that variables which aren’t utilized still get processed is a bit of a head-scratcher. It feels like the 64-bit compiler needs a serious tune-up if it’s doing unnecessary work like that. That’s not what you’d expect from a modern compiler at all.

  2. Not sure what you’re talking about. This isn’t about try except vs try finally. There was no exception thrown. The Compiler loaded values into variables that were never even used. Of all the variables that were initialized, the only one that was NOT dead code was i := 666;

    i := 0; is a value that was never used.
    a – h are variables that are never used. The 32-bit compiler will generate hints for these things and eliminate them automatically. Is it just bad practice for them to be there in the first place? Well, yes, maybe, most of the time…. But there are situations where I used to rely on the optimizer to throw them out, usually when they’re used for purposes of things like debug logs.

    I’ve also found that the Delphi compiler loves to load the same value into the same register over and over and over and over… particularly the value of “self”. IMO the compiler should already know the value of SELF and what register it resides, and whether that register is dirty… therefore loading “SELF” into a register where SELF already resides is a complete waste of CPU. Might as well just put NOOPs everywhere. I don’t think you actually disassembled the code sir. Do you work for Embarcadero QA?

Leave a Reply to Ada Lovelace Cancel reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.