هنگامیکه دربارهی بهینهسازی برنامه C صحبت میکنیم، معمولاً به دو جنبه اشاره داریم: 1) حجم کد برنامه 2) سرعت اجرای برنامه.
کامپایلرهای امروزی C گزینههای متنوعی برای بهینهسازی برنامه C میکروکنترلر AVR در هر دو جنبهی حجم و سرعت اجرای کد دارند. با این حال یک برنامه با کدنویسی خوب این فرصت را برای کامپایلرها ایجاد میکند که کد را به بهترین حالت بهینه کنند. در برخی مواقع بهینه کردن کد برنامه در یک جنبه، تأثیر منفی بر جنبهی دیگر دارد. بنابراین برنامهنویسان با توجه به نیازهای خاص خود باید بین این دو جنبهی بهینهسازی، تعادل برقرار کنند.
دانستن نکات و ترفندهای بهینهسازی برنامه C میکروکنترلرهای هشت بیتی AVR به برنامهنویسان کمک میکند که برنامهای با کارایی بالا داشته باشند. در این مقاله قصد داریم به همین نکات بپردازیم. اما پیش از بهینه کردن برنامههای سیستمهای تعبیهشدهی خود (embedded systems) نیاز است که درک درستی از معماری و ساختار هستهی AVR داشته باشیم. همچنین باید بدانیم کامپایلر از چه ترفندهایی برای تولید بهینهی کد استفاده میکند. با سیسوگ همراه باشید.
معماری میکروکنترلر هشت بیتی AVR
یکی از ویژگیهای مهم میکروکنترلر AVR، برخورداری از معماری هاروارد (HARVARD) است. در این معماری، داده و دستورالعمل در حافظهای جداگانه قرار گرفته و همچنین از مسیرهای سیگنال جداگانه استفاده میکنند. میکروکنترلرهای AVR دارای 32 رجیستر کاری هشت بیتی هستند. این رجیسترها جزیی از حافظهی داده به شمار میآیند. گفتنی است تعداد رجیسترهای کاری بر کارایی پردازنده تأثیر به سزایی دارد؛ چراکه دستورات منطقی و محاسباتی با رجیسترهای کاری کار میکنند.
شاید برای شما مفید باشد: میکروکنترلر مقصر نیست مقصر برنامه نویسی است
ALU میتواند در سیکل کلاک به یک زوج از رجیسترهای کاری دسترسی پیدا کند و با دو عملوند که در این دو رجیستر هستند دستورالعمل را اجرا کرده و نتیجه را به رجیستر مقصد بازگرداند. زمان اجرای دستورالعملها تنها یک سیکل کلاک است و در یک ساختار Pipeline فازهای اجرای دستورالعمل و واکشی بهصورت موازی انجام میشود. حافظهی برنامه بهصورت 16 بیتی سازمان یافتهاند و تمام دستورالعملهای AVR شانزده یا سی و دو بیت عرض دارند. در میکروکنترلرهای AVR، معماری ریسک (RISC) پیشرفته در راستای کاهش حجم کد برنامه و اجرای عملیات در یک سیکل کلاک و بهینهسازی برنامه C به کار گرفته شده است.
کامپایلر GCC
پیشپاز این، در مقالهی «کامپایلر Codevisionavr در مقابل کامپایلر GCC و مقایسه تخصصی آنها» با تفاوتها و برتری کامپایلر GCC نسبت به کدویژن آشنا شدیم. در این مقاله بر اساس کامپایلر GCC پیش میرویم با این حال همهی نکات و ترفندها برای کامپایلرهای دیگر قابلاجرا است. کامپایلر GCC سطوح مختلف بهینهسازی را فراهم میکند. کامپایلر میتواند بهینهسازی را یا بر روی حجم کد و یا بر روی سرعت اجرای کد انجام دهد که این عمل در پنج سطح مختلف Os , -O3 , -O2 , -O1 , -O0- انجام میگردد. سطح O0- بدون انجام بهینهسازی است. در کنار این سطوح، گزینههای دیگری نیز برای انتخاب شرایط بهینهسازی موردنظر خود وجود دارد. در این لینک همهی سطوح بهینهسازی بهصورت کامل آورده شده است.
نکات و ترفندهای کاهش حجم برنامه برای بهینهسازی برنامه C
1) انواع دادهها و اندازهی آنها
از کوچکترین نوع دادهی ممکن استفاده کنید. کد خود را از لحاظ انواع دادهی بهکار رفته ارزیابی کنید. برای خواندن یک مقدار 8 بیتی به یک متغیر یک بایتی نیاز است نه متغیر دو بایتی. اندازهی انواع دادهها در فایل هدر stdint وجود دارد.
در نمونهی زیر مشاهده میکنیم که در مثال سمت چپ، نوع دادهی int دو بایتی و در مثال سمت راست نوع دادهی char یک بایتی تعریف شده است. همانطور که مشاهده میکنید از حافظه، دو بایت کمتر استفاده شده است:
2) متغیرهای عمومی (Global) و محلی (local)
در بیشتر موارد استفاده از متغیرهای عمومی توصیه نمیشود. تا جایی که ممکن است از متغیرهای محلی استفاده کنید. اگر یک متغیر تنها در یک تابع بهکار میرود، آن را بهصورت محلی در خود تابع تعریف کنید. اگر یک متغیر عمومی تعریف شود، آدرس منحصربهفردی در حافظهی SRAM به آن اختصاص داده میشود و مقدار آن در تمام مدت اجرای برنامه حفظ میشود. متغیرهای محلی برای همهی توابع قابلاستفاده نیستند و محلی از حافظه یا رجیستر کاری به آن اختصاص داده میشود و پس از خروج از تابع این فضا آزاد میشود.
در مثال سمت چپ متغیر عمومی و در مثال سمت راست متغیر محلی به کار رفته است. حجم اشغالی حافظهی برنامه و داده نشان میدهد که متغیر محلی فضای کمتری از حافظه را اشغال میکند.
3) اندیس حلقهی تکرار
همانطور که میدانیم، سه نوع حلقهی تکرار while و for و do-while وجود دارد. اگر سطح بهینهسازی –Os فعال باشد، کامپایلر حلقهها را بهگونهای بهینه میکند که حجم کد یکسانی داشته باشند. با این حال ما میتوانیم با ترفندهایی، حجم کد را بیشتر کاهش دهیم. اگر از حلقهی do-while در برنامهی خود استفاده میکنیم، بسته به اینکه از اندیس افزایشی یا کاهشی تعریف میشود، اندازهی کد متفاوتی ایجاد خواهد شد. ما بهطور معمول در برنامهها اندیس افزایشی را بهکار میبریم. به این معنا که از مقدار صفر تا مقدار حداکثر افزایش مییابد. اما از نظر بهینهسازی کد بهتر است که از مقدار حداکثر تا صفر کاهش یابد؛ به این علت که در حلقهی افزایشی، باید هر بار که حلقهی تکرار اجرا میشود، مقدار اندیس با مقدار حداکثر حلقه که در بخش شرط حلقهی تعریفشده، مقایسه شود. در حلقهی کاهشی نیاز نیست که در هربار اجرای حلقه شرط حلقه چک شود زیرا مقدار اندیس اگر به صفر برسد، پرچم Z را در رجیستر وضعیت SREG یک میکند.
در مثال، تفاوت حجم اشغالی حافظه را در دو حالت حلقهی کاهشی و افزایشی مشاهده میکنید.
4) تلفیق حلقههای تکرار
اگر حلقههای متفاوت، محدودهی تکرار یکسان داشته باشند و از دادهی یکدیگر استفاده نکنند، میتوانیم آنها را تلفیق کرده و به یک حلقهی واحد تبدیل کنیم تا تعداد حلقهها را در برنامه کاهش دهیم. تلفیق حلقهها به کاهش حجم کد و افزایش سرعت اجرای برنامه منجر میشود.
در قسمتهای دیگر با نکات و ترفندهای بیشتری دربارهی کاهش حجم کد برنامه برای بهینهسازی برنامه C آشنا خواهیم شد. پس از آن به سراغ افزایش سرعت اجرای برنامه و کاهش زمان اجرا میرویم.
منبع: Atmel
مهندس
دستتون درد نکنه
چرا در ID آردینو
وقتی یک متغییر گلوبال تعریف و استفاده میشه و وقتی آن را در داخل یک تابع بصورت لوکال استفاده می کنیم آردینو تغییری در اعلام میزان sram نمیده چرا؟
دلایل متفاوتی میتونه داشته باشه ، از جمله حوزه بندی و یا استفاده در اینتراپت و..
بهتره نمونه کد رو قرار بدید که بشه دلیلش رو توضیح داد.
این مقاله واقعا برای من مفید بود.
همیشه از استادهامون میشنیدیم که فلان کار باعث میشه در مصرف حافظه صرفه جویی بشه ولی چون نمونه نمیدیدیم خیلی رعایت نمیکردیم.
اما این تصاویر خیلی واضح این تاثیر این نکات رو نشون میداد.
خیلی ممنون از شما.
عالی بود مهندس. فکر نمی کردم انقدر ساده بشه بهینه سازی انجام داد. درواقع فکر نمی کردم مثلا شمارش افزایشی یا کاهشی انقدر موثر باشه.
خیلی ممنون ازینکه تجربه هاتونو در اختیار دیگران قرار میدید.
خواهش میکنم. خوشحالم که این مقاله مورد توجه شما قرار گرفته.