Есть ли оптимизация для получения возвращаемого значения в Delphi? - PullRequest
3 голосов
/ 19 января 2010

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

TAilmentP = Record // actually a number but acts like a pointer
private
  Ordinal: Byte;
public
  function Name: String; inline;
  function Description: String; inline;
  class operator Implicit (const Number: Byte): TAilmentP; inline;
End;

 TSkill = Class
   Name: String;
   Power: Word;
   Ailment: TAilmentP;
 End;

class operator TAilmentP.Implicit (const Number: Byte): TAilmentP;
begin
  Result.Ordinal := Number;
  ShowMessage (IntToStr (Integer (@Result))); // for release builds
end;

function StrToAilment (const S: String): TAilmentP; // inside same unit
var i: Byte;
begin
  for i := 0 to Length (Ailments) - 1 do
    if Ailments [i].Name = S then
    begin
      ShowMessage (IntToStr (Integer (@Result))); // for release builds
      Result := i; // uses the Implicit operator
      Exit;
    end;
  raise Exception.Create ('"' + S + '" is not a valid Ailment"');
end;

Теперь я пытался облегчить свою жизнь, перегружая оператор преобразования, так что, когда я пытаюсь назначить байт объекту TAilmentP, он назначает его полю Ordinal. Однако, как я уже проверял, кажется, что эта попытка на самом деле является дорогостоящей с точки зрения производительности, поскольку любой вызов неявного «оператора» создаст новый объект TAilmentP для возвращаемого значения, сделает свое дело, а затем вернет значение и сделайте побайтную копию обратно в вызывающий объект, так как адреса различаются.

Мой код довольно часто вызывает этот метод, если честно, и кажется, что это медленнее, чем просто присвоение моего значения непосредственно полю Ordinal моего объекта.

Есть ли способ заставить мою программу фактически присвоить значение непосредственно моему полю с помощью ЛЮБОГО метода / функции? Даже встраивание не работает. Есть ли способ вернуть ссылку на (запись) переменной, а не сам объект? Наконец (и извините за то, что немного не в тему), почему перегрузка операторов осуществляется с помощью статических функций? Не сделает ли их методы экземпляра более быстрым, поскольку вы можете обращаться к полям объекта, не обращаясь к ним? Это действительно пригодится здесь и в других частях моего кода.

[EDIT] Это код ассемблера для оператора Implicit со всеми оптимизациями и без функций отладки (даже «Отладочная информация» для точек останова).

add al, [eax] /* function entry */
push ecx
mov [esp], al /* copies Byte parameter to memory */
mov eax, [esp] /* copies stored Byte back to register; function exit */
pop edx
ret

Что еще смешнее, так это то, что следующая функция имеет команду mov eax, eax при запуске. Теперь это выглядит действительно полезным. : P О да, и мой неявный оператор тоже не стал встроенным.

Я почти уверен, что [esp] - это переменная Result, так как она имеет адрес, отличный от того, который я назначаю. При отключенной оптимизации [esp] заменяется на [ebp- $ 01] (то, что я назначаю) и [ebp- $ 02] (параметр Byte), добавляется еще одна инструкция для перемещения [ebp- $ 02] в AL (которая затем помещает его в [ebp- $ 01]), и избыточная инструкция mov все еще там с [epb- $ 02].

Я что-то не так делаю или Delphi не имеет оптимизации возвращаемого значения?

Ответы [ 3 ]

3 голосов
/ 19 января 2010

Типы возврата - даже записи - которые поместятся в регистр, возвращаются через регистр. Только более крупные типы внутренне преобразуются в параметры "out", которые передаются в функцию по ссылке.

Размер вашей записи равен 1. Сделать копию вашей записи так же быстро, как сделать копию обычной Byte.

Код, который вы добавили для наблюдения адресов ваших Result переменных, на самом деле причиняет вред оптимизатору. Если вы не запрашиваете адрес переменной, компилятору не требуется выделять для нее память. Переменная может существовать только в регистре. Когда вы запрашиваете адрес, компилятор должен выделить стековую память, чтобы у него был адрес, чтобы дать вам.

Избавьтесь от своего кода "release mode" и вместо этого наблюдайте за работой компилятора в окне CPU. Вы должны быть в состоянии наблюдать, как ваша запись существует в основном в регистрах. Оператор Implicit может даже скомпилироваться до неоперативного состояния, так как оба регистра ввода и вывода должны быть EAX.


Независимо от того, являются ли операторы методами экземпляров или статическими, не имеет большого значения, особенно в плане производительности. Методы экземпляра по-прежнему получают ссылку на экземпляр, к которому они обращаются. Это просто вопрос, имеет ли ссылка имя, которое вы выбираете, или оно называется Self и передано неявно. Хотя вам не нужно было бы писать «Self». перед вашим доступом к полю переменная Self по-прежнему нуждается в разыменовании так же, как параметры метода статического оператора.

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

1 голос
/ 19 января 2010

Delphi должен оптимизировать возвращаемое назначение с помощью указателей. Это также верно для C ++ и других скомпилированных языков ООП. Я прекратил писать Pascal до того, как была введена перегрузка операторов, поэтому мои знания немного устарели. Далее следует то, что я хотел бы попробовать:

Что я думаю, это ... вы можете создать объект в куче (использовать New) и передать указатель обратно из вашего метода "Implicit"? Это должно избежать ненужных накладных расходов, но заставит вас иметь дело с возвращаемым значением в качестве указателя. Перегрузить ваши методы для работы с типами указателей?

Я не уверен, что вы можете сделать это с помощью встроенной перегрузки оператора. Как я уже говорил, перегрузка - это то, что я хотел в Паскале в течение почти десятилетия, и с ним никогда не приходилось играть. Я думаю, что стоит попробовать. Возможно, вам придется смириться с тем, что вы должны убить свои мечты об элегантном наборе типов.

Есть несколько предостережений со вставками. Вы, наверное, уже знаете, что подсказка отключена (по умолчанию) для отладочных сборок. Вы должны быть в режиме релиза, чтобы профилировать / тестировать или изменять свои настройки сборки. Если вы еще не вошли в режим выпуска (или не изменили настройки сборки), скорее всего, ваши встроенные подсказки игнорируются.

Обязательно используйте const для подсказки компилятору для дальнейшей оптимизации. Даже если это не сработает для вашего случая, это хорошая практика, чтобы войти в. Пометка того, что не должно меняться, предотвратит все виды катастроф ... и дополнительно даст возможность компилятору агрессивно оптимизировать.

Чувак, я бы хотел знать, разрешил ли Delphi кросс-юнит, сейчас, но я просто не могу. Многие компиляторы C ++ только встроены в один и тот же файл исходного кода, если вы не поместите код в заголовок (заголовки не имеют корреляции в Паскале). Это стоит поиск или два. Попытайтесь сделать встроенные функции / методы локальными для их вызывающих, если можете. Это по крайней мере поможет время компиляции, если не больше.

Все из идей. Надеюсь, это извилистое помогает.

0 голосов
/ 19 января 2010

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

Я имею в виду случаи, когда возвращаемое значение может потребоваться перераспределить, как, например, вызов функции, которая принимает параметр TAilmentP с байтовым значением ... Я не думаю, что вы можете напрямую назначить параметры функции, поскольку она еще не была создана, и исправление, нарушающее нормальный и установленный способ генерации вызовов функций в ассемблере (т. е. попытка получить доступ к полям параметра до его создания, - нет-нет, поэтому вам придется до этого создайте этот параметр, затем назначьте ему то, что вы должны назначить ВНЕ конструктора, а затем вызовите функцию в ассемблере).

Это особенно верно и очевидно для других операторов (с помощью которых вы можете оценивать выражения и, следовательно, создавать временные объекты), но не так много, поскольку вы думаете, что это похоже на оператор присваивания в других языках (например, в C ++ (который может быть членом экземпляра), но на самом деле это намного больше, чем это - это также конструктор. Например

procedure ShowAilmentName (Ailment: TAilmentP);
begin
  ShowMessage (Ailment.Name);
end;

[...]
begin
ShowAilmentName (5);
end.

Да, неявный оператор тоже может это делать, что довольно круто. : D В этом случае я думаю, что 5, как и любой другой байт, будет преобразован в TAilmentP (как при создании нового объекта TAilmentP на основе этого байта) с заданным неявным оператором, а затем объект будет скопирован побайтно в Параметр Ailment, затем вводится тело функции, выполняет свою работу и при возврате временный объект TAilmentP, полученный в результате преобразования, уничтожается. Это еще более очевидно, если Ailment будет const, поскольку он должен быть ссылочным, а также константным (без изменения после вызова функции).

В C ++ оператор присваивания не имеет отношения к вызовам функций. Вместо этого можно было бы использовать конструктор для TAilmentP, который принимает параметр Byte. То же самое можно сделать в Delphi, и я подозреваю, что он будет иметь приоритет над неявным оператором, однако, что не поддерживает C ++, но делает Delphi, так это имеет понижающее преобразование в примитивные типы (Byte, Integer и т. Д.), Так как операторы перегружены с использованием операторов класса. Таким образом, такая процедура, как «процедура ShowAilmentName (Number: Byte);» никогда не сможет принять вызов типа «ShowAilmentName (SomeAilment)» в C ++, но в Delphi это возможно.

Итак, я полагаю, что это побочный эффект от оператора Implicit, также похожего на конструктор, и это необходимо, поскольку записи не могут иметь прототипов (таким образом, вы не можете преобразовать как один путь, так и другой между двумя записями, просто используя конструкторы). Кто-нибудь еще думает, что это может быть причиной?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...