توسيع خطي

في مجال الحوسبة ، يُعدّ التوسيع المضمن ، أو التضمين ، تحسينًا يدويًا أو بواسطة المُصرّف، حيث يتم استبدال موضع استدعاء الدالة بجسم الدالة المُستدعاة. يُشبه التوسيع المضمن توسيع الماكرو ، ولكنه يحدث أثناء عملية التجميع دون تغيير شفرة المصدر (النص)، بينما يحدث توسيع الماكرو قبل التجميع، وينتج عنه نص مختلف تتم معالجته لاحقًا بواسطة المُصرّف .

يُعدّ تضمين التعليمات البرمجية تحسينًا مهمًا، لكن له تأثيرات معقدة على الأداء. [ 1 ] وكقاعدة عامة ، يُحسّن تضمين بعض التعليمات البرمجية السرعة بتكلفة ضئيلة جدًا من حيث المساحة، لكن الإفراط في تضمينها يُؤثر سلبًا على السرعة، نظرًا لاستهلاك التعليمات البرمجية المُضمّنة لجزء كبير من ذاكرة التخزين المؤقت للتعليمات ، فضلًا عن استهلاكها لمساحة كبيرة. وقد قدّم بيتون جونز ومارلو (1999) دراسة استقصائية للأبحاث الأكاديمية المحدودة حول تضمين التعليمات البرمجية من ثمانينيات وتسعينيات القرن الماضي. [ 2 ]

ملخص

يُشبه التوسيع المضمن توسيع الماكرو، حيث يضع المُصرّف نسخة جديدة من الدالة في كل موضع يتم استدعاؤها فيه. تعمل الدوال المضمنة بشكل أسرع قليلاً من الدوال العادية لتوفير تكاليف استدعاء الدوال، إلا أن ذلك يُؤثر سلبًا على الذاكرة. فإذا تم تضمين دالة ما عشر مرات، فسيتم إدراج عشر نسخ منها في الكود. لذا، يُعد التضمين الأمثل للدوال الصغيرة التي يتم استدعاؤها بشكل متكرر. في لغة C++، يتم تضمين الدوال الأعضاء في فئة ما افتراضيًا إذا تم تعريفها ضمن تعريف الفئة (دون الحاجة إلى استخدام الكلمة المحجوزة inline )؛ وإلا، فستكون الكلمة المحجوزة ضرورية. قد يتجاهل المُصرّف محاولة المبرمج لتضمين دالة، خاصةً إذا كانت كبيرة الحجم.

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

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

في الوضع الطبيعي، عند استدعاء دالة، ينتقل التحكم إلى تعريفها عبر أمر تفرع أو استدعاء. أما مع التضمين المباشر، فينتقل التحكم مباشرةً إلى كود الدالة، دون الحاجة إلى أمر تفرع أو استدعاء.

عادةً ما تُنفّذ المُترجمات التعليمات البرمجية باستخدام التضمين المباشر. تتطلب شروط الحلقات وأجسامها تقييمًا كسولًا . تتحقق هذه الخاصية عند تضمين التعليمات البرمجية لحساب شروط الحلقات وأجسامها. تُعدّ اعتبارات الأداء سببًا آخر لتضمين التعليمات البرمجية.

في سياق لغات البرمجة الوظيفية ، عادة ما يتبع التوسيع المضمن تحويل الاختزال بيتا . [ 3 ]

قد يقوم المبرمج بتضمين دالة يدويًا عبر نسخ ولصق الكود المصدري ، كعملية لمرة واحدة . مع ذلك، تُفضّل طرق أخرى للتحكم في التضمين (انظر أدناه)، لأنها لا تتسبب في ظهور أخطاء ناتجة عن إغفال المبرمج لنسخة مكررة (ربما معدلة) من جسم الدالة الأصلية أثناء إصلاح خطأ في الدالة المُضمّنة.

التأثير على الأداء

يتمثل الأثر المباشر لهذا التحسين في تحسين الأداء الزمني (عن طريق إزالة تكلفة الاستدعاء)، على حساب زيادة استخدام المساحة [ أ ] (بسبب تكرار جسم الدالة). ويُهيمن توسيع الكود الناتج عن تكرار جسم الدالة، باستثناء الحالات البسيطة، [ ب ] وبالتالي فإن الأثر المباشر للتوسيع المضمن هو تحسين الوقت على حساب المساحة.

مع ذلك، تكمن الفائدة الرئيسية للتوسيع المضمن في إتاحة المزيد من التحسينات وجدولة أفضل، وذلك بفضل زيادة حجم جسم الدالة، حيث يصبح التحسين الأمثل ممكنًا مع الدوال الأكبر حجمًا. [ 4 ] أما التأثير النهائي للتوسيع المضمن على السرعة فهو معقد، نظرًا لتأثيراته المتعددة على أداء نظام الذاكرة (وخاصةً ذاكرة التخزين المؤقت للتعليمات )، والذي يُهيمن على الأداء في المعالجات الحديثة: فبحسب البرنامج المحدد وذاكرة التخزين المؤقت، قد يؤدي تضمين دوال معينة إلى زيادة الأداء أو انخفاضه. [ 1 ]

يختلف تأثير تضمين التعليمات البرمجية باختلاف لغة البرمجة والبرنامج، وذلك تبعًا لاختلاف درجات التجريد. ففي لغات البرمجة الإجرائية منخفضة المستوى مثل C و Fortran، يُحسّن التضمين عادةً السرعة بنسبة 10-20%، مع تأثير طفيف على حجم الكود، بينما في اللغات الأكثر تجريدًا، قد يكون تأثيره أكبر بكثير، نظرًا لعدد الطبقات التي يُزيلها التضمين، ومن الأمثلة البارزة على ذلك لغة Self ، حيث حقق أحد المترجمات تحسينًا في الأداء يتراوح بين 4 و 55 ضعفًا عند استخدام التضمين. [ 2 ]

تتمثل الفوائد المباشرة لإلغاء استدعاء الدالة فيما يلي:

لكن الفائدة الرئيسية من تضمين الدوال تكمن في التحسينات الإضافية التي يتيحها. إذ يمكن إجراء تحسينات تتجاوز حدود الوظائف دون الحاجة إلى تحسينات بين الإجراءات (IPO): فبمجرد تنفيذ التضمين، يصبح من الممكن إجراء تحسينات إضافية داخل الإجراء نفسه ("تحسينات شاملة") على جسم الدالة الموسّع. على سبيل المثال:

  • يمكن في كثير من الأحيان نشر الثابت الذي يتم تمريره كوسيط إلى جميع مثيلات المعلمة المطابقة، أو يمكن "رفع" جزء من الدالة خارج الحلقة (عبر نقل التعليمات البرمجية الثابتة للحلقة ).
  • يمكن تخصيص السجلات عبر جسم الدالة الأكبر.
  • يمكن إجراء تحسينات عالية المستوى، مثل تحليل الهروب وتكرار الذيل ، على نطاق أوسع وتكون أكثر فعالية، لا سيما إذا كان المترجم الذي ينفذ هذه التحسينات يعتمد بشكل أساسي على التحليل داخل الإجراء. [ 5 ] يمكن القيام بذلك دون تضمين، ولكنه يتطلب مترجمًا ورابطًا أكثر تعقيدًا بشكل ملحوظ (في حالة وجود المُستدعي والمُستدعى في وحدات تجميع منفصلة).

في المقابل، قد تسمح مواصفات اللغة في بعض الحالات للبرنامج بوضع افتراضات إضافية حول وسائط الإجراءات، وهي افتراضات لا يمكنه وضعها بعد تضمين الإجراء، مما يحول دون بعض التحسينات. تتتبع المترجمات الأكثر تطورًا (مثل مترجم غلاسكو هاسكل (GHC)) هذا الأمر، بينما يفقد التضمين البسيط هذه المعلومات.

ومن الفوائد الأخرى لتضمين البيانات في نظام الذاكرة ما يلي:

  • يؤدي حذف التفرعات والحفاظ على التعليمات البرمجية التي تُنفذ متقاربة في الذاكرة إلى تحسين أداء ذاكرة التخزين المؤقت للتعليمات من خلال تحسين موضعية المرجع (الموضع المكاني وتسلسل التعليمات). هذا التأثير أقل من التحسينات التي تستهدف التسلسل تحديدًا، ولكنه ذو أهمية. [ 6 ]

تتمثل التكلفة المباشرة للتضمين في زيادة حجم الكود، نتيجةً لتكرار جسم الدالة في كل موقع استدعاء. مع ذلك، لا يحدث ذلك دائمًا، لا سيما في حالة الدوال القصيرة جدًا، حيث يكون جسم الدالة أصغر من حجم استدعاء الدالة (عند المستدعي، بما في ذلك معالجة الوسائط والقيمة المُعادة)، مثل دوال الوصول أو التعديل البسيطة (دوال الجلب والتعيين)؛ أو في حالة دالة تُستخدم في مكان واحد فقط، حيث لا يتم تكرارها. بالتالي، يمكن تقليل التضمين أو الاستغناء عنه عند تحسين حجم الكود، كما هو الحال غالبًا في الأنظمة المُدمجة .

يُؤثر تضمين التعليمات البرمجية سلبًا على الأداء، نظرًا لتوسع حجم الكود (بسبب التكرار) الذي يُضعف أداء ذاكرة التخزين المؤقت للتعليمات. [ 7 ] ويكون هذا التأثير بالغ الأهمية إذا كانت مجموعة عمل البرنامج (أو جزء مُستخدم بكثرة من الكود) قبل التوسع تتسع في مستوى واحد من التسلسل الهرمي للذاكرة (مثل ذاكرة التخزين المؤقت L1 )، ولكنها لم تعد تتسع بعد التوسع، مما يؤدي إلى تكرار حالات عدم العثور على البيانات في ذاكرة التخزين المؤقت في ذلك المستوى. ونظرًا للاختلاف الكبير في الأداء بين مستويات التسلسل الهرمي المختلفة، فإن هذا يُؤثر سلبًا على الأداء بشكل ملحوظ. فعلى أعلى مستوى، قد يُؤدي ذلك إلى زيادة أخطاء الصفحات ، أو تدهور كارثي في ​​الأداء بسبب التبديل المُفرط ، أو حتى فشل البرنامج في العمل تمامًا. وهذا الأخير نادر الحدوث في تطبيقات سطح المكتب والخوادم الشائعة، حيث يكون حجم الكود صغيرًا نسبيًا مقارنةً بالذاكرة المتاحة، ولكنه قد يُشكل مشكلة في البيئات ذات الموارد المحدودة مثل الأنظمة المُدمجة. ومن طرق التخفيف من هذه المشكلة تقسيم الدوال إلى مسار مُضمن صغير ( مسار سريع )، ومسار غير مُضمن أكبر (مسار بطيء). [ 7 ]

يُعدّ تضمين التعليمات البرمجية بشكل مباشر (Inlineing) الذي يُؤثر سلبًا على الأداء مشكلةً خاصةً بالوظائف الكبيرة التي تُستخدم في أماكن متعددة، إلا أن تحديد نقطة التعادل التي يبدأ عندها التضمين بانخفاض الأداء أمرٌ صعب، ويعتمد عمومًا على الحمل الدقيق، لذا يُمكن تحسينه يدويًا أو باستخدام ملف تعريف الأداء . [ 8 ] تُشابه هذه المشكلة تحسينات توسيع الكود الأخرى، مثل فكّ حلقات التكرار ، والتي تُقلل أيضًا من عدد التعليمات المُعالجة، ولكنها قد تُؤدي إلى انخفاض الأداء بسبب ضعف أداء الذاكرة المؤقتة.

إن التأثير الدقيق لتقنية التضمين المباشر على أداء الذاكرة المخبئية معقد. ففي حالة أحجام الذاكرة المخبئية الصغيرة (أصغر بكثير من مجموعة العمل قبل التوسيع)، يطغى عامل زيادة التسلسل، مما يُحسّن أداء الذاكرة المخبئية. أما في حالة أحجام الذاكرة المخبئية القريبة من مجموعة العمل، حيث يؤدي التضمين المباشر إلى توسيع مجموعة العمل بحيث لا تتسع لها الذاكرة المخبئية، فإن هذا العامل يطغى أيضًا، مما يؤدي إلى انخفاض أداء الذاكرة المخبئية. وفي حالة أحجام الذاكرة المخبئية الأكبر من مجموعة العمل، يكون تأثير التضمين المباشر على أداء الذاكرة المخبئية ضئيلاً. علاوة على ذلك، يمكن للتغييرات في تصميم الذاكرة المخبئية، مثل إعادة توجيه الحمل ، أن تُعوّض الزيادة في حالات عدم العثور على البيانات في الذاكرة المخبئية. [ 9 ]

دعم المُترجم

تستخدم المترجمات آليات متنوعة لتحديد استدعاءات الدوال التي يجب تضمينها؛ وتشمل هذه الآليات تلميحات يدوية من المبرمجين لدوال محددة، بالإضافة إلى التحكم الشامل عبر خيارات سطر الأوامر . يتم التضمين تلقائيًا بواسطة العديد من المترجمات في لغات برمجة متعددة، بناءً على تقييم جدوى التضمين، بينما في حالات أخرى، يمكن تحديده يدويًا عبر توجيهات المترجم ، عادةً باستخدام كلمة مفتاحية أو توجيه يُسمى `include` inline. عادةً ما يشير هذا التوجيه فقط إلى أن التضمين مرغوب فيه، وليس إلزاميًا، وتختلف قوة هذا التوجيه باختلاف اللغة والمترجم.

عادةً، يضع مطورو المترجمات مشكلات الأداء المذكورة أعلاه في الاعتبار، ويقومون بدمج أساليب استدلالية في مترجماتهم لاختيار الوظائف التي يجب تضمينها لتحسين الأداء، بدلاً من إضعافه، في معظم الحالات.

تطبيق

بمجرد أن يقرر المترجم تضمين دالة معينة، تصبح عملية التضمين نفسها بسيطة في الغالب. وبحسب ما إذا كان المترجم يُضمّن الدوال في أكواد لغات مختلفة، فإنه يستطيع التضمين إما على تمثيل وسيط عالي المستوى (مثل أشجار بناء الجملة المجردة ) أو على تمثيل وسيط منخفض المستوى. في كلتا الحالتين، يقوم المترجم ببساطة بحساب الوسائط ، وتخزينها في متغيرات تُطابق وسائط الدالة، ثم يُدرج جسم الدالة في موضع الاستدعاء.

يمكن للرابط أيضًا تضمين الدوال. عند تضمين الرابط للدوال، قد يُضمّن دوالًا غير متوفرة مصادرها، مثل دوال المكتبات (انظر تحسين وقت الربط ). كما يمكن لنظام التشغيل تضمين دالة. يُمكن لتضمين وقت التشغيل استخدام معلومات التنميط الديناميكي لاتخاذ قرارات أفضل بشأن الدوال التي يجب تضمينها، كما هو الحال في مُصرّف Java HotSpot . [ 10 ]

فيما يلي مثال بسيط على التوسيع المضمن الذي يتم تنفيذه "يدويًا" على مستوى المصدر في لغة C :

دالة التنبؤ ( عدد صحيح x ) { إذا كان ( x == 0 ) { إرجاع 0 ؛ } وإلا { إرجاع x - 1 ؛ } }

قبل التضمين:

دالة int ( int y ) { return pred ( y ) + pred ( 0 ) + pred ( y + 1 ); }

بعد تضمين النص:

دالة عددية ( عدد صحيح y ) { عدد صحيح مؤقت tmp ؛// (1) إذا كانت ( y == 0 ) { tmp = 0 ; } وإلا { tmp = y - 1 ; }// (2) إذا كان ( 0 == 0 ) { tmp += 0 ; } وإلا { tmp += 0 - 1 ; }// (3) إذا كان ( y + 1 == 0 ) { tmp += 0 ; } وإلا { tmp += ( y + 1 ) - 1 ; }return tmp ; }

لاحظ أن هذا مجرد مثال. في تطبيق C فعلي، يُفضّل استخدام ميزة تضمين التعليمات البرمجية، مثل وحدات الماكرو المُعاملة أو الدوال المضمنة، لإخبار المُصرّف بتحويل التعليمات البرمجية بهذه الطريقة. يسرد القسم التالي طرقًا لتحسين هذه التعليمات البرمجية.

تضمين البيانات عن طريق توسيع وحدات الماكرو للتجميع

توفر وحدات الماكرو في لغة التجميع طريقة بديلة للتضمين المباشر، حيث يمكن عادةً إنشاء سلسلة من التعليمات مباشرةً من خلال توسيع الماكرو من عبارة مصدر ماكرو واحدة (مع صفر أو أكثر من المعاملات). قد يكون أحد هذه المعاملات خيارًا لإنشاء روتين فرعي منفصل لمرة واحدة يحتوي على السلسلة، ثم تتم معالجته عن طريق استدعاء مضمن للدالة. مثال:

نقل من=المصفوفة1، إلى=المصفوفة2، مضمن=لا

الأساليب الاستدلالية

تم استكشاف مجموعة متنوعة من الأساليب الاستدلالية المختلفة للتضمين المباشر. عادةً، يمتلك خوارزمية التضمين المباشر ميزانية محددة من التعليمات البرمجية (زيادة مسموح بها في حجم البرنامج) وتهدف إلى تضمين مواقع الاستدعاء الأكثر أهمية دون تجاوز تلك الميزانية. من هذا المنطلق، تُصمم العديد من خوارزميات التضمين المباشر عادةً على غرار مسألة حقيبة الظهر . [ 11 ] لتحديد مواقع الاستدعاء الأكثر أهمية، يجب على خوارزمية التضمين المباشر تقدير فائدتها، أي الانخفاض المتوقع في وقت التنفيذ. عادةً، تستخدم خوارزميات التضمين المباشر معلومات تحليل الأداء حول تكرار تنفيذ مسارات التعليمات البرمجية المختلفة لتقدير الفوائد. [ 12 ]

بالإضافة إلى معلومات التوصيف، تطبق المترجمات الأحدث التي تعمل في الوقت المناسب العديد من الأساليب الاستدلالية الأكثر تقدمًا، مثل: [ 5 ]

  • التكهن بمسارات التعليمات البرمجية التي ستؤدي إلى أفضل تقليل في وقت التنفيذ (عن طريق تمكين تحسينات المترجم الإضافية نتيجة للتضمين) وزيادة الفائدة المتصورة لهذه المسارات.
  • تعديل عتبة الفائدة مقابل التكلفة للتضمين بشكل تكيفي بناءً على حجم وحدة التجميع وكمية التعليمات البرمجية المضمنة بالفعل.
  • يتم تجميع الإجراءات الفرعية في مجموعات، ثم تضمين المجموعات بأكملها بدلاً من الإجراءات الفرعية المفردة. هنا، يخمن الأسلوب الاستدلالي المجموعات من خلال تجميع تلك الطرق التي يؤدي تضمين مجموعة فرعية مناسبة منها إلى أداء أسوأ من عدم تضمين أي شيء على الإطلاق.

فوائد

يُعدّ التوسيع المضمن بحد ذاته تحسينًا، إذ يُزيل الحمل الزائد من الاستدعاءات، لكن أهميته تكمن في كونه تحويلًا تمكينيًا . بمعنى آخر، بمجرد أن يُوسّع المُصرّف جسم الدالة في سياق موقع استدعائها - غالبًا مع وسائط قد تكون ثوابت ثابتة - فإنه قد يتمكن من إجراء مجموعة متنوعة من التحويلات التي لم تكن ممكنة من قبل. على سبيل المثال، قد يكون التفرع الشرطي صحيحًا دائمًا أو خاطئًا دائمًا في موقع الاستدعاء هذا. وهذا بدوره قد يُتيح حذف التعليمات البرمجية غير المستخدمة ، أو نقل التعليمات البرمجية غير المتأثرة بالحلقات ، أو حذف متغيرات الاستقراء .

في مثال لغة C المذكور في القسم السابق، تتوافر فرص عديدة لتحسين الأداء. وقد يتبع المترجم التسلسل التالي من الخطوات:

  • لا تقوم العبارات tmp += 0الموجودة في السطرين (2) و(3) بأي شيء. يمكن للمترجم حذفها.
  • الشرط 0 == 0صحيح دائمًا، لذلك يمكن للمترجم استبدال السطر المميز بـ (2) بالنتيجة tmp += 0(التي لا تفعل شيئًا).
  • يمكن للمترجم إعادة كتابة الشرط y+1 == 0إلى y == -1.
  • يمكن للمترجم اختزال التعبير (y + 1) - 1إلى y.
  • yلا يمكن أن يكون كلا التعبيرين y+1مساوياً للصفر. هذا يسمح للمترجم بحذف أحد الاختبارات.
  • في عبارات مثل أن if (y == 0) return yقيمة yمعروفة في النص، ويمكن تضمينها.

تبدو الوظيفة الجديدة كالتالي:

دالة int ( int y ) { إذا كان ( y == 0 ) { أرجع 0 ؛ } إذا كان ( y == -1 ) { أرجع -2 ؛ } أرجع 2 * y - 1 ؛ }

الحدود

لا يُمكن دائمًا توسيع الاستدعاءات المضمنة بالكامل، وذلك بسبب التكرار : فالتوسيع المضمن المتكرر للاستدعاءات لا يُنهي العملية. توجد حلولٌ مُتعددة، مثل توسيع مقدارٍ مُحدد، أو تحليل مُخطط الاستدعاءات وكسر الحلقات عند عُقدٍ مُعينة (أي عدم توسيع بعض الحواف في حلقة تكرارية). [ 13 ] تظهر مُشكلة مُماثلة في توسيع الماكرو، حيث لا يُنهي التوسيع التكراري العملية، ويتم حلها عادةً بمنع استخدام الماكرو التكراري (كما هو الحال في لغتي C وC++).

مقارنة مع وحدات الماكرو

تقليديًا، في لغات مثل C ، كان يتم توسيع الدوال المضمنة على مستوى الكود المصدري باستخدام وحدات ماكرو مُعاملة . يوفر استخدام الدوال المضمنة الحقيقية، كما هو متوفر في C99 ، العديد من المزايا مقارنةً بهذا الأسلوب:

  • في لغة C، لا تقوم استدعاءات الماكرو بإجراء فحص النوع ، أو حتى التحقق من أن الوسائط سليمة التكوين، في حين أن استدعاءات الدوال عادة ما تفعل ذلك.
  • في لغة C، لا يمكن للماكرو استخدام الكلمة المفتاحية return بنفس معنى استخدامها في الدالة (إذ سيؤدي ذلك إلى إنهاء الدالة التي طلبت التوسيع، وليس الماكرو). بعبارة أخرى، لا يمكن للماكرو إرجاع أي قيمة ليست نتيجة آخر تعبير تم استدعاؤه داخله.
  • بما أن وحدات الماكرو C تستخدم مجرد استبدال نصي، فقد يؤدي ذلك إلى آثار جانبية غير مقصودة وعدم كفاءة بسبب إعادة تقييم الوسائط وترتيب العمليات .
  • غالباً ما يصعب فهم أخطاء المُصرّف داخل وحدات الماكرو، لأنها تشير إلى الكود المُوسّع، وليس إلى الكود الذي كتبه المبرمج. لذا، فإن معلومات تصحيح الأخطاء للكود المُضمّن عادةً ما تكون أكثر فائدة من تلك الخاصة بالكود المُوسّع بواسطة وحدات الماكرو.
  • يصعب أو يستحيل التعبير عن العديد من البنى باستخدام وحدات الماكرو، أو أنها تستخدم صيغة مختلفة تمامًا. تستخدم الدوال المضمنة نفس صيغة الدوال العادية، ويمكن تضمينها وإخراجها بسهولة حسب الرغبة.

يمكن للعديد من المترجمات أيضًا توسيع بعض الدوال المتكررة بشكل مضمن ؛ [ 14 ] عادةً ما تكون وحدات الماكرو المتكررة غير قانونية.

يحب بيارن ستروستروب ، مصمم لغة C++، التأكيد على ضرورة تجنب وحدات الماكرو قدر الإمكان، ويدعو إلى الاستخدام المكثف للوظائف المضمنة.

أساليب الاختيار

تُدمج العديد من المترجمات الدوال بشكل مكثف كلما كان ذلك مفيدًا. ورغم أن هذا قد يؤدي إلى زيادة حجم الملفات التنفيذية ، إلا أن الدمج المكثف أصبح مرغوبًا فيه بشكل متزايد مع ازدياد سعة الذاكرة بوتيرة أسرع من سرعة المعالج. يُعد الدمج تحسينًا بالغ الأهمية في لغات البرمجة الوظيفية والبرمجة كائنية التوجه ، والتي تعتمد عليه لتوفير سياق كافٍ لدوالها الصغيرة عادةً، مما يجعل التحسينات التقليدية فعالة.

الدعم اللغوي

لا توفر العديد من اللغات، بما في ذلك جافا واللغات الوظيفية، بنيات لغوية للدوال المضمنة، ولكن غالبًا ما تقوم مترجماتها أو مفسراتها بتوسيع الدوال المضمنة بشكل مكثف. [ 5 ] بينما توفر لغات أخرى بنيات للتلميحات الصريحة، عادةً كتعليمات للمترجم (براغمات).

تحتوي لغة Ada على توجيه برمجي للوظائف المضمنة.

يمكن تعريف الدوال في لغة Common Lispinline على أنها مضمنة من خلال الإعلان على النحو التالي: [ 15 ]

( إعلان ( إرسال مضمن )) ( تعريف دالة إرسال ( x ) ( استدعاء دالة ( الحصول على ( سيارة x ) ' إرسال ) x ))

يحاول مترجم لغة هاسكل GHC تضمين الدوال أو القيم الصغيرة بما يكفي، ولكن يمكن الإشارة إلى التضمين بشكل صريح باستخدام توجيه اللغة: [ 16 ]

key_function :: Int -> String -> ( Bool , Double ) {-# INLINE key_function #-}

لغة C ولغة C++

تحتوي لغتا C و C++inline على كلمة مفتاحية تُشير إلى أن تضمين الدوال قد يكون مفيدًا؛ ومع ذلك، في الإصدارات الأحدث، يتمثل الغرض الرئيسي منها في تغيير مستوى رؤية الدالة وسلوك ربطها. [ 17 ]

في لغة C++، يتم تضمين طريقة فئة معينة داخل جسم الفئة ضمنيًا.

كوتلين

في لغة Kotlin ، inlineيتم تضمين الدالة مباشرةً، ويمكن الإشارة إلى عدم تضمينها باستخدام ` noinline.`. inlineيقوم هذا بنسخ بايت كود الدالة إلى موقع الاستدعاء، وتضمين وسائط تعبير لامدا، وإزالة تكلفة استدعاء الدالة وتخصيص ذاكرة تعبير لامدا. [ 18 ] في لغة Java المكافئة (أو بايت كود Java )، يتم تمثيل الدالة كمنطق في موقع الاستدعاء.

الصدأ

في لغة Rust ، يتم تضمين الدوال تلقائيًا بواسطة المُصرّف. [ 19 ] توفر Rust #[inline]خاصية تُشير إلى المُصرّف بضرورة تضمين دالة ما، لكنها لا تضمن ذلك؛ فقد يتجاهل المُصرّف حتى هذه الخاصية #[inline(always)]. في وضع التصحيح، لن يقوم المُصرّف بالتضمين أبدًا. [ 20 ]

انظر أيضاً

ملحوظات

  1. استخدام المساحة هو "عدد التعليمات"، وهو يشمل استخدام مساحة وقت التشغيل وحجم الملف الثنائي .
  2. يتقلص حجم الكود فعليًا بالنسبة للوظائف القصيرة جدًا، حيث تكون تكلفة الاستدعاء أكبر من جسم الوظيفة، أو الوظائف ذات الاستخدام الواحد، حيث لا يحدث تكرار.

مراجع

  1. 1 2 تشين وآخرون 1993 .
  2. جونز، سيمون بيتون؛ مارلو، سيمون (يوليو 2002). "أسرار مُضمِّن مُصرِّف غلاسكو هاسكل" . مجلة البرمجة الوظيفية . 12 ( 4-5 ): 393-434 . doi : 10.1017/S0956796802004331 . ISSN 1469-7653 . 
  3. Chen et al. 1993 ، 3.4 توسيع الدالة المضمنة، ص. 14.
  4. 1 2 3بروكوبيك وآخرون، خوارزمية استبدال مضمنة تزايدية مدفوعة بالتحسين للمترجمات الفورية، منشور CGO'19 حول المضمن المستخدم في مترجم Graal لـ JVM
  5. Chen et al. 1993 , 3.4 Function inline expansion, p. 19–20.
  6. 1 2 بنيامين بولان (8 أغسطس 2013). "زيادة غير عادية في السرعة: الحجم مهم" .
  7. انظر على سبيل المثال نظام التحسين التكيفي المؤرشف بتاريخ 2011-08-09 في Wayback Machine في Jikes RVM لـ Java.
  8. Chen et al. 1993 ، 3.4 توسيع الدالة المضمنة ، ص. 24-26.
  9. وصف للدالة المضمنة المستخدمة في مترجم Graal JIT للغة Java
  10. شيفلر، تحليل الاستبدال المضمن للغة برمجة هيكلية
  11. ماثيو أرنولد، ستيفن فينك، فيفيك ساركار، وبيتر إف. سويني، دراسة مقارنة للأساليب الاستدلالية الثابتة والقائمة على الملفات الشخصية للتضمين
  12. بيتون جونز ومارلو 1999 ، 4. ضمان الإنهاء، ص 6-9.
  13. دلالات تضمين الإجراءات الفرعية المتكررة " بقلم هنري ج. بيكر
  14. إعلان INLINE و NOTINLINE في Common Lisp HyperSpec
  15. 7.13.5.1. توجيه INLINE الفصل 7. خصائص لغة GHC
  16. "محدد مضمن - cppreference.com" . en.cppreference.com . تم الاطلاع عليه بتاريخ 10 يناير 2026 .
  17. "الدوال المضمنة" . kotlinlang.org . JetBrains sro 23 يونيو 2025.
  18. "توليد الكود - مرجع Rust" . doc.rust-lang.org . تم الاطلاع عليه بتاريخ 2025-05-01 .
  19. "متى نستخدم # [ inline ] - دليل مطوري المكتبة القياسية" . std-dev-guide.rust-lang.org . تم الاطلاع عليه بتاريخ 1 مايو 2025 .