الفريق العربي للبرمجةأرشيف المنتديات · 2000 – 2023
نسخة أرشيفية للقراءة فقط — التسجيل والمشاركة مغلقان، والمحتوى محفوظ كما كان.

Inline directive

بدأه Wael Dalloul في 20 يونيو 2006 · 5 رد · 2,291 مشاهدة · في لغة Delphi
مشاركة: واتساب X فيسبوك تيليجرام
#1 صاحب الموضوع

ما هي Inline directive:

مقدمة: دائما كان يجري نقاش بيني و بين المبرمجين الاخرين حول السؤال التالي:

هل كتابة الكود في Block واحدة اي في Function واحد مثل Main function هي افضل من كتابة الكود بشكل مجزء و مقسم الى عدة توابع؟

و في الاخير اتفقنا انه عند كتابة الكود في Block واحدة هي اسرع بكثير من كتابة الكود مجزء الى توابع و اجرائيات و لكن من الاسباب التي لم تجعلنا نستخدم هذه الطريقة هي ان حجم الكود الناتج هو اكبر بكثير من الحجم الذي قد ينجم عند كتابة توابع و اجرائيات و انه ايضا غير منظم و صعب القراءة و غير قابل لاعادة الاستخدام الخ الخ...

و لكن لماذا اسرع؟ لم نعرف السبب!! حاول ان تفكر في الجواب قليلا

و طبعا لم نكتب اي برنامج نستخدم فيه هذه الطريقة و مع صدور دلفي 2005

ظهر مصطلح جديد الا و هو Inline function و الذي يعطي تحكم بكيفية ترجمة(Compile) الاجرائيات او التوابع(الدوال) بحيث يمكنك من تسريع عمل التوابع الصغيرة و التي يتم استخدامها بكثرة في الكود حيث ممكن ان تعتبر انّ الاجرائيات او التوابع Inline هي اسرع من التوابع التي ليست Inline اي التوابع العادية و لكن هناك سيئة واحدة لاستخدام توابع او اجرائيات Inline الا و هي زيادة في حجم الملف التنفيذي اي ملف Exe الناتج عن تنفيذ البرنامج.

على ما اعتقد انّ Inline function قد وجدت منذ زمن بعيد فيC++ و ها هي اليوم في دلفي.

الان كيف نقوم بتعريف تابع او اجرائية بحيث تكون Inline:

ما عليك الا وضع الكلمة المحجوزة Inline في نهاية تعريف الاجرائية كأنك تستخدم Virtual او override..

و ذلك كما يلي:

procedure MyProc(x:Integer); inline;
begin
	// ...
end;

function MyFunc(y:Char) : String; inline;
begin 
   // ..
end;

ملاحظة: سوف استخدم الكلمة Method للدلالة على التوابع و الاجرائيات

حتى الان لا يوجد اي طريقة لمعرفة انّ التابع سوف يتم تنفيذه Inline او سوف يتم تنفيذه بشكل عادي و لكن هناك بعض الشروط التي لا يمكن استخدام Inline معها مثل ما يلي:

- يجب ان لا يحتوي التابع على assembly code و الا سوف يتم تنفيذه عادي

- تابع البناء(Constructors) و تابع الهدم(destructors) لا يمكن ان يكونا Inline ابدا.

- الجزء الرئيسي من البرنامج و الذي يبدأ ب Begin و ينتهي ب End و الذي يتم بدأ تنفيذ البرنامج من عنده لا يمكن ان يكون Inline كذلك unit initialization و ال unit finalization.

- Method لم يتم تعريفها مسبقا لا يمكن استخدامها ك Inline.

- Method التي تأخذ في بارمتراتها مصفوفة مفتوحة لا يمكن ان تكون Inline.

و هناك العديد من الامور الاخرى التي يمكنكم الاطلاع عليها في Help المرفق مع اللغة.

كيف تعمل Inline functions: اي Method معرف Inline:

نحن نعلم انه عند استدعاء اي Method يتم الحجز لجميع المتحولات المعرفة ضمن هذا ال Method و يتم الحجز ايضا لجميع المتحولات المعرفة في Parameters.

هل تعلمون يا اصدقائي اين يتم هذا الحجز؟ نعم نعرف يتم في Stack اي المكدس

و لكن هل تعلمون ما هو المكدس؟ نعم نعرف هو عبارة عن قطعة او قسم من الذاكرة يتم استخدامها من قبل التطبيق عند التعامل مع متحولات المحلية اوعندما تنادي على إجرائية، يتم وضع محدّداتها و نوع ارجاعها في Stack .

جواب اخر: ال Stack هو واحد من ثلاث مناطق للذاكرة متوفرة للتطبيق. المنطقتان الأخريان تسمّيان ذاكرة جامعة global memory و ركام. Heap

يتم استخدامه لحجز المتحولات المحلية و لتخزين محددات اجرائية و قيمها المرجعة.

عندما ينتهي تنفيذ الاجرائية يتم بشكل تلقائي الغاء حجز المتحولات و البارمترات من الذاكرة هذا هو السبب الذي لا يجعلك تتمكن من استخدام المتحولات المحلية المعرفة ضمن الاجرائية و البارمترات خارج هذه الاجرائية.

يمكن لتطبيقات ويندوز أن تحتفظ بكمية ضخمة من البيانات في ذاكرة ال Stack حيث انه في في دلفي يمكنك تحديد هذا الأمر في صفحة الرابط Linker من خيارات المشروع. اذا ما حصلت على رسالة خطأ لإمتلاء الصفّ stack full فالأغلب ان هذا سببه أن لديك وظيفة تنادي نفسها تكرارا و بدون توقف. وليس بسبب محدودية فضاء ال Stack.

لن اتحدث كثير عن ال Stack و انما سوف ابقى في Inline Function و كيف تعمل!؟

ولكن يجب علينا الغوص قليلا في بعض الامور و التي تعتبر Low level و عادة اغلب المطورين لا يعيرون لها انتباههم و ذلك لوجود صعوبة بعض الشيئ و لانهم ليسوا بحاجة اليها في بناء تطبيقاتهم و لا لمعرفة كيف تعمل. و لكن نريد ان نفهم كيف تعمل Inline function و لماذا هي اسرع من Function العادية ما السر الذي تخفيه وراءها Inline function حتى يكون تنفيذها اسرع من تنفيذ ال function العادية و لماذا حجم الملف التنفيذي يكبر عند استخدامها؟

دعونا نبدأ رويدا رويدا خطوة بخطوة!

عندما يتم عمل Compile لاجرائية او تابع(Method) ماذا يحدث؟

سوف يتم ضم كل من لغة الالة التي تمثل تعليمات ال Method و كود الدخول و الخروج من ال Method الى الملف التنفيذي.

ايضا سوف يتولد لكل استدعاء ل Method تعليمات لغة الة تقوم بتمرير البامترات و تعليمات اخرى تقوم باستدعاء ال Method.

دعونا نلقي نظرة على بعض تعليمات لغة الالة:

ليكن لدينا التابع MyAbs و الذي يعيد القيمة المطلقة لاي رقم ممرر له حيث انّ كود دلفي لعمل هذا التابع و لاستدعاءه يكون على الشكل التالي

Function MyAbs(I: Integer): Integer;
begin
  if I < 0  then
	Result := -I
  else
	Result := I;
end;

begin
  x := MyAbs(-20);
end.

الان لاستدعاء تابع Myabs في القسم الرئيسي من البرنامج يقوم دلفي بتوليد كود لغة الالة التالي

mov eax,$ffffffec
call MyAbs
mov [$0040565c],eax

ما هذا طلاسم؟ خربيش جاج؟!! لا انه كود اسمبلي كود الالة الذي كنا نتحدث عنه

التعليمة الاولى تقوم بوضع الرقم -20 في المسجل eax اي ال Stack

التعليمة الثانية تقوم باستدعاء التابع Myaps

التعليمة الثالثة و الاخيرة تقوم بوضع النتيجة الراجعة من التابع بالمتحول X حيث انّ $0040565c هي عنوان المتحول x في الذاكرة

دعونا الان نرى كود الالة الذي يولده دلفي للتابع MyAps

// begin
push ebp
mov ebp,esp
add esp,-$08
mov [ebp-$04],eax

// if I < 0  then
cmp dword ptr [ebp-$04],$00
jnl $00403a19

// Result := -I
mov eax,[ebp-$04]
neg eax
mov [ebp-$08],eax
jmp $00403a1f

// Result := I;
mov eax,[ebp-$04]
mov [ebp-$08],eax
mov eax,[ebp-$08]

// end;
pop ecx
pop ecx
pop ebp
ret

القسم الاول:

// begin
push ebp
mov ebp,esp
add esp,-$08
mov [ebp-$04],eax

التعليمتين الاولتين فقط يتم حجز المتحولات اللازمة للاستخدام فقط هكذا لا اريد ان اتوسع في هذا الامر

التعليمة الثالثة تحجز 8 بايت من ذاكرة ال Stack و هي مساحة كافية لحفظ قيمتين من النوع Integer.

التعليمة الرابعة تقوم بنقل القيمة من البارميتر الى ال Stack

القسم الثاني:

// if I < 0 then
cmp dword ptr [ebp-$04],$00
jnl $00403a19

هنا يتم فحص فيماذا كانت قيمة I تحتاج لكي تحول الى قيمة موجبة ام لا.

التعليمة الاولى: تقوم بالمقارنة بين البارميتر I الموجود في ال Stack مع قيمة الصفر 0

التعليمة الثانية: اذا كانت قيمة البارميتر I هي اكبر من صفر 0 اي انّ القيمة الموجودة في المتحول I لا تحتاج الى تحويل هنا يتم القفز الى القسم الثالث. و من ثم يتم اعادة القيمة الى الخرج.

القسم الثالث:

// Result := I;
mov eax,[ebp-$04]
mov [ebp-$08],eax
mov eax,[ebp-$08]

القسم الرابع:

في حال كانت قيمة I هي قيمة سالبة سوف يتم هنا عكس اشارة I و من ثم يتم اعادة القيمة الى الخرج.

// Result := -I
mov eax,[ebp-$04]
neg eax
mov [ebp-$08],eax
jmp $00403a1f
mov eax,[ebp-$08]

القسم الخامس

// end;
pop ecx
pop ecx
pop ebp
ret

هنا يتم الغاء حجز المتحولات التي تم حجزها في البادية حيث باستدعاء هذه التعليمات يتم ارجاع ال Stack الى الوضع الذي كان عليه قبل استدعاء التابع كاملا.

اخر تعليمة و هي ret تعيد التحكم الى المستدعي لكي يتم اكمال التنفيذ.

الان دعونا ننظر كيف سوف يتعامل دلفي مع الكود السابق في حال وجود كلمة Inline بجانب تعريف التابع:

في الحقيقة عند استخدام Inline تختلف طريقة اصدار دلفي للكود كليا عن الطريقة السابقة حيث انه عند استخدام كلمة Inline لا يستدعي دلفي التابع بل يقوم بنسخ التابع ووضعه عند كل استدعاء للتابع اي عمل عدة نسخ ووضع كل منها في مكان الاستدعاء طبعا عدة نسخ اسمبلي اي كود الالة.

بهذه الطريقة لا يقوم ال Compiler بتوليد كود لا لتعريف البارمترات اي القسم الاول الذي تحدثنا عنه و لا يقوم ايضا بارجاع القيم اي لا يقوم بتوليد الكود الذي في القسم الاخير.

عند استخدام ال Inline functions في التوابع الصغيرة سوف تلاحظ زيادة كبيرة في سرعة التنفيذ و بشكل خاص عند استخدام التابع في الحلقات.

الان نسخة من التابع السابق و لكن هذه المرة مع وجود كلمة Inline اي استخدام Inline function:

function MyAbs(I: Integer): Integer; inline;
begin
  if I < 0  then
	Result := -I
  else
	Result := I;
end;

عند استخدام توابع Inline لا يتم توليد اي كود اضافي حيث يتم فقط توليد التعليمات لتنفيذ المطلوب دون ادخال اي شيئ في ال Stack او استرجاع اي شيئ منه.

هذا هو كود الالة الذي يتم وضعه في حال استخدمت Inline function

mov eax,$ffffffec
test eax,eax
jnl $00403a6c
neg eax
mov ebx,eax

الان وضح كل شيئ وانكشف الغطاء وعرفنا ما هو سبب سرعة Inline function عن ال function العادي؟ بسبب نقص عدد التعليمات اللازمة لتنفيذ المطلوب

اذا اعيد و اكرر حتى توضح الفكرة اكثر عند كتابة Inline function كل التعليمات التي تؤلف هذا التابع سوف يتم نسخها الى كل مكان استدعاء لهذا التابع مما يؤدي الى حجم كبير في الملف التنفيذ النهائي و ذلك بسبب تكرار الكود و سوف يؤدي ايضا الى زيادة كبيرة في السرعة بسبب انّ حجم التعليمات الازمة لتنفيذ الامر بدون استدعاء تكون اقل من التعليمات اللازمة لتنفيذ الامر مع استدعاء.

فكرة مفيدة و هامة للجميع:

قد يقول البعض انّ Inline function اتت مع نسخة دلفي 2005 اي لم تكن موجودة في النسخ السابقة دلفي7 و ما سبق و بالتالي فإن ال Compiler للدلفي 7 و ما سبق لا يستطيع ترجمة Inline function ؟!!!

ماذا لو اردت ان اترجم برنامجي على نسخة دلفي7 ماذا يحصل؟!! اكيد لن تتمكن من ترجمته

فما رأيكم بماذا نرد على هذا المبرمج الفذ و الذي يفكر للبعيد و يحسب حساب كل شيئ؟

ما رأيكم هل من جواب فكر قليلا بالامر!!؟

الجواب هو:

في دلفي هناك شيئ يسمى conditional compilation اي الترجمة المشروطة اي استطيع ان افحص شيئ ما اذا لم يكن مثلما اريد فلا تتم الترجمة.

مثال: اذا كتب الجملة التالية:

{$IFDEF LINUX}
WriteLn('Linux');
{$ENDIF}

هذه الجملة التي كتبناها لن يتم تنفيذها الى اذا كنت تنفذ برنامجك على Linux

$IFDEF نضع بعدها ما نريد ان نفحصه و نختم بعد ذلك $ENDIF

الان نحن نريد ان يكون التابع Inline في حال كنا نعمل على نسخة دلفي 2005 او 2006 و ان يتم تنفيذ التابع بشكل عادي في حال كانت النسخة هي دلفي7 او ما سبق الان لفحص رقم النسخة نستخدم ما يلي

{$IFDEF VER170}

// Delphi 2005 specific code

{$ENDIF}

حيث انّ VER170 تدل على انّ النسخة المستخدمة هي دلفي 2005 و VER180 تدل على انّ النسخة المستخدمة هي دلفي 2006.

الان في برنامجنا كيف نطبق هذا:

function MyAbs(I: Integer): Integer; {$IFDEF VER170} inline; {$ENDIF}

لاحظوا وضعنا الكلمة Inline بين العبارتين {$IFDEF VER170} و {$ENDIF} اي فقط في حال كانت نسخة ال compiler هي دلفي 2005 يتم وضع التابع Inline و الا يبقى التابع عادي.

ملاحظة:

VER170: رقم نسخة دلفي 2005

VER180: رقم نسخة دلفي 2006

الان النسخ من 1 الى 7 محجوزة لنسخ Turbo Pascal اي VER1 هي نسخة Compiler ل Turbo Pascal

النسخ من 8 الى 15 هي نسخ دلفي من 1 الى دلفي 7

و النسخة 16 هي دلفي 8

الموضوع مترجم بتصرف من كتاب Inside Delphi 2006

امل ان اكون قد افدتكم بقليل من المعلومات

بانتظار اسئلتكم و مناقشتكم و ارائكم

رب اجعلني مقيم الصلاة ومن ذريتي ربنا وتقبل دعاء.

لا تنسى: "العقل مثل العضلة كلما استخدمته أكثر كلما ازدادت قوته"

#2

موضوع جميل، بانتظار المزيد

d4baa0.gif
#3

موضوع جيد بل مهم كثيرا

نرجوا المزيد :rolleyes:

يقول المثل الشعبي الجزائري

( أقري علمك و علما لا تدريه ، و قل ربي زدني علما ، و فوق كل ذى علم عليم )

اللهم زدنا و لا تنقصنا و أعطنا و لا تحرمنا

#4

موضوع يستحق كل تنويه بارك الله فيك أخي وائل.

أصبحنا نفتقد لمثل هذه المواضيع في منتدى الديلفي.

بالنسبة لفائدة إستعمال inline فهي لزيادة سرعة التنفيذ لأن كل إستدعاء لدالة أو إجرائية هو مجموعة من التعليمات الإضافية التي تأخذ جزءا من الوقت عند إستدعائها.

فيجب أن ترى أن كل BEGIN هي مجموعة من تعليمات الأسمبلي على هذا النحو مثلا:

		push	ebp
		mov	ebp, esp
		push	ecx
		push	ebx
		push	esi
		push	edi

وكذلك كل END توافقها مجموعة من تعليمات لغة التجميع

				 mov	 eax, esi
				 pop	 edx
				 pop	 esi
				 pop	 ebx
				 retn

فعندما تستعمل inline فإنك لا تعرف دالة بالمعنى عام لدالة بل تعرف مجموعة من التعليمات التي سيتم حقنها مباشرة عند كل ذكر لأسم الدالة, فليس هناك أية عملية إستدعاء, بل تسلسل عادي لتعليمات assembly.

وهذا هو السبب في أن إستغمالها يربح الوقت اللازم لتهييء stak عند إستدعاء الدوال العادية. لكن هناك قيود لإستعمالها, فهي لها إيجابيات وكذلك سلبيات.

الإيجابيات ذكرت أعلاه, وسلبياتها هي تضخيم الملف التنفيذيلأن كود الدلة سيتكرر داخل كود البرنامج في كل مكان يتم منه إستدعؤها فيه.

لهذ فإستعمالها يستدعي التريث قليلا, فلا نستعملها إذا كانت الدالة تستدعى في أماكن كثيرة من الرنامج, وكذلك لا تستعمل إذا كان نص كود الدالة كبير جد, ونستعملها إذا كان عامل الوقت مطلوب فعلا.....

سأتغيب عن المنتدى وستكون لي زيارات خفيفة بين الحين و الحين

يا صاحب الهم إن الهم منفرج.....................أبشر بخير فإن الفارج الله.

اليأس يقطع أحيانا بصاحبه..........................لا تيأس فإن الكافي الله.

الله يحدث بعد العسر ميسرة.........................لا تفزعن فإن الصانع الله.

إذا بليت فثق بالله وارض به...................فإن الذي يكشف البلوى هو الله.

و الله ما لك غير الله من أحد.......................فحسبك الله في كل لك الله

أخوكم مراد

27_06_06_05_21_41_1151410901logo_adl_wal_ihsan.jpg

#5

كنت انتظر تعقيبك على هذا الموضوع. الله يجزيك الخير على الزيادة في التوضيح

في الحقيقة نتمى ان يبقى قسم دلفي عامر بمواضيع ممتازة و مفيدة و نرجوا ان تساعدنا في ذلك

اقتباس
فيجب أن ترى أن كل BEGIN هي مجموعة من تعليمات الأسمبلي على هذا النحو

وكذلك كل END توافقها مجموعة من تعليمات لغة التجميع

و ايضا هناك البارمترات التي يتم اعادة حجزها فهي ايضا تأخذ وقت اضافي

في C++ هناك Inline Method حيث تكون عندما تكتب جسم التابع داخل الصنف Class فهل يتم معاملته كما في دلفي؟

ارجوا من لديه خبرة في C++ ان يفيدنا في ذلك

رب اجعلني مقيم الصلاة ومن ذريتي ربنا وتقبل دعاء.

لا تنسى: "العقل مثل العضلة كلما استخدمته أكثر كلما ازدادت قوته"

#6

السلام عليكم ,,

الأخ وليد وضع رابط لموضوع في قسم الـ++C حول الـ inline functions في delphi ,,

هذا رابط الموضوع بلغة ++C لإكمال النقاش حول الموضوع بما أنه تم ذكر ++C في هذا الموضوع :)

هل تعرف ما فائدة الـ Inline Functions في ++c ؟

تحياتي أخ وليد ,,

مواضيع مشابهة