Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Do generic instantiations in multiple units bloat the executable?

Tags:

delphi

This Embarcadero article discussing memory issues for the XE7 IDE contains the following:

Be aware of the “Growth by Generics”

Another scenario that might depend on your application code and cause an increase of the memory used by the compiler and the debugger relates with the way generic data types are used. The way the Object Pascal compiler works can cause the generation of many different types based on the same generic definition, at times even totally identical types that are compiled in different modules. While we won’t certainly suggest removing generics, quite the contrary, there are a few options to consider:

  • Try to avoid circular unit references for units defining core generic types
  • Define and use the same concrete type definitions when possible
  • If possible, refactor generics to share code in base classes, from which a generic class inherits

The last item I understand. The first two I am less clear on.

Do these issues affect only IDE performance, or is there an impact on the size of the compiled code?

For instance, considering the second item, if I declare TList<Integer> in two separate units, will I get two separate chunks of code in each of those units in my executable? I certainly hope not!

like image 478
David Heffernan Avatar asked Jul 28 '15 18:07

David Heffernan


2 Answers

The code bloat they are talking about in the article (as it is about the out of memory issue in the IDE) is related to the generated DCUs and all the meta information that is held in the IDE. Every DCU contains all the used generics. Only when compiling your binary the linker will remove duplicates.

That means if you have Unit1.pas and Unit2.pas and both are using TList<Integer> both Unit1.dcu and Unit2.dcu have the binary code for TList<Integer> compiled in.

If you declare TIntegerList = TList<Integer> in Unit3 and use that in Unit1 and Unit2 you might think this would only include the compiled TList<Integer> in Unit3.dcu but not in the other two. But unfortunately that is not the case.

like image 161
Stefan Glienke Avatar answered Oct 17 '22 15:10

Stefan Glienke


Point 2. This refers to instantiating same generic type where possible. For instance using TList<Integer> in all places instead of having two generic types TList<Integer> and TList<SmallInt>.

Declaring and using TList<Integer> in several units will only include single copy of TList<Integer> in exe file. Also, declaring TIntegerList = TList<Integer> will result with same.

Generic bloat people are referring to relates to having complete TList<T> copy for each specific type you use even though underlying generated code is the same.

For instance: TList<TObject> and TList<TPersistent> will include two separate copies of TList<T> even though generated code could be folded to single one.

That moves us to Point 3. where using base class for common class code and then using generic classes on top of that to get type safety, can save you memory both during compilation and in exe file.

For example, building generic class on top of non generic TObjectList will only include thin generic layer for each specific type instead of complete TObjectList functionality. Reported as QC 108966

  TXObjectList<T: class, constructor> = class(TObjectList)
  protected
    function GetItem(index: Integer): T;
    procedure SetItem(index: Integer; const Value: T);
  public
    function Add: T;
    property Items[index: Integer]: T read GetItem write SetItem; default;
  end;

function TXObjectList<T>.GetItem(index: Integer): T;
begin
  Result := T( inherited GetItem(index));
end;

procedure TXObjectList<T>.SetItem(index: Integer; const Value: T);
begin
  inherited SetItem(index, Value);
end;

function TXObjectList<T>.Add: T;
begin
  Result := T.Create;
  inherited Add(Result);
end;
like image 33
Dalija Prasnikar Avatar answered Oct 17 '22 16:10

Dalija Prasnikar