Макросы в FPC

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

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

Макросы в FPC

Сообщение Brainenjii » 17.01.2012 15:11:06

Стандартная проблема - валидация данных. Как сейчас делаю - есть запись вида
Код: Выделить всё
Type BErrorInfo = Packed Record
  Code: Integer;
  Message: String;
End;
// Assert использую для исключений
Procedure Suppose(Const aCondition: Boolean; Const aCode: Integer;
  Const aMessage: String; Var aErrorInfo: BErrorInfo); Inline;
..
Procedure Suppose(Const aCondition: Boolean; Const aCode: Integer;
  Const aMessage: String; Var aErrorInfo: BErrorInfo);
Begin
  If aCondition And (aErrorInfo.Code = 0) Then
    Begin
      aErrorInfo.Code := aCode;
      aErrorInfo.Message := aMessage;
    End;
End;

Вызываю так:
Код: Выделить всё
Procedure BMeasuresControllerClass.RenameMeasure(Const aMeasureID,
  aDimensionID: Integer; Const aCaption: String; Out aErrorInfo: BErrorInfo);
Var
  aMeasure: BMeasureClass;
Begin
  aMeasure :=  bManager.GetObject(aMeasureID);
  Suppose(aMeasure = nil, CODE_FIND, ERROR_UNABLE_FIND, aErrorInfo);
  // тут приходится проверять предыдующее предположение
  Suppose((aErrorInfo.Code = 0) And (aMeasure.DimensionID = aDimensionID),
    CODE_ILLEGAL_DIMENSION, ERROR_ILLEGAL_DIMENSION, aErrorInfo);
  If aErrorInfo.Code > 0 Then Exit;
  aMeasure := bManager.ChangeObject(aMeasure);
  aMeasure.Caption := aCaption;
End;

Решение с проверкой предыдущего предположения мне кажется неудачным и громоздким. Хотелось бы обойтись без него.
В голову мне приходят только 2 варианта - использовать исключения или макрос.
Исключения использовать не хочу - пока в планах их вызывать только в модели, а в контроллере обойтись обычными кодами ошибок - вроде как это сильно быстрее и легче (поправьте, если не прав).
Макрос, который бы разворачивал Suppose(aMeasure = nil, CODE_FIND, ERROR_UNABLE_FIND, aErrorInfo); в
Код: Выделить всё
  If aMeasure = nil Then
    Begin
      aErrorInfo.Code := CODE_FIND;
      aErrorInfo.Message := ERROR_UNABLE_FIND;
      Exit;
    End;

меня бы устроил более чем полностью. Прочитав документацию по макросам в FPC не понял - можно ли такое сделать. Собственно и вопрос - а можно ли? ^_^

Добавлено спустя 53 минуты 6 секунд:
Удалось сделать костылём:
Код: Выделить всё
..
Interface
..
Type BErrorInfo = Packed Record
  Code: Integer;
  Message: String;
End;

Function Suppose(Const aCondition: Boolean; Const aCode: Integer;
  Const aMessage: String; Var aErrorInfo: BErrorInfo): Boolean; Inline;
..
Implementation
..
Function Suppose(Const aCondition: Boolean; Const aCode: Integer;
  Const aMessage: String; Var aErrorInfo: BErrorInfo): Boolean;
Begin
  Result := aCondition;
  If aCondition And (aErrorInfo.Code = 0) Then
    Begin
      aErrorInfo.Code := aCode;
      aErrorInfo.Message := aMessage;
    End;
end;
..

И вызов:
Код: Выделить всё
{$define Suggest:=if (aErrorInfo.Code = 0) And Suppose}
{$define So:=Then Exit;}
Procedure BMeasuresControllerClass.RenameMeasure(Const aMeasureID,
  aDimensionID: Integer; Const aCaption: String; Out aErrorInfo: BErrorInfo);
Var
  aMeasure: BMeasureClass;
Begin
  aMeasure :=  bManager.GetObject(aMeasureID);
  Suggest(aMeasure = nil, 1, '', aErrorInfo) So;
  Suggest(aMeasure.DimensionID = aDimensionID, 2, '', aErrorInfo) So;
  aMeasure := bManager.ChangeObject(aMeasure);
  aMeasure.Caption := aCaption;
End;

Не нравятся вопиющие костыльность и чужеродность. Плюс, нужно в каждом модуле прописывать дефайны
Аватара пользователя
Brainenjii
энтузиаст
 
Сообщения: 1351
Зарегистрирован: 10.05.2007 00:04:46

Re: Макросы в FPC

Сообщение Odyssey » 17.01.2012 22:30:46

Brainenjii писал(а):(поправьте, если не прав)

Если использовать исключения по назначению, т.е. для исключительных ситуаций, накладные расходы на их вызов и отлов в штатном режиме работы программы равны нулю.
Odyssey
энтузиаст
 
Сообщения: 580
Зарегистрирован: 29.11.2007 17:32:24

Re: Макросы в FPC

Сообщение Sergei I. Gorelkin » 17.01.2012 23:09:45

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


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

Re: Макросы в FPC

Сообщение Brainenjii » 18.01.2012 09:04:40

Код: Выделить всё
Var
  bCounter: Integer = 1;

Function HelloWorld: Integer; Inline;
Begin
  Result := bCounter;
  Inc(bCounter);
  If bCounter mod 100 = 0 Then bCounter := 0;
End;

Var
  aStart: TDateTime;
  aDiff: TDateTime=0;
  i, j, aDummy: Integer;
begin
  For i := 0 To 10 Do
    Begin
      aStart := Now;
      For j := 0 To 10000000 Do
        Try
          If HelloWorld = 0 Then Raise Exception.Create('WTF');
        Except On E: Exception Do
          Inc(aDummy);
        End;
      aDiff += Now - aStart;
    End;
  WriteLn('Exceptions: ', FormatDateTime('ss:zz', aDiff / 11));
  WriteLn(aDummy);
  aDummy := 0;
  aDiff := 0;
  For i := 0 To 10 Do
    Begin
      aStart := Now;
      For j := 0 To 10000000 Do
        If HelloWorld = 0 Then
          Inc(aDummy);
      aDiff += Now - aStart;
    End;
  WriteLn('Codes: ', FormatDateTime('ss:zz', aDiff / 11));
  WriteLn(aDummy);
  aDummy := 0;
  aDiff := 0;
  For i := 0 To 10 Do
    Begin
      aStart := Now;
      For j := 0 To 10000000 Do
        Repeat
          If HelloWorld = 0 Then
            Begin
              Inc(aDummy);
              Break;
            End;
        Until TRUE;
      aDiff += Now - aStart;
    End;
  WriteLn('Breaks: ', FormatDateTime('ss:zz', aDiff / 11));
  WriteLn(aDummy);
end.

Exceptions: 00:353
1100000
Codes: 00:078
1100000
Breaks: 00:079
1100000

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

Про строки не знал, было бы интересно сравнить выполнение операций со строками с этими кадрами, и без них...
Аватара пользователя
Brainenjii
энтузиаст
 
Сообщения: 1351
Зарегистрирован: 10.05.2007 00:04:46

Re: Макросы в FPC

Сообщение Max Rusov » 18.01.2012 11:05:22

Sergei I. Gorelkin
А не планируется ли в FPC под Win64 реализация механизма исключений принятого в Win64 (без except-фреймов)? В Delphi 64 его реализовали - получается очень эффективно, FPC сильно проигрывает по скорости :(
Max Rusov
постоялец
 
Сообщения: 191
Зарегистрирован: 25.04.2009 15:46:03

Re: Макросы в FPC

Сообщение AlexVinS » 18.01.2012 11:32:33

Max Rusov писал(а):Sergei I. Gorelkin
А не планируется ли в FPC под Win64 реализация механизма исключений принятого в Win64 (без except-фреймов)? В Delphi 64 его реализовали - получается очень эффективно, FPC сильно проигрывает по скорости :(

Вроде как Сергей как раз этим сейчас и занимается)
Аватара пользователя
AlexVinS
новенький
 
Сообщения: 95
Зарегистрирован: 27.01.2009 01:18:01

Re: Макросы в FPC

Сообщение Brainenjii » 18.01.2012 12:04:19

В общем остановился на таком макросе:
Код: Выделить всё
{$define MIf:=if (aErrorInfo.Code = 0) And (}
{$define MThen:=) Then Begin aErrorInfo := ErrorInfo}
{$define MBreak:=; Break; End;}
{$define MExit:=; Exit; End;}
{$define MCheckBreak:=If aErrorInfo.Code = 0 Then Break}
{$define MCheckExit:=If aErrorInfo.Code = 0 Then Exit}

Используется примерно так:
Код: Выделить всё
  aMeasure := bManager.GetObject(aMeasureID);
  MIf(aMeasure = nil)MThen(1, 'Unable find Measure')MExit; // константы убраны для наглядности
  Repeat
    aAspectsManager := BAspectsManagerClass.Build;
    aAspectsList := BAspectsList.Build;
    For i := 0 To aAspects.Count - 1 Do
      Begin
        aAspect := aAspectsManager.GetObject(aAspects[i]);
        MIf(aAspect = nil)MThen(2, 'Attempt to insert nil')MBreak;
        aAspectsList.Add(aAspect);
      End;
    MCheckBreak;
    aMeasure := bManager.ChangeObject(aMeasure);
    aMeasure.Caption := aCaption;
    aMeasure.DimensionID := aDimensionID;
    aMeasure.LoadAspects(aAspectsList);
  Until TRUE;
  aAspectsManager.Burn;
  aAspects.Burn;

Производительность такая:
Код: Выделить всё
program Project1;

{$mode objfpc}{$H+}{$macro on}
{$define MIf:=if (aErrorInfo.Code = 0) And (}
{$define MThen:=) Then Begin aErrorInfo := BuildErrorInfo}
{$define MBreak:=; Break; End;}
{$define MExit:=; Exit; End;}

uses
  {$IFDEF UNIX}{$IFDEF UseCThreads}
  cthreads,
  {$ENDIF}{$ENDIF}
  Classes, sysutils
  { you can add units after this };

Type BErrorInfo = Packed Record
  Code: Integer;
  Message: String;
End;

Function ErrorInfo(Const aCode: Integer;
  Const aMessage: String): BErrorInfo; Inline;
Begin
  Result.Code := aCode;
  Result.Message := aMessage;
End;

Var
  bCounter: Integer = 1;

Function HelloWorld: Integer; Inline;
Begin
  Result := bCounter;
  Inc(bCounter);
  If bCounter mod 100 = 0 Then bCounter := 0;
End;

Var
  aStart: TDateTime;
  aDiff: TDateTime=0;
  i, j, aDummy: Integer;
  aErrorInfo: BErrorInfo;
begin
  For i := 0 To 10 Do
    Begin
      aStart := Now;
      For j := 0 To 10000000 Do
        Try
          If HelloWorld = 0 Then Inc(aDummy);
        Except On E: Exception Do
          Inc(aDummy);
        End;
      aDiff += Now - aStart;
    End;
  WriteLn('Exceptions: ', FormatDateTime('ss:zz', aDiff / 11));
  WriteLn(aDummy);

  aDummy := 0;
  aDiff := 0;
  For i := 0 To 10 Do
    Begin
      aStart := Now;
      For j := 0 To 10000000 Do
        If HelloWorld = 0 Then
          Inc(aDummy);
      aDiff += Now - aStart;
    End;
  WriteLn('Codes: ', FormatDateTime('ss:zz', aDiff / 11));
  WriteLn(aDummy);

  aDummy := 0;
  aDiff := 0;
  For i := 0 To 10 Do
    Begin
      aStart := Now;
      For j := 0 To 10000000 Do
        Repeat
          If HelloWorld = 0 Then
            Begin
              Inc(aDummy);
              Break;
            End;
        Until TRUE;
      aDiff += Now - aStart;
    End;
  WriteLn('Breaks: ', FormatDateTime('ss:zz', aDiff / 11));
  WriteLn(aDummy);

  aDummy := 0;
  aDiff := 0;
  For i := 0 To 10 Do
    Begin
      aStart := Now;
      For j := 0 To 10000000 Do
        Repeat
          MIf(HelloWorld = 0)MThen(0, '')MBreak;
        Until TRUE;
      aDiff += Now - aStart;
    End;
  WriteLn('Macro: ', FormatDateTime('ss:zz', aDiff / 11));
  WriteLn(aDummy);
end.

Exceptions: 00:295
1100000
Codes: 00:075
1100000
Breaks: 00:075
1100000
Macro: 00:088
1100000

Хотя, конечно, очень бы хотелось что-то вроде
Код: Выделить всё
{$define SuggestBreak($Condition, $Code, $Message):=
If (aErrorInfo.Code = 0) And($Condition) Then
  Begin
    aErrorInfo.Code := $Code;
    aErrorinfo.Message := $Message;
    Break;
  End;
}
Аватара пользователя
Brainenjii
энтузиаст
 
Сообщения: 1351
Зарегистрирован: 10.05.2007 00:04:46

Re: Макросы в FPC

Сообщение Max Rusov » 18.01.2012 12:49:08

Brainenjii писал(а):Про строки не знал, было бы интересно сравнить выполнение операций со строками с этими кадрами, и без них...

Большую часть overhead'а, привносимого механизмом исключений при работе со строками можно избежать с помощью директивы {$ImplicitExceptions off}. Правда при этом, как я понимаю, можно получить утечки памяти, если исключение все-таки возникнет.
Max Rusov
постоялец
 
Сообщения: 191
Зарегистрирован: 25.04.2009 15:46:03

Re: Макросы в FPC

Сообщение Sergei I. Gorelkin » 18.01.2012 14:28:04

- Параметры в макросах не поддерживаются.
- Поддержку SEH для Win64 закоммитил буквально вчера. Можно пробовать, собрав компилятор с помощью 'make cycle OPT=-dTEST_WIN64_SEH' (если сумеете собрать пакеты, которые в связи с переходом на fpmake находятся в несколько размазанном состоянии). Да, и с GNU утилитами (as, ld) версии 2.21 эта штука не дружит совсем никак.
Аватара пользователя
Sergei I. Gorelkin
энтузиаст
 
Сообщения: 1406
Зарегистрирован: 24.07.2005 14:40:41
Откуда: Зеленоград

Re: Макросы в FPC

Сообщение Max Rusov » 18.01.2012 14:40:17

Sergei I. Gorelkin писал(а):- Поддержку SEH для Win64 закоммитил буквально вчера.

Прикольно. Надо потестировать :)
Max Rusov
постоялец
 
Сообщения: 191
Зарегистрирован: 25.04.2009 15:46:03

Re: Макросы в FPC

Сообщение Brainenjii » 18.01.2012 15:55:30

Пичалька...
А не планируется ли, часом? ^_^'
Аватара пользователя
Brainenjii
энтузиаст
 
Сообщения: 1351
Зарегистрирован: 10.05.2007 00:04:46

Re: Макросы в FPC

Сообщение Ask » 18.01.2012 18:08:02

Я в подобных случаях поступаю так:
Код: Выделить всё
function Check(aCond: Boolean; var aErrorInfo: BErrorInfo; Const aCode: Integer;
  const aMessage: ShortString): Boolean; Inline;
begin
  Result := (aErrorInfo.Code = 0) and aCond;
  if Result then begin
    aErrorInfo.Code := aCode;
    aErrorInfo.Message := aMessage;
  end;
end;

(ShortString -- только для оптимизации в предположении, что фактически для сообщений используются короткие константные строки).
У меня этот код в приведённом выше тесте даёт всего 20% падение в производительности -- зато никаких макросов.
Ask
постоялец
 
Сообщения: 163
Зарегистрирован: 25.12.2008 03:51:37


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

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

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

Рейтинг@Mail.ru