Оптимизация и INLINE

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

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

Ответить
mike
новенький
Сообщения: 55
Зарегистрирован: 23.02.2007 16:25:00

Оптимизация и INLINE

Сообщение mike »

Можно ли каким-нибудь образом оптимизировать вызов такого типа:

Код: Выделить всё

procedure AppendLog(const LogKind: TLogKind; const Text: sting);
begin
  if LogKind in LogKinds then WriteLn(LogFile, Text);
end;

//...
  AppendLog(lkDebug, 'ErrCode='+IntToStr(ErrCode)+' "'+SysErrorMessage(ErrCode)+'"');
//...

В чем суть. Если глобальное множество LogKinds не содержит элемент lkDebug, то в лог не будет попадать соответствующая строка. Но проблема в том, что она все равно будет сформирована, будут вызваны функции IntToStr() и SysErrorMessage(), несколько раз будет перевыделена память и произведена конкатенация элементов строки. И все это для того, чтобы уже внутри функции понять, что ничего сохранять не нужно. В итоге большие и совершенно неоправданные потери времени. Проверка множества перед вызовом AppendLog() конечно решает проблему, но загромождает код.

На первый взгляд должна была помочь директива inline, но ассемблерный листинг показал, что "разворачивание" процедуры производится просто в месте (и вместо) ее вызова, все подготовительные действия выполняются точно так же, хотя как раз в этом случае можно было бы сгенерить такой код:

Код: Выделить всё

//...
  if lkDebug in LogKinds then WriteLn(LogFile, 'ErrCode='+IntToStr(ErrCode)+' "'+SysErrorMessage(ErrCode)+'"');
//...

Которого я ожидал и который решил бы проблему...

Короче, нет ли в FPC чего-нибудь типа сишных макросов, которые просто подставляли бы в нужное место шаблонный код без предварительного вычисления переданных в качестве параметров выражений?
sign
энтузиаст
Сообщения: 1131
Зарегистрирован: 30.08.2009 09:20:53

Сообщение sign »

Нарисовать процедуру

Код: Выделить всё

procedure AppendMyLog(const LogKind: TLogKind; lkDeb: ...; ErrCode: Integer);
begin
  if lkDeb in LogKind then
    WriteLn(LogFile, 'ErrCode='+IntToStr(ErrCode)+' "'+SysErrorMessage(ErrCode)+'"');
end;
Аватара пользователя
AbakAngelSoft
постоялец
Сообщения: 273
Зарегистрирован: 06.08.2008 19:28:26
Откуда: Краснодар
Контактная информация:

Сообщение AbakAngelSoft »

Использовать функцию Format

Код: Выделить всё

procedure AppendLog(const LogKind: TLogKind; const AText: string; Params: array of const);
begin
  if LogKind in LogKinds then begin
    WriteLn(LogFile, Format(AText,  Params));
  end;
end;

В отличии от варианта sign сохранится гибкость первоначальной функции, а все преобразования типов, конкатенация и т.д. будут вызваны только при необходимости.
Вот правда SysErrorMessage(ErrCode) прийдется все таки вызвать заранее.

Добавлено спустя 3 минуты 31 секунду:
Возникла идея!
Можно в качестве Params передавать не готовые параметры а ссылки на функции или методы их возвращающие. Только тогда прийдется реализовать что-то вроде замыканий и простенькая функция вывода лога станет громадным монстром.
Последний раз редактировалось AbakAngelSoft 30.03.2010 12:01:07, всего редактировалось 1 раз.
MageSlayer
постоялец
Сообщения: 216
Зарегистрирован: 07.09.2006 12:30:44

Сообщение MageSlayer »

AbakAngelSoft писал(а):Добавлено спустя 3 минуты 31 секунду:
Возникла идея!
Можно в качестве Params передавать не готовые параметры а ссылки на функции или методы их возвращающие. Только тогда прийдется реализовать что-то вроде замыканий и простенькая функция вывода лога станет громадным монстром.


И еще идея - создать новую/попатчить в компиляторе функцию типа WriteLn (например для файла StdErr) , чтоб пропускать код в зависимости от глобального флага :)))).
Сорри, не удержался :).

Собственно - да. Замыканий/делегатов тут не хватает.
Аватара пользователя
AbakAngelSoft
постоялец
Сообщения: 273
Зарегистрирован: 06.08.2008 19:28:26
Откуда: Краснодар
Контактная информация:

Сообщение AbakAngelSoft »

MageSlayer писал(а):делегатов тут не хватает

Делегат - это я так понимаю типобезопасный указатель на функцию. А в паскале указатель на функцию разве не имеет определенный тип с указанием параметров вызова и указанием возвращаемого значения? Поправьте если я ошибаюсь.

Кстати замыкания в паскале можно реализовать не расширяя язык. Через объекты и ссылки на методы этих объектов. По моему в delphi так и сделали, разбавив немного синтаксическим сахаром.
MageSlayer
постоялец
Сообщения: 216
Зарегистрирован: 07.09.2006 12:30:44

Сообщение MageSlayer »

AbakAngelSoft писал(а):
MageSlayer писал(а):делегатов тут не хватает

Делегат - это я так понимаю типобезопасный указатель на функцию. А в паскале указатель на функцию разве не имеет определенный тип с указанием параметров вызова и указанием возвращаемого значения? Поправьте если я ошибаюсь.


Э-э. Это дурацкий разброд в терминах.

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

См. http://www.digitalmars.com/d/2.0/lazy-evaluation.html Кстати, там пример как раз с логгированием :)

AbakAngelSoft писал(а):Кстати замыкания в паскале можно реализовать не расширяя язык. Через объекты и ссылки на методы этих объектов. По моему в delphi так и сделали, разбавив немного синтаксическим сахаром.


Не расширяя язык, это, имхо, преувеличение. Хотя, да, в Дельфе вроде сделано через объекты. Тяжелая и универсальная реализация, короче.
Мне бы, чего-нить попроще :).
См. viewtopic.php?f=1&t=5507
mike
новенький
Сообщения: 55
Зарегистрирован: 23.02.2007 16:25:00

Сообщение mike »

Спасибо за советы, я так понимаю макросов с параметрами нет, жаль. Вариант с кучей специализированных лог-процедур на все случаи жизни -- это слишком, а использование Format() все равно не избавит от неоправданного вызова IntToStr() и SysErrorMessage() (а значит и выделения памяти под результат с ее немедленным освобождением). Код может вызываться миллионы раз в секунду и это становится большой проблемой.

Придется писать условия по месту, а это превратит простой код:

Код: Выделить всё

//...
if ErrCode <> 0 then
  AppendLog(lkError, 'ErrCode='+IntToStr(ErrCode)+' "'+SysErrorMessage(ErrCode)+'"')
else
  AppendLog(lkDebug, 'Opeation: Success')
//...
в этот:

Код: Выделить всё

//...
if ErrCode <> 0 then
  if lkError in LogKinds then
    AppendLog('ErrCode='+IntToStr(ErrCode)+' "'+SysErrorMessage(ErrCode)+'"')
  else
else
  if lkDebug in LogKinds then
    AppendLog('Opeation: Success')
  else
//...

Совсем иная читаемость :(
скалогрыз
долгожитель
Сообщения: 1804
Зарегистрирован: 03.09.2008 02:36:48

Сообщение скалогрыз »

mike писал(а):Придется писать условия по месту, а это превратит простой код:

Код: Выделить всё

//...выше написан...

Совсем иная читаемость :(


Зачем писать "по месту вызова"? сделай отдельную процедуру. Сделай её inline чтобы не было обидно за "потерю времени на вызов процедуры". И читабельность вырастает до небес.

Код: Выделить всё

const
  AllowedToLog  = [lkError];

function AllowErrorLog(kind: TLogKind): Boolean; inline;
begin
  Result:=kind*AllowedToLog<>[];
end;

procedure AppendSysErrLog(kind: TLogKind; errCode: integer); inline;
begin
  if errCode=0 then
    AppendLog(kind, 'Opeation: Success')
  else if AllowErrorLog(kind) then
    AppendLog('ErrCode='+IntToStr(ErrCode)+' "'+SysErrorMessage(ErrCode)+'"')
end;


соответсвенно вызов:

Код: Выделить всё

  AppendSysErrLog(lkDebug, errCode);
Аватара пользователя
AbakAngelSoft
постоялец
Сообщения: 273
Зарегистрирован: 06.08.2008 19:28:26
Откуда: Краснодар
Контактная информация:

Сообщение AbakAngelSoft »

mike писал(а):не избавит от неоправданного вызова IntToStr() и SysErrorMessage()

От неоправданного вызова IntToStr избавит!
скалогрыз писал(а):сделай отдельную процедуру

Совершенно верный подход!
mike
новенький
Сообщения: 55
Зарегистрирован: 23.02.2007 16:25:00

Сообщение mike »

скалогрыз, у меня в AppendLog() может передаваться что угодно, нет единого шаблона. Это может быть не только код и расшифровка ошибки, это бывает и информация об IP-адресе, и диагностическое сообщение о потребленной памяти и т.п. Предлагаете на каждый единичный случай логирования строки отдельную процедуру писать?
скалогрыз
долгожитель
Сообщения: 1804
Зарегистрирован: 03.09.2008 02:36:48

Сообщение скалогрыз »

mike писал(а): Предлагаете на каждый единичный случай логирования строки отдельную процедуру писать?

Конечно! нужно же как-то заменять макросы с параметрами ;)
Я бы рад повторить дельное предложение с использованием Format, но не буду.

Кстати, таких "единичных случаев", на самом деле, по пальцам пересчитать.

Всяко лучше использовать структурированный код, чем писать всё в кучу?! или я заблуждаюсь?!

Код: Выделить всё

unit Log.

procedure AllowToLog(kind: TLogKind);

procedure AppendLog(kind: TLogKind; const S: AnsiStirng);
procedure AppendLog(kind: TLogKind; const Fmt: AnsiString; const Params: array of const);

---
unit LogEx;

uses  Log;

// каждая из процедур вызывает AppendLog, с предварительной проверкой kind
procedure AppendSysErrLog(kind: TLogKind; Err: Integer);
procedure AppendIPLog(kind: TLogKind; const Cmd: AnsiString; IPAddr: TIPAddr);
procedure AppendMemUsage(kind: TLogKind; const Cmd: AnsiString; Mem: PtrUInt);
mike
новенький
Сообщения: 55
Зарегистрирован: 23.02.2007 16:25:00

Сообщение mike »

скалогрыз писал(а):Я бы рад повторить дельное предложение с использованием Format, но не буду.

Миллион-другой ненужных stralloc'ов (или как их там зовут) в секунду тоже очень плохо (даже без учета внутренностей SysErrorMessage() и иже с ним).

скалогрыз писал(а):Кстати, таких "единичных случаев", на самом деле, по пальцам пересчитать.

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

P.S. Уже давно все переписал под проверку перед вызовом.
скалогрыз
долгожитель
Сообщения: 1804
Зарегистрирован: 03.09.2008 02:36:48

Сообщение скалогрыз »

mike писал(а):Миллион-другой ненужных stralloc'ов (или как их там зовут) в секунду тоже очень плохо (даже без учета внутренностей SysErrorMessage() и иже с ним).

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

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

А во время работы программы собирать только необходимые данные (н.р. код ошибки, но не текст ошибки)

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

а что можно записать и в каких комбинациях!? что есть такого, с чем Format не справиться?! format('%s %x %d', [s, 4, 5]);
н.р.

Код: Выделить всё

TIPAddr = record
   b1,b2,
   b3,b4: byte;
end;

writeln(format('удалённый сервер %d.%d.%d.%d не ответил во время', [ip.b1,ip.b2,ip.b3,ip.b4]));
alexrayne
постоялец
Сообщения: 125
Зарегистрирован: 03.12.2008 15:56:26

Сообщение alexrayne »

А во время работы программы собирать только необходимые данные (н.р. код ошибки, но не текст ошибки)

+1
сам делал журналирование, милионов событий небуло, всего несколько сотен\сек но формирование сообщения из данных (оно же форматирование) + подсистема вывода в банальный файл давала серьезные лаги в журналируемом коде. посему делал специальный буффер сообщений и несколько раз\сек вызывал задачу которая етот буффер печатала в файл все свободное время.
Ответить