Главная страница
    Top.Mail.Ru    Яндекс.Метрика
Форум: "Прочее";
Текущий архив: 2008.07.27;
Скачать: [xml.tar.bz2];

Вниз

Коварный 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;
Скачать: [xml.tar.bz2];

Наверх




Память: 0.56 MB
Время: 0.007 c
15-1212514820
olevacho_
2008-06-03 21:40
2008.07.27
инструменты создания отчетов для полиграфии


2-1214667019
NewQuestion
2008-06-28 19:30
2008.07.27
Разделитель целой и дробной части в денежной единице.


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


2-1214401410
Makaveli
2008-06-25 17:43
2008.07.27
WinInet и HttpSendRequest


15-1213187241
Dennis I. Komarov
2008-06-11 16:27
2008.07.27
MS SQL Server 2005 EE





Afrikaans Albanian Arabic Armenian Azerbaijani Basque Belarusian Bulgarian Catalan Chinese (Simplified) Chinese (Traditional) Croatian Czech Danish Dutch English Estonian Filipino Finnish French
Galician Georgian German Greek Haitian Creole Hebrew Hindi Hungarian Icelandic Indonesian Irish Italian Japanese Korean Latvian Lithuanian Macedonian Malay Maltese Norwegian
Persian Polish Portuguese Romanian Russian Serbian Slovak Slovenian Spanish Swahili Swedish Thai Turkish Ukrainian Urdu Vietnamese Welsh Yiddish Bengali Bosnian
Cebuano Esperanto Gujarati Hausa Hmong Igbo Javanese Kannada Khmer Lao Latin Maori Marathi Mongolian Nepali Punjabi Somali Tamil Telugu Yoruba
Zulu
Английский Французский Немецкий Итальянский Португальский Русский Испанский