تالار گفتمان مانشت
انواع بهینه سازی در کامیایلرها - نسخه‌ی قابل چاپ

انواع بهینه سازی در کامیایلرها - admin - 08 خرداد ۱۳۸۹ ۰۴:۵۴ ب.ظ

کامپایلرهای جدید قادر به انجام تغییرات زیادی بر روی کد ورودی به منظور افزایش کارایی آن میباشند. برای یک برنامه نویس سودمند خواهد بود که بداند یک کامپایلر چه بهینه سازی هایی را میتواند انجام دهد و کدام را نمیتواند. در این بخش به معرفی تعدادی از بهینه سازی هایی که یک کامپایلر میتواند انجام دهد میپردازیم.

۱- Function inlining

کامپایلر میتواند فراخوانی یک تابع را با بدنه آن جایگزین نماید. به مثال زیر دقت نمایید:

کد:
// Example 1a

float square (float a) {

return a * a;}

float parabola (float x) {

return square(x) + 1.0f;}

کامپایلر میتواند دستور فراخوانی تابع square را با بدنه آن جایگزین نماید. به صورت زیر:

کد:
// Example 1b

float parabola (float x) {

return x * x + 1.0f;}

مزایای این بهینه سازی عبارتند از:

۱- بالاسری ناشی از فراخوانی و بازگشت و انتقال پارامتر به تابع حذف میشود.

۲- درصد محلی بودن ارجاعات در کد بیشتر و درنتیجه ضریب برخورد در حافظه نهان بیشتر میشود.

۳- اگر فقط یک فراخوانی روی تابع inline شده وجود داشته باشد کد کوچکتر میشود.

۴- inline نمودن یک تابع فرصتی را برای اعمال سایر بهینه سازی‌ها روی کد بوجود می آورد که ذیلا توضیح داده خواهد شد.

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

۲- constant folding and constant propagation

در بهینه سازی constant folding یک عبارت یا زیرعبارت که فقط شامل ثوابت باشد با نتیجه آن عبارت جایگزین میشود. به مثال زیر دقت نمایید:

کد:
// Example 2a

double a, b;

a = b + 2.0 / 3.0;
کامپایلر این کد را با کد زیر جایگزین مینماید:

کد:
// Example 2b

a = b + 0.666666666666666666667;
به این نکته دقت نمایید که در عبارتی مثل b * 2.0 / 3.0 بهینه سازی constant folding انجام نمیشود چراکه کامپایلر در ابتدا عمل b * 2.0 را انجام میدهد. پس بهتر است این عبارت توسط خود برنامه نویس به صورت b * (2.0 / 3.0) نوشته شود.

در بهینه سازی constant propagation یک ثابت در صورت امکان در بین یک سری از عبارات منتشر میشود. به مثال زیر توجه نمایید:

کد:
// Example 3a

float parabola (float x) {

return x * x + 1.0f;}

float a, b;

a = parabola (2.0f);

b = a + 1.0f;

کامپایلر این کد را با کد زیر جایگزین مینماید:

کد:
// Example 3b

a = 5.0f;

b = 6.0f;
البته اگر در عبارت تابعی باشد که نتواند inline شود یا نتواند در زمان کامپایل محاسبه گردد نمیتوان از بهینه سازی های constant folding و constant propagation استفاده کرد. به مثال زیر توجه نمایید:

کد:
// Example 4

double a = sin(0.8);

تابع sin در یک کتابخانه جدا تعریف شده و نمیتوان از کامپایلر انتظار داشت که بتواند این تابع را inline نماید و آن را در زمان کامپایل محاسبه نماید.

۳- Pointer elimination

اگر در زمان کامپایل معلوم باشد که یک اشاره گر به چه خانه ای از حافظه اشاره میکند میتوان آن اشاره گر را حذف نمود. به مثال زیر دقت نمایید:

کد:
// Example 5a

void Plus2 (int *p) {

*p = *p + 2;}

int a;

Plus2 (&a);
کامپایلر این کد را با کد زیر جایگزین مینماید:

کد:
// Example 5b

a += 2;

۴- Common subexpression elimination

اگر در یک عبارت چندین بار یک زیر عبارت تکرار شده باشد آنگاه کامپایلر فقط یکبار آن زیرعبارت را محاسبه مینماید. به مثال زیر دقت نمایید:

کد:
// Example 6a

int a, b, c;

b = (a+1) * (a+1);

c = (a+1) / 4;

کامپایلر این کد را با کد زیر جایگزین مینماید:

کد:
// Example 6b

int a, b, c, temp;

temp = a+1;

b = temp * temp;

c = temp / 4;
۵- Register variables

در این بهینه سازی کامپایلر سعی میکند که متغیرهایی را که بیشتر از همه مورد استفاده قرار میگیرند در ثبات های cpu ذخیره کند. از جمله این متغیرها میتوان به شمارنده های حلقه ها، اشاره گرها، پارامترهای توابع و … اشاره کرد.

دقت کنید که اگر در کد یک برنامه با آدرس یک متغیر کار میکنیم آن متغیر نمیتواند در یک ثبات قرار گیرد چراکه ثبات آدرس ندارد.

۶- Join identical branches

در این بهینه سازی قسمت های مشترک کد مربوط به دو پرش if-then-else به منظور صرفه جویی در کد یکی میشوند. به مثال زیر دقت نمایید:

کد:
// Example 8a

double x, y, z; bool b;

if (b) {

y = sin(x);

z = y + 1.;

}

else {

y = cos(x);

z = y + 1.;

}

کامپایلر این کد را با کد زیر جایگزین مینماید:

کد:
// Example 8b

double x, y; bool b;

if (b) {

y = sin(x);

}

else {

y = cos(x);

}

z = y + 1.;
۷- Eliminate jumps

با کپی کردن کدی که به آن پرش میشود میتوان از انجام پرش جلوگیری کرد. به مثال زیر دقت نمایید:

کد:
// Example 9a

int SomeFunction (int a, bool b) {

if (b) {

a = a * 2;

}

else {

a = a * 3;

}

return a + 1;

}
کامپایلر این کد را با کد زیر جایگزین مینماید:

کد:
// Example 9b

int SomeFunction (int a, bool b) {

if (b) {

a = a * 2;

return a + 1;

}

else {

a = a * 3;

return a + 1;

}

}

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

// Example 10a

کد:
if (true) {

a = b;

}

else {

a = c;

}

کامپایلر این کد را با کد زیر جایگزین مینماید:

کد:
// Example 10b

a = b;

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

کد:
// Example 11a

int SomeFunction (int a, bool b) {

if (b) {

a = a * 2;

}

else {

a = a * 3;

}

if (b) {

return a + 1;

}

else {

return a - 1;

}

کامپایلر این کد را با کد زیر جایگزین مینماید:

کد:
// Example 11b

int SomeFunction (int a, bool

if (b) {

a = a * 2;

return a + 1;

}

else {

a = a * 3;

return a - 1;

}

}

۸- Loop unrolling

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

کد:
// Example 12a

int i, a[2];

for (i = 0; i < 2; i++) a[i] = i+1;
کامپایلر این کد را با کد زیر جایگزین مینماید:

کد:
// Example 12b

int a[2];

a[0] = 1; a[1] = 2;

۹- Loop invariant code motion

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

کد:
// Example 13a

int i, a[100], b;

for (i = 0; i < 100; i++) {

a[i] = b * b + 1;

}
کامپایلر این کد را با کد زیر جایگزین مینماید:

کد:
// Example 13b

int i, a[100], b, temp;

temp = b * b + 1;

for (i = 0; i < 100; i++) {

a[i] = temp;

}