گسترش در خط

از ویکی‌پدیا، دانشنامهٔ آزاد

در علوم کامپیوتر گسترش در خط به معنی جایگزینی دستور فراخوانی یک تابع با متن خود آن تابع است. این کار به صورت دستی توسط برنامه‌نویس (به‌طور مثال به وسیله کلیدواژه‌های تعریف‌شده زبان) یا به صورت خودکار (توسط کامپایلر) انجام می‌شود.

این عمل بسیار شبیه به گسترش ماکرو است، ولی تفاوت عمدهای که گسترش در خط دارد این است که در زمان کامپایل انجام می‌شود ولی جایگزینی ماکروها در زمان پیش از کامپایل صورت می‌گیرد و متن برنامه ورودی تحلیل‌گر کامپایلر تغییر پیدا می‌کند. مزیت گسترش در خط نسبت به ماکرو این است که ایرادهای ماکرو را از قبیل احتمال رعایت نشدن اولویت عملیات ریاضی ندارد.

گسترش در خط تأثیر زیادی بر عملکرد کامپایلر دارد [۱] و یکی از بهینه‌سازی‌های ساده در کامپایلرها است که می‌تواند منجر به افزایش سرعت شود. البته گسترش در خط بیش از حد به دلیل اشغال کردن قسمت عمده‌ای حافظه نهان دستورها ممکن است باعث کندتر شدن برنامه گردد. بررسی عملکرد گسترش در خط در مقاله‌های دهه ۸۰ تا ۹۰ میلادی در سال ۱۹۹۹ توسط Jones و Marlow انجام شده‌است [۲].

بررسی کلی[ویرایش]

همان‌طور که گفته شد گسترش در خط از گسترش ماکرو سریع‌تر است و دلیل آن این است که در گسترش در خط سر بار فراخوانی تابع (که شامل ذخیره‌سازی مقادیر ثبّات‌ها و پرش است) دیگر وجود ندارد و خطوط مربوط به متن تابع مستقیماً اجرا می‌شود. از جهت دیگر اما گسترش در خط می‌تواند باعث هدر رفتن حافظه شود زیرا به ازای هر محل از برنامه که تابع فراخوانی شده باشد، یک رونوشت از متن تابع در آن‌جا اضافه می‌گردد. در نتیجه استفاده از گسترش در خط معمولاً در حالتی که متن تابع کوتاه است می‌تواند کارساز باشد.

به‌طور مثال در زبان ++C، توابعی که عضو کلاس‌ها هستند در صورتی که در داخل تعریف کلاس تعریف شوند به‌طور پیش‌فرض در خط سازی می‌شوند. در غیر این صورت برنامه‌نویس می‌بایستی صراحتاً از کلیدواژه inline استفاده کند. همچنین دلیل اشاره شده بالا، کامپایلر این زبان، توابع بزرگی که برنامه‌نویس صراحتاً درخواست خطی‌سازی آن‌ها را کرده‌است، خطی‌سازی نمی‌کند.

همچنین در برنامه‌نویسی تابعی، گسترش در خط معمولاً پس از تبدیل کاهش بتا صورت می‌گیرد.

تأثیر آن بر روی عملکرد برنامه[ویرایش]

گسترش در خط سر بار فراخوانی تابع را کاهش می‌دهد ولی دلیل اصلی انجام آن زمینه‌سازی برای بهینه‌سازی‌های دیگر است زیرا گسترش در خط اندازه برنامه را زیاد می‌کند و بهینه‌سازی‌های بهتری بر روی برنامه‌های با اندازه بزرگتر قابل انجام است [۳]. پیش‌بینی اثر نهایی گسترش در خط بر روی عملکرد برنامه پیچیده‌است و به دلیل اثر آن بر روی حافظه نهان دستورها، بسته به برنامه و اندازه حافظه نهان متفاوت است [۱].

همچنین میزان این تأثیر بین زبان‌های مختلف متفاوت است. به‌طور مثال در زبان‌های سطح پایین مانند C و Fortran، به اندازه ۱۰–۲۰٪ افزایش سرعت مشاهده می‌شود، در حالی که در زبان‌های سطح بالاتر و انتزاعی‌تر گسترش در خط می‌تواند چندین مرحله از اجرای برنامه را حذف کند. مثال این‌گونه زبان‌ها، زبان سلف (زبان_برنامه‌نویسی) است که ضریب بهبودی بین ۴ تا ۵۵ داشته‌است [۲].

پشتیبانی کامپایلرها[ویرایش]

کامپایلرها از روش‌های مختلفی برای تشخیص اینکه چه تابعی را گسترش در خط دهند، استفاده می‌کنند. این روش‌ها عبارتند از:

  • مشخص شدن توابع توسط برنامه‌نویس
  • دستورهای خط فرمان
  • تشخیص خودکار کامپایلر

گسترش در خط در بسیاری از زبان‌ها به صورت خودکار توسط کامپایلر صورت می‌گیرد و کامپایلر بررسی می‌کند که آیا این عمل برای سرعت برنامه مفید است یا خیر. همچنین کلیدواژه‌هایی مانند inline نیز در برخی از زبان‌ها به عنوان پیشنهادی از طرف برنامه‌نویس برای در خطی سازی تابع وجود دارد. هرچند که همان‌طور که گفته شد این کلیدواژه‌ها کامپایلر را مجبور به انجام این عمل نمی‌کند.

به‌طور کلی توسعه‌دهندگان کامپایلرها از الگوریتم جستجوی کاشف و روش‌های مکاشفه‌ای در طراحی کامپایلر استفاده می‌کنند تا تشخیص دهند که در چه جایی نیاز به گسترش در خط می‌باشد.

پیاده‌سازی[ویرایش]

پس از تشخیص اینکه کدام توابع باید گسترش در خط داده شوند، انجام این عمل معمولاً ساده است. کامپایلر می‌تواند که این عمل را در یک نمایش میانی سطح بالا مانند درخت نحو انتزاعی انجام دهد یا اینکه گسترش در خط در سطح پایین صورت گیرد. در هر دو حالت کامپایلر ورودی‌های تابع را حساب می‌کند، آن‌ها را در متغیرهایی قرار می‌دهد و در نهایت متن تابع را با محل فراخوانی آن تابع، جایگزین می‌کند.

پیوند دهندهها نیز می‌توانند گسترش در خط انجام دهند. پیونددهنده‌ها توابعی را که از کتابخانه‌های دیگر استفاده شده و در متن برنامه اولیه موجود نبوده را گسترش در خط می‌دهند.

در مثال زیر یک گسترش در خط به صورت دستی انجام شده‌است:

int pred(int x) {
    if (x == 0)
       return 0;
    else
       return x - 1;
}

قبل از در خط سازی:

 int f(int y) {
     return pred(y) + pred(0) + pred(y+1);
 }

بعد از آن:

int f(int y) {
    int temp;
    if (y == 0) temp  = 0; else temp  = y       - 1; /* (1) */
    if (0 == 0) temp += 0; else temp += 0       - 1; /* (2) */
    if (y+1 == 0) temp += 0; else temp += (y + 1) - 1; /* (3) */
    return temp;
}

مقایسه با ماکرو[ویرایش]

پیش از C99 در زبان سی در خط سازی به کمک ماکروها انجام می‌شد. مزایای گسترش در خط نسبت به ماکروها عبارتند از:

  • در سی هنگام استفاده از ماکروها، نوع متغیرها بررسی نمی‌شود در صورتی که در فراخوانی توابع این کار باید صورت گیرد.
  • در سی ماکروها نمی‌توانند مقداری را برگردانند (استفاده از کلیدواژه return موجب برگرداندن تابعی می‌شود که در آن از ماکرو استفاده شده‌است). این در حالی است که توابع در خط سازی شده این قابلیت را دارند.
  • درک خطاهای کامپایل در برنامه‌هایی که از ماکرو استفاده کرده‌اند دشوار است زیرا خطا به خطی ارجاع می‌کند که پس از گسترش ماکرو در برنامه به وجود آمده‌است و همین امر کار اشکال‌زدایی را دشوار می‌کند.
  • به دلیل این‌که ماکروها صرفاً جایگزینی متن هستند، استفاده از آن‌ها ممکن است تبعات ناخواسته‌ای مانند نقض اولویت عملیات ریاضی را داشته باشد.

بی‌یارنه استراس‌تروپ خالق زبان سی پلاس‌پلاس نیز استفاده از توابع در خط را به جای ماکروها توصیه می‌کند.[۴]

منابع[ویرایش]

  1. ۱٫۰ ۱٫۱ Chen et al. 1993.
  2. Chen et al. 1993, 3.4 Function inline expansion, p. 14.
  3. "C: do {…} while(0)?". Retrieved 1 January 2018.