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!
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.
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;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With