حذف کد مرده
در نظریه کامپایلر، شاخهای از علوم کامپیوتر نظری، حذف کد مرده (به انگلیسی: Dead code elimination) نوعی از بهینهسازی کامپایلر (به انگلیسی: Compiler optimization) است، که برای حذف بخشی از کد، که در نتیجهٔ برنامه تأثیری ندارد، استفاده میشود. کد مرده شامل بخشی از کد است که هیچگاه اجرا نمیشود و یا در صورت اجرا، خروجی آن مورد استفاده قرار نگرفته و در واقع فقط بر روی متغیرهای مرده (متغیرهایی که در بقیه بخشهای برنامه خوانده نمیشوند و از آنها استفادهای نمیشود) تأثیر میگذارد.[1][2] بهطور معمول، کامپایلرها در زمان کامپایل برنامه، به صورت بازگشتی تعیین میکنند که کدام متغیرها و بخشهای برنامه استفاده شدهاند. متغیرها و بخشهای استفاده شده علامت گذاری میشوند و در نهایت بخشهای علامت نخورده، از برنامه حذف میشوند.[3] اکثر کامپایلرهای پیشرفته، گزینههای متعددی را در سطوح مختلف برای حذف کد مرده در اختیار قرار میدهند. در سطوح پایین، کامپایلر فقط بخشهایی از کد که اجرای آنهای غیرممکن است را حذف میکند. در سطوح بالاتر کامپایلر فضایی را برای ذخیرهسازی متغیرهای بیاستفاده در نظر نمیگیرد. در سطوح بالاتر از آن نیز، کامپایلر دستورها و توابع بیاستفاده را شناسایی و حذف میکند.
مزایا
حذف کد مرده مزیتهای فراوانی دارد که از جملهٔ آنها میتوان به کم شدن اندازهٔ برنامه اشاره کرد، که بدین ترتیب از اجرا شدن کدی که در نتیجهٔ برنامه تأثیری ندارد و عملهایی که میتوان بدون انجام آنها به نتیجه رسید، جلوگیری میشود. در نتیجه، مدت زمان اجرای برنامه کاهش مییابد و این موضوع در بسیاری از اوقات، میتواند تأثیر زیادی در بهینهتر شدن برنامه داشته باشد. علاوه بر این، حذف کد مرده باعث سادهتر شدن ساختار برنامه میشود و به این صورت، راه برای انجام دیگر بهینهسازیها باز میشود.[1]
خطرات
از طرفی باید در عملیات حذف کد مرده بسیار دقت نمود، چرا که ممکن است نتیجهٔ محاسبات بخشی از کد، در جای دیگری استفاده نشود، ولی آن محاسبات باعث ایجاد تغییر در یک متغیر سراسری و یا تولید استثنا (به انگلیسی: Exception) شود، که در این صورت حذف آن بخش از کد، باعث ایجاد خطا در عملکرد برنامه میشود. بهطور معمول، کامپایلرها زمانی که در مورد مرده بودن بخشی از کد ابهام هست، به صورت محافظ کارانه عمل میکنند.[1]
مثالها
این برنامه که با زبان سی (C) نوشته شدهاست را در نظر میگیریم.
int foo(void)
{
int a = 24;
int b = 25; /* Assignment to dead variable */
int c;
c = a * 4;
return c;
b = 24; /* Unreachable code */
return 0;
}
تحلیلی ساده از کاربرد مقادیر، مشخص میکند که متغیر b
بعد از اینکه برای اولین بار در خط چهارم مقداردهی میشود، در تابع foo
استفاده نمیشود. از طرفی، متغیر b
یک متغیر محلی در تابع foo
است و به این دلیل نمیتواند خارج از foo
استفاده شود. بنابراین متغیر b
مرده است و کامپایلر در زمان بهینهسازی میتواند آنرا حذف نماید.
همچنین از آنجا که تابع همواره در خط هفتم، با دستور بازگشت به کار خود پایان میدهد، هیچ مسیر اجرایی به خط هشتم که متغیر b
برای دومین بار در آن مقداردهی میشود نمیرسد. بنابراین، دسترسی به این مقداردهی امکانپذیر نمیباشد و این بخش از کد، قابل حذف شدن است. باید دقت شود که اگر برنامه ساختار پیچیده تری داشت، به عنوان مثال بعد از دستور بازگشت از تابع در خط هفتم، برچسبی وجود داشت و از جای دیگری از برنامه دستور goto
به آن برچسب بود، آن موقع ممکن بود مسیر اجرایی به دومین مقداردهی متغیر b
وجود داشته باشد و دیگر b
غیرقابل دسترس نبود.
علاوه بر این، با اینکه محاسباتی در این تابع انجام میشود، مقدار آن محاسبات در جایی که خارج از حوزهٔ این تابع قابل دسترسی باشد ذخیره نمیشود. بنابراین با توجه به اینکه تابع همواره مقدار ۹۶ را بازمیگرداند، ممکن است در زمان بهینهسازی به مقداری که بازمیگرداند ساده شود.
حال برنامهٔ دیگری که با زبان سی نوشته شدهاست را در نظر میگیریم.
int main(void) {
int a = 5;
int b = 6;
int c;
c = a * (b / 2);
if (0) { /* DEBUG */
printf("%d\n", c);
}
return c;
}
در این مثال نیز از آنجا که مقدار صفر، همواره برابر با false در نظر گرفته میشود، دستور if هیچگاه اجرا نخواهد شد و بنابراین کل بخش if در هنگام بهینهسازی از برنامه حذف میشود.
حذف پویای کد مرده
گاهی اوقات غیرقابل دسترس و مرده بودن بخشی از کد، در شرایط خاصی اتفاق میافتد و در هنگام کامپایل مشخص نمیشود. به عنوان مثال این شرایط را ورودی برنامهها میتوانند ایجاد کنند و باعث شوند بخشی از کد هیچگاه در زمان اجرای برنامه، اجرا نشوند. بخشی از کد به صورت پویا مرده (به انگلیسی: Dynamically dead) است اگر بسته به شرایط برنامه در زمان اجرا، مقادیری که آن بخش از کد تولید میکند، در جای دیگری از برنامه استفاده نشود.[4] به بیان دیگر، یک دستور به صورت پویا مرده، نمونهای از یک دستور ایستا (به انگلیسی: Static instruction) است، که یک مقدار ثبات (به انگلیسی: Register) مرده را ایجاد میکند.[5] محققان دریافتهاند که کسر عظیمی از دستورات اجرا شدهٔ برنامهها به صورت پویا مردهاند.[4]
جستارهای وابسته
منابع
- DEBRAY، SAUMYA؛ EVANS، WILLIAM (۲۰۰۰). «Compiler Techniques for Code Compaction». ACM Transactions on Programming Languages and Systems. doi:10.1145/349214.349233.
- «Dead Code Elimination». بایگانیشده از اصلی در ۲۱ دسامبر ۲۰۱۷. دریافتشده در ۳۱ دسامبر ۲۰۱۷.
- «Dead Code Elimination».
- Jantz، Marianne؛ Kulkarni، Prasad (۲۰۱۲). «Understand and Categorize Dynamically Dead Instructions for Contemporary Architectures». Interaction between Compilers and Computer Architectures (INTERACT), 2012 16th Workshop on. IEEE.
- Butts، Adam؛ Sohi، Guri (۲۰۰۲). «Dynamic Dead-Instruction Detection and Elimination» (PDF). ASPLOS X Proceedings of the 10th international conference on Architectural support for programming languages and operating systems.