Разнообразные экземпляры в нескольких единицах раздувают исполняемый файл?
Эта статья 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
, но не в двух других. Но, к сожалению, это не так.