موضوع مقاله: بهینه‌سازی کامپایلر | مخاطب: عمومی | لحن: دانشنامه‌ای | ساختار: عنوان، مقدمه، 3 بخش، جمع‌بندی | مقاله: | # بهینه‌سازی کامپایلر | مقدمه: بهینه‌سازی یکی از مراحل اصلی در طراحی کامپایلر است که معمولاً پس از تولید کد میانی آغاز می‌شود و هدف آن بهبود کارایی برنامه بدون تغییر در رفتار ظاهری آن است. | ## بخش 1: بهینه‌سازی کامپایلر | این فرآیند می‌تواند شامل کاهش حجم کد، کاهش تعداد محاسبات، حذف کدهای مرده، بهینه‌سازی تخصیص ثبات‌ها، بازآرایی حلقه‌ها، و افزایش locality حافظه باشد. با این حال، بهینه‌سازی ممکن است فرآیند اشکال‌زدایی را دشوارتر کند، زیرا ترتیب اجرای دستورها، ساختار کد یا موقعیت متغیرها ممکن است تغییر یابد. از این‌رو بسیاری از کامپایلرها اجازه نمی‌دهند بهینه‌سازی و اشکال‌زدایی به‌طور هم‌زمان فعال باشند. علاوه بر این، در صورتی که کد قرار است روی پردازنده‌هایی با ویژگی‌های متفاوت اجرا شود (مانند سرعت حافظه یا ساختار pipeline متفاوت)، اعمال بهینه‌سازی‌های خاص یک معماری ممکن است نادرست یا غیربهینه باشد. تکنیک‌های بهینه‌سازی ممکن است وابسته به زبان برنامه‌نویسی، وابسته به معماری مقصد یا مستقل از هر دو باشند؛ با این حال، بیشتر روش‌ها به صورت مستقل از زبان پیاده‌سازی می‌شوند و ویژگی‌های معماری مقصد را می‌توان با پارامترهایی مشخص کرد. بهینه‌سازی حلقه‌ها حلقه‌ها بهترین کاندیداها برای بهینه‌سازی هستند چراکه بیشتر وقت برنامه در حلقه‌ها صرف می‌شود. تحلیل متغیرهای استقرایی: در هر حلقه اگر متغیری باشد که به صورت حاصل عملیات ریاضی بیان شده باشد، شاید بتوان آن را هربار بدون محاسبه (index variable) بر روی متغیر اندیس دوباره، با یک تغییر کوچک بروز کرد. این کار همچنین می‌تواند متغیر اندیس را به سمت بی‌مصرف شدن و در نهیت حذف پیش ببرد. مساوی داشته باشند و با داده (iteration) تلفیق حلقه‌ها: اگر دو حلقه مجاور تعداد تکرار های یکدیگر کار نکنند، می‌توان آن‌ها را تلفیق کرده و به یک حلقه واحد تبدیل کرد تا بدینگونه جلوگیری شود. | ## بخش 2: بهینه‌سازی کامپایلر | هر چند گاهی عکس این عمل نیز (تبدیل یک حلقه به چند حلقه جدا از هم) می‌تواند باعث بهینه شدن کد شود که این مورد بیشتر در مورد پردازنده‌های چند هسته‌ای بکار می‌رود است while بهتر از حلقه‌های do/while واژگون کردن حلقه: اغلب استفاده از حلقه‌های چرا که چک کردن شرط حلقه در آخر حلقه باعث استفاده کمتر از پرش می‌شود. pipelining داشته باشید در اغلب پردازنده‌ها دستورها پرش جلوی استفاده از جابجایی کد مستقل از حلقه: اگر مقدار یک عبارتی که باید محاسبه شود در تمام تکرارهای حلقه ثابت باشد، با جابجا کردن کد مربوط به محاسبه حاصل عبارت به بیرون حلقه می توانیم سرعت برنامه را افزایش دهیم. بازکردن حلقه: اگر تعداد تکرار یک حلقه در زمان کامپایل مشخص باشد، می‌توان کد بدنه حلقه را به آن تعداد کپی کرد تا از چک کردن شرط حلقه و همچنین دستورها پرش جلوگیری می‌شود و ممکن cache شود. هرچند این کار باعث افزایش طول کد و استفاده کمتر از است حتی برنامه را کندتر هم بکند. بهینه‌سازی‌های مبتنی بر جریان داده حذف زیرعبارت مشترک: اگر یک عبارت ریاضی چند بار در کد تکرار شده باشد می‌توان حاصل آن را فقط یکبار حساب کرده، در یک ثبات گذاشت و از آن استفده کرد. محاسبه مقادیر ثابت: می‌توان حاصل عبارتهایی که مقدار آن‌ها در زمان کامپایل مشخص است را در زمان کامپایل محاسبه و جایگذاری کرد تا نیازی به محاسبه آن‌ها در زمان اجرا نباشد. اگر بدانیم یک متغیر هیچ نام استعاری دیگری ندارد می‌توانیم برخی بهینه‌سازیهایی را انجام دهیم مثل اگر مقدار آن در زمان کامپایل مشخص بود، مقدار آن را جایگزین نام آن کنیم. در زبانهای برنامه‌نویسی دارای تقریباً هیچ بهینه‌سازی در این زمینه نمی‌توأم انجام داد چرا که هر (C اشاره گر (مثل زبان اشاره‌گری ممکن است به یک متغیر دلخواه اشاره کند و یک نام استعاری برای آن باشد. بهینه‌سازی‌های مربوط به تولید کد تخصیص مناسب ثباتها: برای تخصیص ثباتها به متغیرها معمول از رنگ‌آمیزی گراف تداخل استفاده می‌شود. | ## بخش 3: بهینه‌سازی کامپایلر | اگر نتوان گراف را با تعداد رنگ ذکر شده رنگ کرد، یک یا چند متغیر باید در حافظه ذخیره شوند که به منظور بهینه‌سازی، این متغیرها باید متغیرهای کم استفده تر باشند. بسیاری از عملیاتها را می CISC انتخاب صحیح دستورالعملها: در پردازنده‌های با معماری توان به چند طریق کد کرد. این که برای هر کاری از چه سلسله دستورالعملهایی استفاده شود در دست کامپایلر است تا با انتخاب صحیح دستورالعملهل، کد را بهینه کند. به عنوان مثال اگر یک دستور برای اجرا شدن به حاصل دستور قبل از pipelining خود نیاز داشته باشد، می‌گوییم دستور دوم به اولی وابسته است و باعث جلوگیری از می‌شود. هر ترتیب توپولوژیکال رئوس گراف، معرف یک ترتیب (DAG) یک گراف جهتدار بی دور صحیح برای اجرای دستورها است. اگرچه این عمل ممکن است در نگاه اول بی‌معنی به نظر برسد، ولی با توجه پیشرفت سریعتر سرعت پردازنده‌ها نسبت به دسترسی به حافظه، این‌گونه بهینه‌سازی‌ها به مرور اهمیت بیشتری پیدا کرده‌اند. دیگر بهینه‌سازی‌ها اگر فراخوانی بازگشتی یک تابع در انتهای آن صورت: (Tail Recursion) حذف بازگشتی از ته گرفته باشد، می‌توان آن به یک تابع غیربازگشتی تبدیل کرد تا ار هزینه فراخوانی چندباره تابع جلوگیری شود. خذف بررسی کرانه‌ها: برای زبان‌های برنامه‌نویسی که قبل از دسترسی به یک آرایه از طریق اندیس، مقدار اندیس را بررسی می‌کنند که در محدوده آرایه باشد، حذف این بررسی در مواقعی که مقدار اندیس در زمان کامپایل معلوم است می‌تواند کمک بزرگی به سرعت اجرای برنامه بکند. این عملیات باید یکی از آخرین عملیاتهای بهینه‌سازی باشد چرا که بسیاری از بهینه‌سازی‌های دیگر حجم کد مرده را بیشتر می‌کنند. | ## جمع‌بندی | کردن توابع: این کار می‌تواند از هزینه فراخوانی توابع جلوگیری کند و بخصوص در Inline کاربرد دارد.