Jump to content
Tommi Prami

(FYI) Delphi can't optimize out unneeded inheritance

Recommended Posts

I was curious does the compiled optimize out inhetiance that is needlesly added. Turns out it can't.

Is there any technical reason that this is not possible, how Delphi classes work, or it is just missing optimization  from Delphi compiler.,

My point here is that, if you care, remove unneeded in heritance from ytou code base. Effect is very small, most likely not even measurable, but do as you please.


image.thumb.png.dfb685080aee7bb800a57bf770f9f471.png


In very tight loopå this might be porblem, most cases not. Why someone would add those, is different discussion. Sometimes I add overridden method, because I think I need to do something in there, but I just forget to remove it later.

Test project:

unit Unit27;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
  TForm27 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

  TBaseClass = class(TObject)
  strict private
    FList: TStringList;
  public
    constructor Create;
    destructor Destroy; override;
  end;

  TFoo1 = class(TBaseClass)
  strict private
  public
    constructor Create;
    destructor Destroy; override;
  end;

  TFoo2 = class(TFoo1)
  strict private
  public
    constructor Create;
    destructor Destroy; override;
  end;

  TFoo3 = class(TFoo2)
  strict private
  public
    constructor Create;
    destructor Destroy; override;
  end;

  TFoo4 = class(TFoo3)
  strict private
  public
    constructor Create;
    destructor Destroy; override;
  end;

  TFoo5 = class(TFoo4)
  strict private
  public
    constructor Create;
    destructor Destroy; override;
  end;

  TFoo6 = class(TFoo5)
  strict private
  public
    constructor Create;
    destructor Destroy; override;
  end;

  TFoo7 = class(TFoo6)
  strict private
  public
    constructor Create;
    destructor Destroy; override;
  end;

  TFoo8 = class(TFoo7)
  strict private
  public
    constructor Create;
    destructor Destroy; override;
  end;

  TFoo9 = class(TFoo8)
  strict private
  public
    constructor Create;
    destructor Destroy; override;
  end;

  TFoo10 = class(TFoo9)
  strict private
  public
    constructor Create;
    destructor Destroy; override;
  end;

var
  Form27: TForm27;

implementation

{$R *.dfm}

uses
  System.Diagnostics;

procedure TForm27.Button1Click(Sender: TObject);
const
  LOOP_COUNT = 1_000_000;
var
  LSW: TStopwatch;
begin
  LSW := TStopwatch.StartNew;
  for var I := 1 to LOOP_COUNT do
  begin
    var LObject := TBaseClass.Create;
    LObject.Free;
  end;
  LSW.Stop;
  Memo1.Lines.Add('BaseClass: ' + FormatFloat('0.00', LSW.Elapsed.TotalMilliseconds) + ' ms');

  LSW := TStopwatch.StartNew;
  for var I := 1 to LOOP_COUNT do
  begin
    var LObject := TFoo10.Create;
    LObject.Free;
  end;
  LSW.Stop;
  Memo1.Lines.Add('unneeded inheritance: ' +  FormatFloat('0.00', LSW.Elapsed.TotalMilliseconds) + ' ms');
end;

procedure TForm27.FormCreate(Sender: TObject);
begin

end;

{ TBaseClass }

constructor TBaseClass.Create;
begin
  inherited Create;

  FList := TStringList.Create;
end;

destructor TBaseClass.Destroy;
begin
  FList.Free;

  inherited Destroy;
end;

{ TFoo9 }

constructor TFoo9.Create;
begin
  inherited Create;
end;

destructor TFoo9.Destroy;
begin

  inherited Destroy;
end;

{ TFoo1 }

constructor TFoo1.Create;
begin
  inherited Create;

end;

destructor TFoo1.Destroy;
begin

  inherited Destroy;
end;

{ TFoo2 }

constructor TFoo2.Create;
begin
  inherited Create;
end;

destructor TFoo2.Destroy;
begin

  inherited Destroy;
end;

{ TFoo3 }

constructor TFoo3.Create;
begin
  inherited Create;

end;

destructor TFoo3.Destroy;
begin

  inherited Destroy;
end;

{ TFoo4 }

constructor TFoo4.Create;
begin
  inherited Create;

end;

destructor TFoo4.Destroy;
begin

  inherited Destroy;
end;

{ TFoo5 }

constructor TFoo5.Create;
begin
  inherited Create;

end;

destructor TFoo5.Destroy;
begin

  inherited Destroy;
end;

{ TFoo6 }

constructor TFoo6.Create;
begin
  inherited Create;

end;

destructor TFoo6.Destroy;
begin

  inherited Destroy;

end;

{ TFoo7 }

constructor TFoo7.Create;
begin
  inherited Create;

end;

destructor TFoo7.Destroy;
begin

  inherited Destroy;
end;

{ TFoo8 }

constructor TFoo8.Create;
begin
  inherited Create;

end;

destructor TFoo8.Destroy;
begin

  inherited Destroy;
end;

{ TFoo10 }

constructor TFoo10.Create;
begin
  inherited Create;

end;

destructor TFoo10.Destroy;
begin

  inherited Destroy;
end;

end.

 

Edited by Tommi Prami
Added TRUE reson for message

Share this post


Link to post
17 minutes ago, David Heffernan said:

The reason this is not optimised away is that it would take development effort to do so but would bring no benefit to anyone. 

True, Benefit would be very small.

Share this post


Link to post

OT: I know that they shouldn't be compared but when I see the kind of efforts done to optimize many aspects of .NET versions after versions, giving it a very positive image to many (younger and older) developers, I can't see how Delphi could survive once most of us retire if it doesn't bring "sexy" optimizations and promote them.

I don't say that this particular one should be implemented but would it be that costly for EMB to hire a full-time developer whose sole task would be to improve performances here and there, and actually blog about it, instead of hiding everything until release for whatever reason ? I believe that this could create a lot of positive feedback and could attract developers intrigued by this "new" Delphi language that they've never heard about 😉

  • Like 1
  • Sad 1

Share this post


Link to post
2 hours ago, John R. said:

I can't see how Delphi could survive once most of us retire if it doesn't bring "sexy" optimizations and promote them.

[...] would it be that costly for EMB to hire a full-time developer whose sole task would be to improve performances here and there, and actually blog about it, instead of hiding everything until release for whatever reason ?

Telling (the right) people about Delphi and how good it is (or rather: was), was something that has not been done since the very beginning. Not even Borland managed that. They advertised in Development magazines (and later websites), but not where those people who make decisions about the money to buy development tools (the C*Os of companies) would read about it. So when a developer asked his boss for the money to buy Delphi related tools, they had to tell him what it is (and who Borland, later Embarcadero is) and what the value would be for the money, because they had never heard these names.

 

It's even worse nowadays: We see Embarcadero endlessly blubbing on about how great their tools are to developers, but not going into specifics. That's the way you talk to the C*Os of companies, not to developers. But the C*O people do not read these posts because they just don't visit embarcadero.com and the Delphi blogs. So so they still haven't heard these names when it comes to investment decisions. And the developers don't read them because they don't get any interesting information from them.

Edited by dummzeuch
  • Sad 1

Share this post


Link to post

I am all for better optimization performed by the compiler but this particular case is not among them - if you write empty methods then use some static code analysis to let them be detected and remove them.

Optimizing out empty methods (and here we are not talking about some regular methods but ctor and dtor!) is something that most compilers don't do for various reasons.

 

Only when methods get inlined (which is not possible for virtual methods unless devirtualization is possible but that's an entirely different story) the compiler can detect that there actually is nothing to (and calling inherited is not nothing) and in fact the Delphi compiler does that - but ctor and dtor can not be inlined.

 

The case you show here gets more interesting though if you remove all the empty ctors and dtors because then I still see a slight difference of 10-15%.

The reason for that is TObject.InitInstance

 

P.S. Just FYI if I run the benchmark without the empty ctor and dtor in Delphi 12 release config I get these numbers:

BaseClass: 106,01 ms
unneeded inheritance: 119,40 ms

The same code compiled in Delphi 10.1 gives these numbers - so much about "there is no progress on optimization":

BaseClass: 156,23 ms
unneeded inheritance: 175,53 ms

Although these particular differences are most likely because of the following two improvements I provided:

Edited by Stefan Glienke
  • Like 2

Share this post


Link to post
On 2/7/2025 at 12:43 PM, Stefan Glienke said:

I am all for better optimization performed by the compiler but this particular case is not among them - if you write empty methods then use some static code analysis to let them be detected and remove them.

For sure. I just thought (or hoped) that this kind of unneeded stuff would be optimized out quite from the start.

Curiocity came from, that I stumbled upon that kind of code, not that deep for sure as my example, just wanted to measure, and report the results here.

-Tee-

  • Like 1

Share this post


Link to post
On 2/7/2025 at 12:43 PM, Stefan Glienke said:

P.S. Just FYI if I run the benchmark without the empty ctor and dtor in Delphi 12 release config I get these numbers:


BaseClass: 106,01 ms
unneeded inheritance: 119,40 ms

The same code compiled in Delphi 10.1 gives these numbers - so much about "there is no progress on optimization":


BaseClass: 156,23 ms
unneeded inheritance: 175,53 ms

 

That is quite nice. Thanks for info.

  • Like 1

Share this post


Link to post
On 2/7/2025 at 12:43 PM, Stefan Glienke said:

Although these particular differences are most likely because of the following two improvements I provided:

Any optimization that makes Object/Recor "init/deinit" faster is surely good optimization. These will make almost mall apps faster.

Other good candidates would be TList, TStrings, TDataSet  (and descendants), just on top of my head. All in those would help in many places.

Surely there are tons of places in RTL thatb would need some love...

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×