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

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 моего объекта.

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

[РЕДАКТИРОВАТЬ] Это код ассемблера для неявного оператора со всеми оптимизациями и без функций отладки (даже "Отладочная информация" для точек останова).

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
Cloud737 19 Янв 2010 в 03:42
Если значение Byte однозначно идентифицирует запись, то в вашей программе имеется не более 256 записей. Это не более 512 строк имени и описания. Это ничего . Что вы реально ожидаете получить от всей этой «оптимизации»?
 – 
Rob Kennedy
19 Янв 2010 в 04:23
Я рекомендую вам удалить свой «не связанный» вопрос и опубликовать его как отдельный вопрос. Ответ здесь только отвлечет от ответа на ваш вопрос о записях Delphi.
 – 
Rob Kennedy
19 Янв 2010 в 04:24
Я думаю, что для вашего вопроса было бы полезно добавить еще немного кода, иллюстрирующего реализацию Implicit и пример использования этой записи. Где вы используете неявный оператор? Как вы измерили затраты на производительность?
 – 
Rob Kennedy
19 Янв 2010 в 04:29
Это байтовое значение почти никогда не будет однозначно идентифицировать запись (размер массива записей обычно будет меньше 10), и поэтому такая оптимизация актуальна, поскольку она будет использоваться в других объектах, возможно, даже больше 256. Кроме того, мой вопрос не обязательно связано с моей реализацией, так как я видел и другие случаи, когда я бы тоже хотел его использовать. Я пока оставлю этот не связанный с этим вопрос, но мне очень хочется поставить его в отдельный вопрос. Хорошо, я добавил еще код.
 – 
Cloud737
19 Янв 2010 в 17:42
Стоимость производительности на самом деле не измеряется, я просто предположил, что, поскольку переменные StrToAilment и Implicit Result имеют разные адреса, это означает, что возвращаемое значение Implicit копируется в переменную Result StrToAilment, а не присваивается ей напрямую.
 – 
Cloud737
19 Янв 2010 в 17:44

3 ответа

Лучший ответ

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

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

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

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


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

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

3
Rob Kennedy 19 Янв 2010 в 21:41
Я изучил это (даже с отключенной «Отладочной информацией», то есть без каких-либо точек останова, просто чтобы быть уверенным ... какая это была боль: P), и я вижу, что он все еще пытается что-то скопировать (результат?) , хотя кажется, что конечный результат - довольно бесполезная операция. Вот ассемблерный код для неявного оператора (к сожалению, без форматирования): add al, [eax]; / * запись функции / push ecx; mov [esp], al; / копирует параметр Byte в память / mov eax, [esp]; / копирует сохраненный байт обратно в регистр * / pop edx; ret; Может быть, объект в ESP - это та часть, которую я пытаюсь назначить?
 – 
Cloud737
20 Янв 2010 в 00:54
Что еще смешнее, так это то, что следующая функция при запуске имеет инструкцию mov eax, eax. Теперь это выглядит действительно полезным. : P Ах да, и мой неявный оператор тоже не был встроен. :(
 – 
Cloud737
20 Янв 2010 в 00:55
Я знаю, что вы имеете в виду, говоря о переменной Self и разыменовании ее. Я говорил, что если бы Implicit был методом экземпляра, я мог бы назначить его непосредственно полю, которое я хотел, вместо того, чтобы назначать полю возвращаемое значение, а затем побайтно копировать его в назначенное. В любом случае, я думаю, что Delphi действительно оптимизирует возвращаемое значение, просто я слишком глуп, чтобы это видеть. Я просто кодирую так, как мне удобнее, и позволяю компилятору делать свою работу, не беспокоясь о микрооптимизации, поскольку я уверен, что компилятор достаточно умен.
 – 
Cloud737
20 Янв 2010 в 01:06
Инструкции ассемблера, которые вы цитируете, выглядят довольно наивно. Вы уверены, что у вас включена оптимизация?
 – 
Rob Kennedy
20 Янв 2010 в 02:30
Да, я абсолютно уверен. снова проверяет Ага. Даже встраивание включено. Более того, все параметры отладки отключены. Фреймы стека, утверждения, отладочная информация, локальные символы - все выключено. И я удостоверился, что я создал всю программу в режиме Release перед запуском (как будто даже если я переключу конфигурации сборки, если я не соберу всю программу снова, она просто скомпилируется и запустится в предыдущей конфигурации).
 – 
Cloud737
20 Янв 2010 в 10:16

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

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

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

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

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

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

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

1
pestilence669 19 Янв 2010 в 05:15
2
Встраивание было введено в Delphi 2005 и всегда было перекрестным. Я не вижу в документации никаких предположений о том, что на директиву Delphi inline влияют какие-либо настройки отладки.
 – 
Rob Kennedy
19 Янв 2010 в 05:41
Да, я знаю об этом, но это происходит только в том случае, если вы используете классы, выделенные в куче. Кроме того, насколько я помню, классы C ++ выделяются стеком, что означает отсутствие оптимизации для возвращаемых назначений. Я мог бы создать указатель, как вы говорите, сделав TAilmentP самим классом, но тогда не было бы смысла использовать неявный оператор, поскольку этот класс фактически будет указателем, а не "имитирующим".
 – 
Cloud737
19 Янв 2010 в 17:56
Я проверил, и вы правы, встраивание отключено в режиме отладки (режим выпуска, если все в порядке), хотя я не уверен, что это значение по умолчанию, поскольку я изменил параметры в конфигурациях сборки, включая базовую. Угадайте, поэтому компилятор предупреждал меня о проблемах с встраиванием только в режиме Release, хотя он должен был сделать это раньше, в любом случае. В любом случае, я переключился в режим выпуска и распечатал адреса возвращаемых значений (отредактировал свой вопрос, чтобы включить код), и, конечно же, они все еще разные.
 – 
Cloud737
19 Янв 2010 в 18:04
Delphi позволяет встраивать кросс-юниты, я только что использовал это раньше в этом проекте. Существуют некоторые ограничения, например, невозможность иметь встроенные функции в части интерфейса, которая использует встроенные функции в части реализации, или иметь встраивание между циклическими модулями (теми, которые используют друг друга в предложении uses), но обычно это работает хорошо. . Кстати, я думаю, что Delphi-эквивалент заголовков - это «интерфейсная» часть модуля. Тем не менее, весь опубликованный мной код находится в одном и том же модуле.
 – 
Cloud737
19 Янв 2010 в 18:09
Что касается использования константных параметров, то я об этом забыл. Я обычно использую его, особенно на объектах (классах) кучи. Кстати говоря, не являются ли константные параметры на самом деле константными ссылками, что означает, что они реализованы как указатели в ассемблере? Разве это не добавит накладных расходов, если все, что вы хотите передать, - это Byte или SmallInt? Компенсируют ли оптимизации, которые делает компилятор, накладные расходы?
 – 
Cloud737
19 Янв 2010 в 18:12

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

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

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

0
Cloud737 19 Янв 2010 в 20:11
1
Вы ошибаетесь здесь по нескольким пунктам. C ++ может "преобразовывать с понижением", как вы это называете, предоставляя операторы преобразования. Delphi может преобразовывать "в обе стороны" между двумя типами записей: stackoverflow.com/questions/770809 Если вы хотите наблюдать, когда вызываются конструкторы и неявные операторы, устанавливать точки останова или выводить сообщения трассировки; не надо просто гадать, что произойдет, а что не произойдет. Я не знаю, о чем вы говорите относительно «прямого присвоения параметрам функции».
 – 
Rob Kennedy
19 Янв 2010 в 21:50
Привет спасибо! До сих пор я не знал, что в C ++ есть операторы преобразования. Что касается Delphi, я знаю, что он может конвертировать в обоих направлениях, это в основном то, что я (пытался?) Говорил раньше. И забудьте об остальной части сообщения, это было только мое предположение, так как я не видел другого ответа. Я знал, что публиковать это - плохая идея, но я узнал, что в C ++ есть операторы преобразования! : D
 – 
Cloud737
19 Янв 2010 в 23:52