Главная страница
Top.Mail.Ru    Яндекс.Метрика
Текущий архив: 2008.07.27;
Скачать: CL | DM;

Вниз

Коварный inline :)   Найти похожие ветки 

 
LightRipple ©   (2008-06-09 13:40) [0]

Здравствуйте !
Допустим есть у нас вот такой код:

type
PMEM_BLOCK = ^MEM_BLOCK;
MEM_BLOCK = packed record
 Length: ULONG;
 _MaxLength: ULONG;
 pBuffer: Pointer;
 _DiffPtr: ULONG;
end;

function BlockMem_GetMem(const pMemBlock: PMEM_BLOCK; const AllocSize: ULONG): NTSTATUS;
begin
pMemBlock.pBuffer := GetMemory(AllocSize);
if pMemBlock.pBuffer <> nil then
 begin
  pMemBlock.Length := 0;
  pMemBlock._MaxLength := AllocSize;
  Result := STATUS_SUCCESS;
 end
else Result := STATUS_INSUFFICIENT_RESOURCES;
end;

function BlockMem_SetUsedLength(const pMemBlock: PMEM_BLOCK; const UsedLength: ULONG): NTSTATUS;{$IFNDEF INLINE_TEST}inline;{$ENDIF}
begin
if UsedLength <= pMemBlock._MaxLength then
 begin
  pMemBlock.Length := UsedLength;
  Result := STATUS_SUCCESS;
 end
else Result := STATUS_NAME_TOO_LONG;
end;

function BlockMem_MemByOffset(const pMemBlock: PMEM_BLOCK; const Offset: ULONG): Pointer;{$IFNDEF INLINE_TEST}inline;{$ENDIF}
begin
Result := Pointer(ULONG(pMemBlock.pBuffer) + Offset);
end;

procedure _BlockMem_MoveByOffset(const pMemBlock: PMEM_BLOCK; const Buffer; const Offset, BytesWrite: ULONG);{$IFNDEF INLINE_TEST}inline;{$ENDIF}
begin
if BytesWrite > 0 then Move(Buffer, BlockMem_MemByOffset(pMemBlock, Offset)^, BytesWrite);
end;

function BlockMem_MoveByOffset(const pMemBlock: PMEM_BLOCK; const Buffer; const Offset, BytesWrite: ULONG): NTSTATUS;{$IFNDEF INLINE_TEST}inline;{$ENDIF}
begin
Result := BlockMem_SetUsedLength(pMemBlock, Offset + BytesWrite);
if Result = STATUS_SUCCESS then _BlockMem_MoveByOffset(pMemBlock, Buffer, Offset, BytesWrite);
end;

function BlockMem_MoveAppend(const pMemBlock: PMEM_BLOCK; const Buffer; const BytesWrite: ULONG): NTSTATUS;
begin
Result := BlockMem_MoveByOffset(pMemBlock, Buffer, pMemBlock.Length, BytesWrite);
end;


Какое сообщение мы увидим при следующем вызове ?

procedure TMainForm.SpeedButton1Click(Sender: TObject);
var
MemBlock: MEM_BLOCK;
wBuf: WideString;
RetStatus: NTSTATUS;
begin
wBuf := "Hello Dolly !";
RetStatus := BlockMem_GetMem(@MemBlock, 4096);
if RetStatus = STATUS_SUCCESS then
 try
  FillChar(MemBlock.pBuffer^, 4096, 0);
  RetStatus := BlockMem_MoveAppend(@MemBlock, Pointer(wBuf)^, Length(wBuf) * SizeOf(WideChar));
  if NT_SUCCESS(RetStatus) then ShowMessage(PWideChar(MemBlock.pBuffer));
 finally
  FreeMem(MemBlock.pBuffer);
 end;
end;


"Hello Dolly !" ?
А вот и не угадали - пустое. Чтобы найти приветствие для Dolly, надо либо вызвать
ShowMessage(PWideChar(DWord(MemBlock.pBuffer) + DWord(Length(wBuf)) * SizeOf(WideChar))),
либо прописать {$DEFINE INLINE_TEST} перед нашим кодом :)


 
Alien1769 ©   (2008-06-09 14:55) [1]

И шо это было ?
Вопрос для мастеров или замечание для новичков :)


 
версия для печати   (2008-06-09 14:56) [2]

Удалено модератором


 
guav ©   (2008-06-09 15:07) [3]

> [1] Alien1769 ©   (09.06.08 14:55)
> И шо это было ?

Думаю скрытый вопрос "что сломано - код или компилятор?"


 
LightRipple ©   (2008-06-09 15:38) [4]

> [3] guav ©   (09.06.08 15:07)
> Думаю скрытый вопрос "что сломано - код или компилятор?"

Не, [1] Alien1769 © был ближе к истине - "замечание для новичков",
что inline функции могут себя вести не "так, как глаза видят" :)

P.S.
Пришлось потратить время на поиск ошибки,
который усугублялся тем, что при пошаговой отладке не заходим в inline.


 
guav ©   (2008-06-09 15:40) [5]

> [4] LightRipple ©   (09.06.08 15:38)

Т.е. ты гарантируешь что сломан именно компилятор ?


 
LightRipple ©   (2008-06-09 15:47) [6]

> [5] guav ©   (09.06.08 15:40)
> Т.е. ты гарантируешь что сломан именно компилятор ?

Саш, где я такое сказала ?

Код корректен, если мы не объявляем inline.
Он же становится некорректным в другом случае.
(для inline версии в коде "появляется" самая настоящая ошибка)


 
LightRipple ©   (2008-06-09 15:50) [7]

> [6] LightRipple ©   (09.06.08 15:47)

Иными словами: если объявлен inline, то в коде есть ошибка.


 
guav ©   (2008-06-09 15:53) [8]

Я думаю, что в данном случае inline ни на что не должно влиять.
т.е. одно из двух:
1. Ошибка типа undefined behaviour, вот и поведение разное
2. Компилятор ошибается, вот и поведение разное.


 
LightRipple ©   (2008-06-09 15:59) [9]

> [8] guav ©   (09.06.08 15:53)
> Я думаю, что в данном случае inline ни на что не должно влиять.

Влияет, еще и как :)
Перепиши функцию так:
function BlockMem_MoveByOffset(const pMemBlock: PMEM_BLOCK; const Buffer;
                              const Offset, BytesWrite: ULONG): NTSTATUS;{$IFNDEF INLINE_TEST}inline;{$ENDIF}
var
Tmp: ULONG;
begin
Tmp := Offset;
Result := BlockMem_SetUsedLength(pMemBlock, Tmp + BytesWrite);
if Result = STATUS_SUCCESS then _BlockMem_MoveByOffset(pMemBlock, Buffer, Tmp, BytesWrite);
end;


и ошибка исчезнет :) Она возникает именно из-за inline директивы.


 
guav ©   (2008-06-09 16:02) [10]

> [9] LightRipple ©   (09.06.08 15:59)

Значит баг компилятора. Такого не должно быть.


 
LightRipple ©   (2008-06-09 16:08) [11]

>  [10] guav ©   (09.06.08 16:02)
> Значит баг компилятора. Такого не должно быть.

Я сначала тоже не понимала.
Но все правильно.
В случае inline мы не "вызываем" ф-ию с параметрами, а "подставляем ее код" вместо вызова.
Вот и получается, что в строчках:
Result := BlockMem_SetUsedLength(pMemBlock, Offset + BytesWrite);
if Result = STATUS_SUCCESS then _BlockMem_MoveByOffset(pMemBlock, Buffer, Offset, BytesWrite);

Offset становится разным, т.к. подставляется на самом деле не Offset, а pMemBlock.Length.
А он уже успел измениться :)


 
clickmaker ©   (2008-06-09 16:16) [12]

ну да, это как с define max(a, b) ((a) > (b) ? (a) : (b))
c = max(a++, b)
примерно те же грабельки


 
guav ©   (2008-06-09 16:17) [13]

> Offset становится разным

Так не должно такого быть.
Может в delphi какой-то свой смысл инлайна ?
Хотя вряд ли, думаю должно быть как в С++ - вычисление параметров только один раз.

Хочешь сказать что тут будут 3 разных значения ?

function x(): Integer;
begin
 Random(100);
end;

procedure y(i: Integer); inline;
begin
 WriteLn(i);
 WriteLn(i);
 WriteLn(i);
end;

begin
 y(x());
end.


 
guav ©   (2008-06-09 16:19) [14]

> [12] clickmaker ©   (09.06.08 16:16)

Ну так это препроцессор, тут так надо.
Инлайновый max:
template <typename T>
inline T max(T a, T b)
{
 return (((a) > (b)) ? (a) : (b));
}

не содержит этих граблей.


 
LightRipple ©   (2008-06-09 16:26) [15]

> [13] guav ©   (09.06.08 16:17)
> Так не должно такого быть.

Но так есть. Скомпилируй мой код - увидишь своими глазами :)

> Хотя вряд ли, думаю должно быть как в С++ - вычисление параметров только один раз.

Параметры и вычисляются один раз для вызыва функции,
а в случае inline этого вызова вроде как и нет.


 
guav ©   (2008-06-09 16:33) [16]


> [15] LightRipple ©   (09.06.08 16:26)
> а в случае inline этого вызова вроде как и нет.

Вызов есть. Не важно заинлайнен он или нет.
Я считаю, что наблюдаемое поведение противоречит здравому смыслу. В С/С++ точно не так.

> Скомпилируй мой код - увидишь своими глазами
> :)

Верю что баг возможен.
Но ты попробуй более простой случай - например мой код (поправь там Result := Random(100);).
Удивлюсь если i действительно вычислится 3 раза.


 
LightRipple ©   (2008-06-09 16:40) [17]

>  [16] guav ©   (09.06.08 16:33)
> Верю что баг возможен.

Ой не надо мне верить :)
Я не забыла историю с OPEN_IF. Если бы ты там мне поверил, то мы бы и до истины не докопались бы :)

> Но ты попробуй более простой случай

Хорошо. Попробую упростить код с сохранением его свойств :)


 
LightRipple ©   (2008-06-09 16:57) [18]

> [17] LightRipple ©   (09.06.08 16:40)
> Хорошо. Попробую упростить код с сохранением его свойств :)

type
PMEM_BLOCK = ^MEM_BLOCK;
MEM_BLOCK = packed record
 Length: ULONG;
 _MaxLength: ULONG;
end;

function BlockMem_SetUsedLength(const pMemBlock: PMEM_BLOCK; const UsedLength: ULONG): NTSTATUS; inline;
begin
if UsedLength <= pMemBlock._MaxLength then
 begin
  pMemBlock.Length := UsedLength;
  Result := STATUS_SUCCESS;
 end
else Result := STATUS_NAME_TOO_LONG;
end;

function BlockMem_MoveByOffset(const pMemBlock: PMEM_BLOCK; const Offset, BytesWrite: ULONG): NTSTATUS; inline;
var
Tmp: ULONG;
begin
Tmp := Offset;
Result := BlockMem_SetUsedLength(pMemBlock, Offset + BytesWrite);
if Result = STATUS_SUCCESS then Assert(Tmp = Offset);
end;

function BlockMem_MoveAppend(const pMemBlock: PMEM_BLOCK; const BytesWrite: ULONG): NTSTATUS;
begin
Result := BlockMem_MoveByOffset(pMemBlock, pMemBlock.Length, BytesWrite);
end;

procedure TMainForm.SpeedButton1Click(Sender: TObject);
var
MemBlock: MEM_BLOCK;
begin
MemBlock._MaxLength := 4096;
BlockMem_MoveAppend(@MemBlock, 10);
end;


Вот :)


 
guav ©   (2008-06-09 16:57) [19]

А мой код что выдаёт ?


 
LightRipple ©   (2008-06-09 17:00) [20]

> [19] guav ©   (09.06.08 16:57)
> А мой код что выдаёт ?

Твой еще не успела. Сейчас попробую, только этот еще подправлю.


 
LightRipple ©   (2008-06-09 17:08) [21]

Вроде больше выбрасывать нечего:

type
PMEM_BLOCK = ^MEM_BLOCK;
MEM_BLOCK = packed record
 Length: ULONG;
end;

function BlockMem_SetUsedLength(const pMemBlock: PMEM_BLOCK; const UsedLength: ULONG): NTSTATUS; inline;
begin
pMemBlock.Length := UsedLength;
Result := STATUS_SUCCESS;
end;

function BlockMem_MoveByOffset(const pMemBlock: PMEM_BLOCK; const Offset, BytesWrite: ULONG): NTSTATUS; inline;
var
Tmp: ULONG;
begin
Tmp := Offset;
Result := BlockMem_SetUsedLength(pMemBlock, Offset + BytesWrite);
if Result = STATUS_SUCCESS then Assert(Tmp = Offset, IntToStr(Tmp) + sLineBreak + IntToStr(Offset));
end;

function BlockMem_MoveAppend(const pMemBlock: PMEM_BLOCK; const BytesWrite: ULONG): NTSTATUS;
begin
Result := BlockMem_MoveByOffset(pMemBlock, pMemBlock.Length, BytesWrite);
end;


 
LightRipple ©   (2008-06-09 17:22) [22]

> [19] guav ©   (09.06.08 16:57)
> А мой код что выдаёт ?

Твой выдает то что и ожидается.
Но он не совсем точно воспроизводит ситуацию моего кода.


 
guav ©   (2008-06-09 17:33) [23]

> [22] LightRipple ©   (09.06.08 17:22)
> Но он не совсем точно воспроизводит ситуацию моего кода.

Осталось только объяснить в чём разница, где граница и почему это не баг а фича :)


 
LightRipple ©   (2008-06-09 18:56) [24]

> [23] guav ©   (09.06.08 17:33)
> Осталось только объяснить в чём разница, где граница и почему это не баг а фича :)

С разницей, допустим, разберемся.
"не баг а фича" потому что я тысячи раз слышала о том, что глючит Delphi или Windows,
и каждый раз оказывалось, что эти утверждения не соответствовали истине.
И, пока, нет оснований полагать, что данный пример чем-то отличается от своих предшественников :)


 
guav ©   (2008-06-09 22:18) [25]

Вот код, который воспроизводит баг:
procedure Test1(const P: PInteger; const I: Integer); inline;
var
 J: Integer;
begin
 J := I;
 P^ := 1;
 Assert(J = I);
end;

procedure Test();
var
 I: Integer;
begin
 I := 0;
 Test1(@I, I);
end;

А вот чуть изменённый, который не вопроизводит:
procedure Test1(const P: PInteger; const I: Integer); inline;
var
 J: Integer;
begin
 J := I;
 P^ := 1;
 Assert(J = I);
end;

var
 I: Integer;
procedure Test();
begin
 I := 0;
 Test1(@I, I);
end;


В справке про то что aliasing приводит к UB при инланинге - не нашел.


 
LightRipple ©   (2008-06-09 23:43) [26]

> [25] guav ©   (09.06.08 22:18)
> Вот код, который воспроизводит баг:
> ....
> В справке про то что aliasing приводит к UB при инланинге - не нашел.

Т.е. будем считать, что это баг, а не безграмотность автора кода ?
Я правильно поняла ?


 
guav ©   (2008-06-10 00:19) [27]

> [26] LightRipple ©   (09.06.08 23:43)


> Т.е. будем считать, что это баг, а не безграмотность автора кода ?


Считай как хочешь.
Это в С всё проверяется, т.к. и стандарт есть и компиляторов много (вот здесь часто проверяют http://www.comeaucomputing.com/tryitout/ ).
В случае Delphi имеем один компилятор, не всегда надёжный и не все конструкции языка хорошо документированы. Поэтому трудно сказать.
Но я считаю это багом.


 
guav ©   (2008-06-10 00:34) [28]

Зачем вообще применять директиву inline, что ты от неё ожидаешь ?


 
LightRipple ©   (2008-06-10 00:42) [29]

> [28] guav ©   (10.06.08 00:34)
> Зачем вообще применять директиву inline, что ты от неё ожидаешь ?

В данном случае, я просто смотрела как она влияет на быстродействие и "разбухание кода".
Часто встречаю ее в чужом коде, видела и в генофонде (вроде Math, не помню).


 
guav ©   (2008-06-10 01:27) [30]

> [29] LightRipple ©   (10.06.08 00:42)

Вот именно, мелочи вроде размера кода и скорости его выполнения. Но не другой смысл вызова функции.


> как она влияет на быстродействие

Замедляет (подобрать количество Inc(M); и Test1(a); чтобы замедляло :-)
procedure Test1(var M: Integer); inline;
begin
 Inc(M);  Inc(M);  Inc(M);  Inc(M);  Inc(M);
 Inc(M);  Inc(M);  Inc(M);  Inc(M);  Inc(M);
 Inc(M);  Inc(M);  Inc(M);  Inc(M);  Inc(M);
 Inc(M);  Inc(M);  Inc(M);  Inc(M);  Inc(M);
 Inc(M);  Inc(M);  Inc(M);  Inc(M);  Inc(M);
 Inc(M);  Inc(M);  Inc(M);  Inc(M);  Inc(M);
 Inc(M);  Inc(M);  Inc(M);  Inc(M);  Inc(M);
 Inc(M);  Inc(M);  Inc(M);  Inc(M);  Inc(M);
 Inc(M);  Inc(M);  Inc(M);  Inc(M);  Inc(M);
 Inc(M);  Inc(M);  Inc(M);  Inc(M);  Inc(M);
 Inc(M);  Inc(M);  Inc(M);  Inc(M);  Inc(M);
 Inc(M);  Inc(M);  Inc(M);  Inc(M);  Inc(M);
 Inc(M);  Inc(M);  Inc(M);  Inc(M);  Inc(M);
 Inc(M);  Inc(M);  Inc(M);  Inc(M);  Inc(M);
 Inc(M);  Inc(M);  Inc(M);  Inc(M);  Inc(M);
 Inc(M);  Inc(M);  Inc(M);  Inc(M);  Inc(M);
end;

procedure Test();
var a: Integer;
begin
 Test1(a);
end;

var T: DWORD; I: Integer;
begin
 T := GetTickCount();
 for I := 0 to 500000 do
   Test();
 T := GetTickCount() - T;
 WriteLn(T);
 ReadLn;
end.



> [29] LightRipple ©   (10.06.08 00:42)


> и "разбухание кода".

Снижает
procedure Test1(var I: Integer); inline;
begin
 I := I + 1;
end;

procedure Test();
var a,b,c,d,e: Integer;
begin
 Test1(a);
 Test1(b);
 Test1(c);
 Test1(d);
 Test1(e);
end;


Два способа правильного применения инлайна: 1. ничего не инлайнить, компилятор сам заинлайнит когда это нужно, если конечно он это умеет. 2. осознанно инлайнить или запрещать инлайн в узких местах, руководствуясь результатами профилирования. А необоснованый инлайн - это "Premature optimization, the root of all evil".


 
LightRipple ©   (2008-06-10 07:24) [31]

> [30] guav ©   (10.06.08 01:27)

Спасибо :)


 
guav ©   (2008-06-10 11:08) [32]

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


 
Игорь Шевченко ©   (2008-06-10 12:02) [33]


> 1. ничего не инлайнить, компилятор сам заинлайнит когда
> это нужно, если конечно он это умеет


Э...а как это компилятор Delphi умеет ?


 
guav ©   (2008-06-10 12:17) [34]

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


 
Игорь Шевченко ©   (2008-06-10 12:49) [35]


> Delphi никак.


речь о delphi идет вроде как. А в delphi inline выполняется согласно директиве.


> А также к тому что раз на результат выполнения inline не
> должно влиять, оно скорее не нужно чем нужно


inline нужно для увеличения быстродействия - собственно для этого оно и придумано, как и развертывание циклов


 
guav ©   (2008-06-10 13:24) [36]

> [35] Игорь Шевченко ©   (10.06.08 12:49)
> inline нужно для увеличения быстродействия

Я же не написал "не нужно". Я написал "скорее не нужно чем нужно".
Инлайн и развёртывание во всех случаях где это можно - это та самая преждевременная оптимизация.
Где-то инлайн и развёртывание будут дейтвительно полезны. Где-то - приводить к снижению быстродействия. Где-то ещё - раскроют глюки компилятора :-) . А в большинстве случаев ни приведут ни к каким ощутимым результатам. Не так ли ?



Страницы: 1 вся ветка

Текущий архив: 2008.07.27;
Скачать: CL | DM;

Наверх




Память: 0.58 MB
Время: 0.019 c
2-1214396148
lewka-serdceed
2008-06-25 16:15
2008.07.27
считавание с web страниц


2-1214306045
Тын-Дын
2008-06-24 15:14
2008.07.27
Корректность конструкции при переопределении конструктора


2-1214223301
F@T@L_Err0r
2008-06-23 16:15
2008.07.27
Закрытие приложения


2-1214292989
NeiL
2008-06-24 11:36
2008.07.27
Декодировать число


3-1202816869
Kuibida
2008-02-12 14:47
2008.07.27
Что за база такая (форматы файлов *.d и *.i) ?