Возврат результата через out-параметр

Вопросы программирования на Free Pascal, использования компилятора и утилит.

Модератор: Модераторы

Возврат результата через out-параметр

Сообщение S!V » 12.03.2008 23:19:08

Здравствуйте. Спрошу вашего совета.
Только сейчас заметил небольшую особенность. Следующая процедура
Код: Выделить всё
type TVec4 = array[0..3] of Single;
procedure VAdd(const v1,v2 :TVec4; out Result :TVec4);
begin
  Result[0] := v1[0]+v2[0];
  Result[1] := v1[1]+v2[1];
  Result[2] := v1[2]+v2[2];
  Result[3] := v1[3]+v2[3];
end;

работает быстрее примерно в 3 раза, чем
Код: Выделить всё
operator + (const v1,v2 :TVec4) :TVec4;
begin
   {код тот же самый}
end;

А у меня уже есть куча кода, использующего перегруженные операторы такого типа. Падение скорости в 3 с лишним раза совсем никчему. Так вот сам вопрос: можно ли как-нибудь этого избежать, не переделывая весь код? И, если сейчас нельзя, то может ли это быть поправлено в следующей версии FPC?
Спасибо.
S!V
незнакомец
 
Сообщения: 6
Зарегистрирован: 07.11.2006 23:31:21

Сообщение trifon » 19.03.2008 15:32:25

В таких вопросах надо приводить код теста полностью.
trifon
постоялец
 
Сообщения: 135
Зарегистрирован: 24.12.2006 12:08:35

Сообщение Attid » 19.03.2008 15:51:01

ну вот все ждут когда сам догадается, а ты тут взял и выложил =)


ЗЫ чтобы не было офтопом совет сделать програмку которую можно запустить и будет вывод аля

Код: Выделить всё
10,00 запуск
10,01 первый вариаант
10,10 второй вариант
Аватара пользователя
Attid
долгожитель
 
Сообщения: 2586
Зарегистрирован: 27.10.2006 17:29:15
Откуда: 44°32′23.63″N 41°2′25.2″E

Сообщение Padre_Mortius » 19.03.2008 16:13:02

Лучше разницу GetTikCount. Для наглядности
Padre_Mortius
энтузиаст
 
Сообщения: 1265
Зарегистрирован: 29.05.2007 17:38:07
Откуда: Спб

Re: Возврат результата через out-параметр

Сообщение Sergei I. Gorelkin » 19.03.2008 17:10:43

S!V писал(а): Так вот сам вопрос: можно ли как-нибудь этого избежать, не переделывая весь код? И, если сейчас нельзя, то может ли это быть поправлено в следующей версии FPC?
Спасибо.


Не переделывая код - вряд ли. В следующей версии FPC - вполне возможно, что поправят.

ps. Собственными руками делал нечто похожее, только для строк. Само исправление - ровно три строки, зато потом три месяца глюки вылавливал, в процессе чего пришлось переписать несколько сотен строк :/
Аватара пользователя
Sergei I. Gorelkin
энтузиаст
 
Сообщения: 1406
Зарегистрирован: 24.07.2005 14:40:41
Откуда: Зеленоград

Re: Возврат результата через out-параметр

Сообщение S!V » 22.03.2008 00:46:43

Sergei I. Gorelkin писал(а): Не переделывая код - вряд ли. В следующей версии FPC - вполне возможно, что поправят.

ps. Собственными руками делал нечто похожее, только для строк. Само исправление - ровно три строки, зато потом три месяца глюки вылавливал, в процессе чего пришлось переписать несколько сотен строк :/


Ясненько, спасибо. Тогда пока передлывать не буду. Вот, кстати, по просьбам трудящихся сделал рабочий тест:
Код: Выделить всё
program project1;
{$mode objfpc}{$H+}
uses SysUtils;
{ $fputype SSE}

type
  TVec4f = array[0..3] of Single;

procedure VAdd_out(const v1,v2 :TVec4f; out Result :TVec4f);
begin
  Result[0] := v1[0]+v2[0];
  Result[1] := v1[1]+v2[1];
  Result[2] := v1[2]+v2[2];
  Result[3] := v1[3]+v2[3];
end;

operator + (const v1,v2 :TVec4f) :TVec4f;
begin
  Result[0] := v1[0]+v2[0];
  Result[1] := v1[1]+v2[1];
  Result[2] := v1[2]+v2[2];
  Result[3] := v1[3]+v2[3];
end;

function TimeToMS(const Time :TSystemTime) :Integer;
begin
  with Time do
    Result := Millisecond + (Second + (Minute + Hour*24)*60)*1000;
end;

var
  Time1, Time2 :TSystemTime;
  i, Time1_ms, Time2_ms, OpTime_ms, OutTime_ms :Integer;
  v1,v2 :TVec4f;
begin
  v1[0] := 0; v1[1] := 0; v1[2] := 0; v1[3] := 0;
  v2[0] := 0; v2[1] := 1; v2[2] := 0; v2[3] := 1;

  GetLocalTime(Time1); Time1_ms := TimeToMS(Time1);
  for i := 1 to 50000000 do
    v1 := v1 + v2;
  GetLocalTime(Time2); Time2_ms := TimeToMS(Time2);
  OpTime_ms := Time2_ms-Time1_ms;
  WriteLn('Operator time: ',OpTime_ms,'ms');

  GetLocalTime(Time1); Time1_ms := TimeToMS(Time1);
  for i := 1 to 50000000 do
    VAdd_out(v1,v2,v1);
  GetLocalTime(Time2); Time2_ms := TimeToMS(Time2);
  OutTime_ms := Time2_ms-Time1_ms;
  WriteLn('Out time: ',OutTime_ms,'ms');
  WriteLn('Factor: ',OpTime_ms/OutTime_ms:0:3,' :(');
end.                                   

Правда, он выдаст неправильное время, если запустится до полуночи, а остановится после. :)
Наблюдение: если тип компонент TVec4f заменить на Double, то время выполнения функции увеличится, а процедуры - останется прежним. Значит, проблема в лишнем копировании при выходе из функции(оператора).

И ещё, я прочитал, что FPC team собирается использовать Static single assignment для оптимизатора в следующей версии компилера. Сам толком не знаю, что это. Может ли это исправить описанную ситуацию?

ЗЫ. Есть идея добавления out-директивы для функций. Т.е. что-то вроде
Код: Выделить всё
function A :Integer; out;

Чтобы Result внутри функции трактовался как параметр процедуры, как будто описанный в заголовке как "out Result :Integer". Это избавит от необходимости копирования результата после выхода из функции. Имеет смысл оставлять запрос на фичу в багрепортере? Т.е. имеет ли она шансы быть внедрённой, или разработчики забивают на подобные запросы, кто-нибудь знает?
S!V
незнакомец
 
Сообщения: 6
Зарегистрирован: 07.11.2006 23:31:21

Сообщение Sergei I. Gorelkin » 22.03.2008 02:20:04

Ассемблерный код для оператора и процедуры совпадает один в один, оператор возвращает результат в неявном out-параметре.

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

В варианте с out ответственность переходит с компилятора на тебя: если внутри ф-ции читать v1 после записи в result, получишь неверный результат, и компилятор с этим ничего не поделает.

Замени v1:=v1+v2 на v:=v1+v2 (v - еще одна переменная типа TVec4f), и оба теста выдадут одинаковые результаты.

Иделогически правильная сигнатура для этого случая выглядит так:
procedure add(var Dest: TVec4f; const Src: TVec4f);

S!V писал(а):И ещё, я прочитал, что FPC team собирается использовать Static single assignment для оптимизатора в следующей версии компилера. Сам толком не знаю, что это. Может ли это исправить описанную ситуацию?

Может быть. *Что-то* от этого точно улучшится. Но, боюсь, ситуацию с присваиванием результата ф-ции одному из аргументов можно разрулить только путем глобальной оптимизации кода, о которой пока что никто даже не заикался.

S!V писал(а):ЗЫ. Есть идея добавления out-директивы для функций. Т.е. что-то вроде Код:
function A :Integer; out;

Чтобы Result внутри функции трактовался как параметр процедуры, как будто описанный в заголовке как "out Result :Integer". Это избавит от необходимости копирования результата после выхода из функции. Имеет смысл оставлять запрос на фичу в багрепортере? Т.е. имеет ли она шансы быть внедрённой, или разработчики забивают на подобные запросы, кто-нибудь знает?


Это смысла не имеет, потому что уже сделано. Все результаты "не-элементарных" типов (записи, строки, массивы, variant) возвращаются в неявных out-параметрах. Элементарные типы (integer и т.п.) возвращаются в регистрах процессора, потому что это быстрее всего. Копирование результата удаляется в тех случаях, когда компилятор может это сделать, не нарушив правильносии кода.
Аватара пользователя
Sergei I. Gorelkin
энтузиаст
 
Сообщения: 1406
Зарегистрирован: 24.07.2005 14:40:41
Откуда: Зеленоград

Сообщение trifon » 22.03.2008 12:47:20

В данном случае free pascal ни при чем, в C так же происходит дополнительное копирование результата, в C++ можно ссылку возвращать, возможно в некоторых C компиляторах оптимизация обходит это, я не в курсе.
Так что out аргумент fpc можно считать оптимизацией
trifon
постоялец
 
Сообщения: 135
Зарегистрирован: 24.12.2006 12:08:35

Сообщение trifon » 22.03.2008 12:59:35

function A :Integer; out;

Это будет полным смысловым аналогом возврата ссылки в C++ и смысл как раз имеет, в этом случае можно будет использовать функцию как левый аргумент присвоения
Код: Выделить всё
A := 5

В C++ так можно.
trifon
постоялец
 
Сообщения: 135
Зарегистрирован: 24.12.2006 12:08:35

Сообщение SAK » 22.03.2008 22:29:19

А чем не устраивает:
Код: Выделить всё
function A :PInteger;

Только здесь явно ответственность за результат ложится на программиста. А в Вашем варианте что получим в таком случае:
Код: Выделить всё
function A :Integer; out;
  var x: integer;
  begin
      A := x;
  end;

Begin
    A := 5;
end.

Куда будет записан результат присваивания?
SAK
постоялец
 
Сообщения: 158
Зарегистрирован: 18.02.2006 00:45:14
Откуда: Тим

Сообщение S!V » 23.03.2008 16:21:29

Sergei I. Gorelkin писал(а):В варианте с out ответственность переходит с компилятора на тебя: если внутри ф-ции читать v1 после записи в result, получишь неверный результат, и компилятор с этим ничего не поделает.

Замени v1:=v1+v2 на v:=v1+v2 (v - еще одна переменная типа TVec4f), и оба теста выдадут одинаковые результаты.

Заменил, но время выполнения первого теста не изменилось (осталось высоким) при любых оптимизациях (-O1 2 3 и прочие). Может быть, в 2.3.1 так и происходит, у меня 2.2.0. А, в общем, теперь всё ясно, спасибо за обстоятельные ответы.

Когда я говорил про out-директиву для функций, имелось ввиду именно "перекладывание ответственности" на программиста в случае, когда результат записывается в один из параметров, передающихся не по значению. Про функцию в левой части := даже не задумывался. )
S!V
незнакомец
 
Сообщения: 6
Зарегистрирован: 07.11.2006 23:31:21

Сообщение trifon » 23.03.2008 18:57:45

Предназначение функции - подстановка вместо значения, перегрузка оператора это тоже функция, только инфиксная.
Предположим есть другая функция, которой надо передать результат данного сложения, используя оператор + это выглядит так
Код: Выделить всё
sfunc(v1+v2)

то есть никакие промежуточные переменные не требуются.
Используя
Код: Выделить всё
procedure VAdd_out(const v1,v2 :TVec4f; out Result :TVec4f);

придется создавать дополнительную переменную и передавать её.
Это разные парадигмы - функциональная и процедурная.
В чистом функциональном программировании модификация переменных и последовательное исполнение кода вообще не предполагаются.
trifon
постоялец
 
Сообщения: 135
Зарегистрирован: 24.12.2006 12:08:35


Вернуться в Free Pascal Compiler

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 12

Рейтинг@Mail.ru