User:Amirmosio/sandbox

بهینه سازی متقابلانه (IPO) مجموعه ای از تکنیک های کامپایلر است برای بهبود عملکرد در برنامه هایی که شامل توابعی پر استفاده با اندازه متوسط و کم هستند.

IPO با بقیه بهینه ساز های کامپایلر متفاوت است به این دلیل که در این بهینه سازی تمام برنامه تحلیل می شود ولی بقیه بهینه ساز ها تنها به یکی از توابع و یا حتی به یک بلوک از کد نگاه می کنند.

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

همینطور IPO ممکن است بهینه سازی های معمولی کامپایلر در سطح کل برنامه هم باشد. برای مثال می توان به حذف کدهای مرده (DCE) اشاره کرد که در در آن کدهایی که هرگز اجرا نمی شوند حذف می شوند.برای رسیدن به این هدف کامپایلر شاخه هایی از کد را که اجرا نمی شوند را آزمایش می کند و در صورت نیاز کد های داخل آن شاخه را حذف می کند. IPO همنیطور استفاده بهتر از ثوابت تعریف شده در کد را تضمین می کند.کامپایلر های مدرن IPO را به عنوان یک گزینه اختیاری در زمان کامپایل ارایه می دهند. پردازش و کار واقعی IPO در هر قدم بین کد قابل خواندن برای انسان ها و تولید کد دودویی قابل اجرا برنامه اتفاق می افتد.

برای زبان هایی که فایل به فایل کامپایل را انجام می دهند، IPO زمانی کارامدی خود را برای ترجمه های واحد ها دارد که دارای دانش نقطه ورود برنامه باشند که بهینه سازی کل برنامه (WPO) قابل اجرا باشد. در بسیاری از موارد این بهینه سازی به صورت بهینه سازی ارتباط زمانی (LTO) پیاده سازی می شود به این دلیل که این برنامه برای linker قابل مشاهده است.

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

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

برای مثال تصور کنید که روالی وجود دارد که تابع F(x) را محاسبه می کند و برنامه در مکان های متفاوتی مقدار تابع F(6) را درخواست می دهد. محاسبه درخواست های دوم به بعد برای این مقدار در مکان های دیگر کاملا غیر ضروریست. نتیجه می توانست با فرض اینکه تابع خالص باشد به جای آن ذخیره شود و در جاهای دیگر که تابع صدا زده شده بود به کار گرفته شود. این بهینه سازی ساده در صورتی که پیاده سازی تابع مورد نظر خالص نباشد درست نیست. برای مثال زمانی که تابع در اجرای خود ارجاعی به متغییر هایی به غیر از متغییر ورودی ۶ در مثال ما داشته باشد که در بین زمان صدا زدن تابع مقدار آن عوض شده باشد و اثرات حاشیه ای مثل چاپ کردن مقداری در لاگ، شمردن تعداد محاسبه ها، جمع کردن زمان گرفته شده از واحد کنترل مرکزی، آماده کردن جدول های داخلی برای ساده سازی، اجرای زیر دستوراتی برای متغییر های مرتبط و …. از دست دادن این اثر های حاشیه ای منجر به پذیرفتن مقدار خروجی برای دفعه های دیگر می شود.

در حالت عمومی تر، جدا از بهینه سازی ها، دلیل دوم برای استفاده از روال ها این است که از ایجاد کد های اضافی و تکراری که خروجی یکسانی دارند جلوگیری کند. بنابر این یک رویکر عمومی برای بهینه سازی عکس عمل بالا به این صورت خواهد بود که: تعدادی یا همه ی فراخوانی های خاص با کد مربوطه و پارامتر های مناسب جایگزین می شوند. سپس کامپایلر برای بهینه کردن خروجی تلاش می کند. WPO و LTO “بهینه سازی کامل برنامه"(WPO) بهینه سازی کامپایلر یک برنامه با استفاده از اطلاعات مربوط به همه (ماژول(برنامه نویسی)|ماژول ها) در برنامه است.به طور معمول، بهینه سازی ها روی در هر ماژول ، "کامپایل" ، اساس)] انجام می شوند. اما این رویکرد ، در حالی که نوشتن و تست ساده تر و نیازمند تقاضای کمتری از منابع در حین کامپایل خود است، قطعیت در مورد ایمنی تعدادی از بهینه سازی ها مانند تهاجمی [[گسترش درون خطی | درگیری را فراهم نمی کند و بنابراین نمی تواند آنها را انجام دهد حتی اگر آنها در واقع به دست آوردن عملکرد تبدیل می شوند که سمنتیک کد شیء منتشر شده را تغییر نمی دهند.

"بهینه سازی زمان پیوند" '(LTO) نوعی بهینه سازی برنامه است که توسط یک کامپایلر به یک برنامه در زمان پیوند انجام می شود. بهینه سازی زمان لینک در زبان های برنامه نویسی مرتبط است که برنامه ها را به صورت فایل به فایل کامپایل می کنند، و سپس آن فایل ها را به هم پیوند می دهد (مانند C و  جاوا تفسیر just in time (JIT)).

هنگامی که همه فایل ها به طور جداگانه به object file تفسیر شدند، به طور سنتی، کامپایلر فایل های شی را به یک فایل واحد ، اجرایی پیوند می دهد. با این حال ، در LTO همانطور که توسط GNU Compiler Collection (GCC) یا LLVM پیاده سازی شده است ، مفسر قادر است intermediate representation ((GIMPLE bytod کد یا بیت کد LLVM) خود را در دیسک قرار دهد به گونه ای که تمام واحدهای مختلف مفسر که برای ساختن یک فایل اجرایی واحد به کار می روند ، می توانند به عنوان یک ماژول واحد هنگامی که در نهایت اتفاق می افتد بهینه سازی شوند. این دامنه بهینه سازی های متقابلانه را در بر می گیرد تا کل برنامه را شامل شود (یا به عبارت دیگر ، هر آنچه در زمان پیوند قابل مشاهده است). با بهینه سازی زمان پیوند ، کامپایلر می تواند اشکال مختلف بهینه سازی متقابلانه را برای کل برنامه اعمال کند ، و این امکان را برای تجزیه و تحلیل عمیق تر ، بهینه سازی بیشتر و در نهایت عملکرد بهتر برنامه فراهم می کند.

در عمل، LTO همیشه كل برنامه را بهینه نمی كند - توابع كتابخانه ، به ویژه  پیوند پویا اشیاء مشترک ، به طور عمد برای جلوگیری از تکرار بیش از حد و قابلیت تغییر در زمان نگه داشته می شوند. پیوند استاتیک به طور طبیعی به مفهوم LTO امانت داده می شود ، اما فقط با بایگانی کتابخانه کار می کند که حاوی اشیاء IR است بر خلاف فایل های ماشین  که فقط با فایل های شیء  کار می کنند. با توجه به نگرانی های مربوط به عملکرد ، حتی از کل واحد همواره به طور مستقیم استفاده نمی شود - یک برنامه می تواند به سبک تقسیم و تسخیر LTO مانند WHOPR GCC تقسیم شود. و البته وقتی برنامه ای که ساخته می شود ، خود یک کتابخانه است ، بهینه‌سازی هر نمادی را که در دسترس خارجی (صادر شده) قرار دارد ، نگه می دارد ، بدون آنکه تلاش زیادی در از بین بردن آنها به عنوان بخشی از DCE داشته باشید.

شکل بسیار محدود تری از WPO هنوز هم بدون LTO امکان پذیر نیست ، همانطور که توسط سوئیچ C | کد | -بعد برنامه-} GCC توضیح داده شده است. این حالت باعث می شود كه GCC فرض كند كه ماژول در حال تدوین شامل نقطه ورود (معمولاً main) كل برنامه است ، به گونه ای كه هر تابع دیگری در آن از بیرون استفاده نمی شود و می توان با خیال راحت ازبهینه شود. از آنجا که فقط به یک ماژول مربوط می شود ، نمی تواند کل برنامه را در بر بگیرد. (می توان آن را با LTO به معنای یک ماژول بزرگ ترکیب کرد ، وقتی پیوند دهنده در حال ارتباط برقرار کردن با GCC در مورد آنچه که از نقاط ورودی یا نمادها که از خارج استفاده می شوند.)

Example
اگر پارامترها به "Silly" گذر به مقدار باشند ، اقدامات رویه هیچ تاثیری روی متغیرهای اصلی ندارند و از آنجا که "Silly" هیچ کاری با محیط آن ندارد(خواندن از فایل ، نوشتن روی یک فایل،تغییر  متغیرهای جهانی مانند  b  و غیره ) کد آن به علاوه همه  فراخوانی ها ممکن است به طور کامل بهینه شده و مقدار "a" راتعریف نشده رها کند(که مهم نیست) که فقط کد "print” می ماند و مقادیر ثابت.

اگر در عوض پارامترها استراتژی ارزشیابی ، پس اقدام بر روی آنها در "Silly" در واقع بر اصل متغیر ها تأثیر می گذارد. این کار معمولاً با انتقال آدرس ماشین پارامترها به رویه انجام می شود تا تغییرات رویه در قسمت اصلی ذخیره سازی انجام شود.

بنابراین در صورت فراخوانی با مرجع ، رویه "Silly" اثر دارد. فرض کنید که فراخوانی های آن در جای خود گسترش یافته است و با پارامترهای مشخص شده توسط آدرس: the code amounts to

سپس کامپایلر می تواند در این مثال نسبتاً کوچک از ثابت ها پیروی کند و دریابد که گزاره های if-عبارت ها ثابت هستند و بنابراین ...

و از آنجا که مقداردهی های "a"،"b"و"x" هیچ چیز را به جهان خارج نمی رسانند - آنها در خروجی ظاهر نمی شوند ، و نه به عنوان ورودی به محاسبات بعدی (که نتیجه آنها به در"do " منجر به خروجی شود ، در غیر این صورت آنها نیز بی نیاز هستند) - هیچ نکته ای در این کد وجود ندارد ، و نتیجه این است که یک روش متغیر برای عبور پارامترهایی که "توسط مرجع" به نظر می رسد استراتژی ارزیابی  است که در آن روی یک کپی محلی از پارامترهایی کار می کند که مقادیر آنها در هنگام خروج از رویه کپی می شوند. اگر این رویه به یک پارامتر یکسان دسترسی داشته باشد ، اما به روشهای مختلف مانند فراخوانی مانند "Silly (a ، a)" یا "Silly (a ، b)" ، اختلافات ایجاد می شود. بنابراین ، اگر پارامترها با استفاده از کپی کردن منتقل شوند ، به ترتیب از چپ به راست "Silly (b،b) "گسترش می یابد به

و در این حالت کپی کردن مقدار "p1” (که تغییر یافته است) در"b” بی معنی است ، زیرا بلافاصله با مقدار "p2" بازنویسی می شود ، که این مقدار اصلاح نشده است. در این رویه از مقدار اصلی "b" استفاده می شود ، بنابراین جمله سوم می شود

چنین اختلافاتی در رفتار احتمالاً باعث ایجاد پازل خواهد شد ، و این سؤالات راجع به ترتیب کپی پارامترها تشدید می شود: آیا در هنگام خروج مانند ورود نیز چپ به راست می شود؟ احتمالاً این جزئیات در کتابچه راهنمای کامپایلر توضیح داده نشده است ، و اگر وجود داشته باشد ، احتمالاً به دلیل نامربوط بودن به وظیفه منتقل می شوند و تا زمانی که یک مشکل پیش می آید فراموش می شوند. اگر (به احتمال زیاد) مقادیر موقتی از طریق یک برنامه ذخیره سازی پشته ارائه می شود ، احتمالاً روند copy-back به ترتیب معکوس برای copy-in خواهد بود ، که در این مثال به این معنی  است که"p1” در عوض آخرین مقدار برگشتی به "b" خواهد بود.

فرایند گسترش یک روال درون خطی را نباید به عنوان نوعی جایگزینی متن در نظر گرفت (مانند در کلان) زیرا خطاهای نحوی ممکن است هنگام تغییر پارامترها ایجاد شود و از فراخوانی های خاص از ثوابت به عنوان پارامتر استفاده کند.از آنجا که این مهم است که اطمینان داشته باشید که هرگونه ثابت که به عنوان پارامتر ارائه می شود ، هیچ تغییری در ارزش خود  نکند (ثابت می تواند در حافظه نگه داشته شود همانطور که متغیرها هستند) مبادا استفاده های بعدی از آن ثابت (ساخته شده از طریق مراجعه به محل حافظه آن) خراب باشد. روش معمول این است که کامپایلر برای کپی کردن مقدار ثابت در یک متغیر موقت کدی را تولید کند که آدرس آن به رویه ها منتقل می شود،تولید کند و اگر مقدار آن تغییر یابد ، مهم نیست. هرگز به محل ثابت کپی نمی شود.

به عبارت دیگر ، یک برنامه آزمایشی کتبی با دقت نوشته شده می تواند در مورد اینکه پارامترها براساس مقدار یا مرجع منتقل می شوند ، گزارش دهند ، و در صورت استفاده ، از چه نوع طرح کپی به داخل و کپی به خارجی استفاده می کنند. با این حال ، تنوع بی پایان است: پارامترهای ساده ممکن است با فراخوانی با کپی منتقل شوند در حالی که ساختارهای بزرگ مانند آرایه ممکن است توسط مرجع منتقل شود. ثابتهای ساده مانند صفر ممکن است توسط کدهای ویژه ماشین (مانند پاک کردن یا LoadZ) ایجاد شود در حالی که ثابت ترین پیچیدگی ها ممکن است در حافظه ذخیره شده و با استفاده از هر تلاشی برای اصلاح آن منجر به خاتمه برنامه فوری و غیره شوند.

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

برخی از زبانهای رایانه اعلان های مربوط به استفاده از پارامترها را فعال می کنند (یا حتی به آن احتیاج دارند) ، و ممکن است فرصتی را فراهم کنند تا اعلام کنند متغیرها مقادیر خود را برای برخی از مجموعه ها محدود می کنند (به عنوان مثال ،۶<x )تا خرد کردن برای فرآیند بهینه سازی و همچنین فراهم کردن بررسی های ارزشمند در انسجام کد منبع برای شناسایی اشتباهات اسان شود. اما این هرگز کافی نیست - فقط به بعضی از متغیرها محدودیت های ساده ای داده می شود ، در حالی که برخی دیگر به مشخصات پیچیده ای نیاز دارند: چگونه می توان مشخص کرد که متغیر "P" یک عدد اول باشد و اگر چنین باشد ،یک عضو عدد اول هست یا نیست. پیچیدگی ها فوری هستند: با توجه به اینکه "M" یک ماه است ، محدوده های معتبر برای "D" روزهای یک ماه چیست؟ و آیا همه تخلفات ارزش توقف فوری برنامه را دارند؟ حتی اگر همه این موارد قابل کنترل باشد ، چه فایده ای دارد؟ و با چه هزینه ای؟ مشخصات كامل و کنترل با جزییات منجر به بيان و تعریف مجدد توابع برنامه به شكل ديگر می شود و جدا از زماني كه کامپایلر در ترجمه آنها مصرف می كند ، در معرض اشکالات و باگ خواهند بود. در عوض ، فقط جزییات ساده با بررسی دامنه در زمان اجرا ارائه می شود.

در مواردی که برنامه ای ورودی را نمی خواند (مانند مثال) ، می توان تصور کرد که تحلیل های کامپایلر در حال انجام است به طوری که نتیجه بیشتر از یک سری خط های چاپ شده نخواهد بود ، یا احتمالاً برخی از حلقه ها به طور مصلحتانه چنین مقادیری را تولید می کنند. آیا آنگاه برنامه ای را برای تولید عددهای اول و تبدیل به بهترین روش شناخته شده برای انجام این کار ، یا در عوض مراجعه به کتابخانه ارائه می دهد؟ بعید است! به طور کلی ، ملاحظات خودسرانه پیچیده ([Entscheidungsproblem) برای جلوگیری از این امر بوجود می آید ، و چاره ای جز اجرای کد تنها با اصلاحات محدود وجود ندارد.

تاریخچه
برای زبانهای رویه ای یا ALGOL ، تجزیه و تحلیل بینابینی و بهینه سازی به نظر می رسد که اوایل دهه 1970 وارد عمل تجاری شده اند. بهینه سازی کامپایلر IBM PL / I از تحلیل های بین رویه ای  به منظور درک عوارض جانبی فراخونی رویه و استثنائات (به اصطلاح PL / I به عنوان "در شرایط") استفاده کرد.

and in papers by Fran Allen. Work on compilation of the APL programming language was, of necessity, interprocedural.

تکنیک های تحلیل بین رویه ای و بهینه سازی موضوع تحقیقات دانشگاهی در دهه های 1980 و 1990 بود. در اوایل دهه 1990 با کامپایلرهایی از محدب ("کامپایلر برنامه" برای محدب C4 و از اردنت کامپایلر برای [[Ardent Titan]) وارد دنیای کامپایلر تجاری شد و دوباره ظهور کردند. این کامپایلرها نشان داده اند که فن آوری ها می توانند به اندازه کافی سریع ساخته شوند تا به عنوان یک کامپایلر تجاری قابل قبول باشند. متعاقباً تکنیکهای بین رویه ای در تعدادی از سیستمهای تجاری و غیرتجاری دیده می شوند.

منابع مرتبط

 * Profile-guided optimization (PGO)
 * Single Compilation Unit