Разнообразные экземпляры в нескольких единицах раздувают исполняемый файл?

Эта статья Embarcadero, в которой обсуждаются проблемы с памятью для XE7 IDE, содержит следующее:

Помните о "росте по дженерикам"

Другой сценарий, который может зависеть от вашего кода приложения и вызвать увеличение памяти, используемой компилятором и отладчиком, связан с тем, как используются общие типы данных. Способ работы компилятора Object Pascal может приводить к генерации множества разных типов на основе одного и того же общего определения, иногда даже полностью идентичных типов, которые скомпилированы в разных модулях. Хотя мы, конечно, не рекомендуем удалять дженерики, напротив, есть несколько вариантов:

  • Старайтесь избегать ссылок на круговые единицы для единиц, определяющих основные типы типов.
  • Определите и используйте те же определения конкретного типа, когда это возможно.
  • Если возможно, генераторы-рефакторики для совместного использования кода в базовых классах, из которых общий класс наследует

Последний элемент, который я понимаю. На первых двух я менее четко понимаю.

Эти проблемы влияют только на производительность IDE или влияет на размер скомпилированного кода?

Например, учитывая второй элемент, если я объявляю TList<Integer> в двух отдельных единицах, я получу два отдельных фрагмента кода в каждом из этих блоков в моем исполняемом файле? Я, конечно, надеюсь, что нет!

Ответ 1

Точка 2. Это означает, что, если это возможно, создается экземпляр такого же родового типа. Например, используя TList<Integer> во всех местах вместо двух общих типов TList<Integer> и TList<SmallInt>.

Объявление и использование TList<Integer> в нескольких единицах будет включать одиночную копию из TList<Integer> в exe файле. Кроме того, объявление TIntegerList = TList<Integer> приведет к тому же.

Люди с общим раздуванием ссылаются на то, что имеют полный экземпляр TList<T> для каждого конкретного типа, который вы используете, хотя базовый сгенерированный код тот же.

Например: TList<TObject> и TList<TPersistent> будут содержать две отдельные копии TList<T>, даже если сгенерированный код может быть сложен на один.

Это приводит нас к пункту 3., где используется базовый класс для обычного кода класса, а затем, используя общие классы поверх этого, чтобы получить безопасность типа, может сохранить вашу память как во время компиляции, так и в exe файле,

Например, создание универсального класса поверх не общего TObjectList будет включать только тонкий общий уровень для каждого конкретного типа, а не полную функциональность TObjectList. Сообщается как 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;

Ответ 2

Раздутый код, о котором они говорят в статье (как об отсутствии проблемы с памятью в среде IDE), связан с генерируемыми DCU и всей метаинформацией, которая хранится в среде IDE. Каждое DCU содержит все используемые дженерики. Только при компиляции вашего бинарного файла компоновщик удаляет дубликаты.

Это означает, что если у вас есть Unit1.pas и Unit2.pas, и оба используют TList<Integer>, оба Unit1.dcu и Unit2.dcu имеют двоичный код для TList<Integer>, скомпилированный в.

Если вы объявите TIntegerList = TList<Integer> в Unit3 и используете это в Unit1 и Unit2, вы можете подумать, что это будет включать только скомпилированный TList<Integer> в Unit3.dcu, но не в двух других. Но, к сожалению, это не так.