بهینه‌سازی عملکرد

مدیریت صحیح حافظه یکی از …

مهم‌ترین عوامل در توسعه نرم‌افزارهای Embedded است.

در این آموزش با روش‌های Memory Optimization در STM32 آشنا می‌شوید؛از جمله:

  • کاهش مصرف RAM و Flash، مدیریت Stack و Heap، استفاده از تخصیص حافظه ایستا (Static Allocation)، جلوگیری از Memory Fragmentation، بهینه‌سازی Bufferها، تحلیل مصرف حافظه با فایل MAP و تنظیم Linker Script برای استفاده بهینه از منابع سخت‌افزاری میکروکنترلر.

فهرست صفحه

برای اجرای سریع‌تر و کارآمدتر برنامه‌ها، استفاده بهینه از تایمرها و وقفه‌ها ضروری است.

این ابزارها می‌توانند کارهایی مانند کنترل زمان، شمارش رویدادها، یا پاسخ به سیگنال‌های خارجی را با دقت و سرعت بالا انجام دهند.

چرا استفاده از تایمرها و وقفه‌ها مهم است؟

کاهش تأخیر:

  • با استفاده از وقفه‌ها، پردازنده می‌تواند به سرعت به وقایع خاص واکنش نشان دهد.

پردازش غیر همزمان:

  • با استفاده از وقفه‌ها می‌توانید به‌طور همزمان چندین وظیفه را انجام دهید بدون اینکه نگران وقفه در وظایف دیگر باشید.

مثال کد:

  • با استفاده از وقفه تایمر برای انجام یک عمل هر 1 میلی‌ثانیه :
				
					#include "stm32f4xx.h"

void TIM2_IRQHandler(void) {
    if (TIM2->SR & TIM_SR_UIF) {  // بررسی پرچم وقفه تایمر
        TIM2->SR &= ~TIM_SR_UIF;  // پاک کردن پرچم وقفه
        // ✅ کد انجام عمل در هر 1 میلی‌ثانیه
    }
}

int main(void) {
    // تنظیمات تایمر
    // فعال‌سازی وقفه تایمر
    while(1) {
        // انجام کارهای دیگر
    }
}

				
			

بهینه‌سازی کد برای عملیات حساس به زمان در بسیاری از سیستم‌ها، عملیات‌هایی وجود دارند که نیازمند سرعت بالا و پاسخ‌دهی سریع هستند.

این عملیات‌ها نباید تأخیر زیادی داشته باشند و باید هرچه سریع‌تر انجام شوند.

نکات کلیدی :


استفاده از وقفه‌ها برای پردازش سریع‌تر :

  • به‌جای استفاده از کدهای طولانی یا حلقه‌های تاخیر، از وقفه‌ها استفاده کنید تا پردازش سریع‌تر انجام شود.

حذف دستورات غیرضروری :

  • از توابعی که باعث تأخیر در اجرای برنامه می‌شوند مانند delay() استفاده نکنید.

عملیات‌ها را به بخش‌های کوچک تقسیم کنید :

  • برای کارهای حساس به زمان، کد باید در قطعات کوچک و سریع اجرا شود.

مثال کد :

  • انجام پردازش سریع در ISR (Interrupt Service Routine) :
				
					#include "stm32f4xx.h"

void EXTI0_IRQHandler(void) {
    if (EXTI->PR & EXTI_PR_PR0) {  // چک کردن پرچم وقفه
        EXTI->PR |= EXTI_PR_PR0;   // پاک کردن پرچم وقفه
        // ✅ پردازش سریع در اینجا انجام می‌شود.
    }
}

int main(void) {
    // تنظیمات اولیه
    // اجرای کدهای دیگر در حلقه اصلی
}

				
			

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

چرا باید از Assembly استفاده کرد ؟


افزایش سرعت :

  • عملیات‌ها می‌توانند بسیار سریع‌تر از کد C باشند.

دسترس‌پذیری بیشتر به منابع سخت‌افزاری :

  • با Assembly می‌توان به‌طور دقیق‌تری کنترل بر روی سخت‌افزار داشت.

مثال کد :

  • ضرب دو عدد با دستورالعمل Assembly :
				
					__asm("MUL R0, R1, R2");  // ضرب سریع دو عدد با Assembly

				
			

بهینه‌سازی حافظه کش برای سرعت بیشتر حافظه کش (Cache) به‌منظور ذخیره داده‌هایی که به‌طور مکرر استفاده می‌شوند، طراحی شده است.

بهینه‌سازی دسترسی به کش می‌تواند باعث افزایش سرعت پردازش‌ها شود.

نکات بهینه‌سازی کش :

دسترسی به داده‌ها به‌صورت متوالی (Locality of Reference) :

  • داده‌ها باید به‌صورت مرتب ذخیره شوند تا کش بهتر بتواند آنها را بارگذاری کند.

اجتناب از دسترسی‌های غیرضروری :

  • هر دسترسی به حافظه کش هزینه‌بردار است، پس باید از دسترسی‌های غیرضروری جلوگیری کرد.

استفاده بهینه از کش داده‌ها :

  • اگر پردازنده اجازه دهد، می‌توان کش را به‌طور دستی مدیریت کرد.

مثال کد :

  • با استفاده از حافظه کش با ذخیره‌سازی داده‌ها به‌صورت متوالی :
				
					int array[100];
for(int i = 0; i < 100; i++) {
    array[i] = i * 2;  // ذخیره‌سازی داده‌ها به‌صورت متوالی
}

				
			

استفاده از پردازنده‌های چند هسته‌ای (Multi-core Processing) در پردازنده‌های چند هسته‌ای، می‌توان فرآیندها را به‌صورت موازی اجرا کرد.

این روش می‌تواند سرعت عملکرد را به‌شدت افزایش دهد.

نکات برای استفاده بهینه از Multi-core :

تقسیم بار کاری بین هسته‌ها:

  • وظایف را بین هسته‌ها تقسیم کنید تا از تمام هسته‌ها استفاده بهینه شود.

هماهنگ‌سازی بین هسته‌ها:

  • از ابزارهایی مانند Semaphore یا Mutex برای هماهنگ‌سازی هسته‌ها استفاده کنید.

اجرای پردازش‌های مستقل بر روی هسته‌های مختلف:

  • هر هسته می‌تواند یک Task متفاوت را انجام دهد.

مثال کد :

  • با استفاده از FreeRTOS برای اجرای Task‌های مختلف روی هسته‌های مختلف :
				
					void Task1(void *pvParameters) {
    // کد Task1 برای هسته اول
}

void Task2(void *pvParameters) {
    // کد Task2 برای هسته دوم
}

int main(void) {
    xTaskCreate(Task1, "Task 1", 100, NULL, 1, NULL);
    xTaskCreate(Task2, "Task 2", 100, NULL, 1, NULL);
    vTaskStartScheduler();
}

				
			

بهینه‌سازی کد برای کاهش پیچیدگی‌های غیرضروری برای بهبود عملکرد سیستم، باید از کدهای ساده و بهینه استفاده کرد.

پیچیدگی‌های اضافی در کد می‌توانند باعث افزایش زمان اجرا و منابع مصرفی شوند.

نکات بهینه‌سازی کد :

حذف کدهای غیرضروری :

  • از توابع و دستورات اضافی که هیچ کاربردی ندارند، پرهیز کنید.

استفاده از کدهای کوتاه و کارآمد :

  • کدهایی که عملکرد مشابه را با دستورات کمتر و سریع‌تر انجام می‌دهند، انتخاب کنید.

استفاده از دستورالعمل‌های بهینه :

  • برای عملیات‌های پر تکرار از دستورات سریع‌تر مانند جمع یا ضرب با دستورالعمل‌های داخلی پردازنده استفاده کنید.

مثال کد :

  • کاهش پیچیدگی در تابع بازگشتی، قبل از بهینه‌سازی:
				
					int factorial(int n) {
    if (n == 0) return 1;
    return n * factorial(n - 1);
}

				
			

پس از بهینه‌سازی :

				
					int factorial(int n) {
    int result = 1;
    while (n > 0) {
        result *= n--;
    }
    return result;
}

				
			

در نسخه بهینه‌شده از حلقه استفاده شده تا از بازگشتی که مصرف حافظه بالایی داشت جلوگیری شود.

استفاده از SIMD برای پردازش موازی داده‌ها در پردازنده‌های مدرن، دستورالعمل‌های SIMD به‌طور همزمان روی چندین داده مشابه کار می‌کنند، که باعث بهبود کارایی در پردازش‌های پر حجم می‌شود.

چرا SIMD مهم است؟

پردازش موازی داده‌ها :

  • به‌جای انجام عملیات‌ها به‌طور سریالی روی هر عنصر، از SIMD می‌توانید عملیات را روی چندین عنصر هم‌زمان انجام دهید.

افزایش سرعت پردازش :

  • با استفاده از SIMD می‌توان زمان پردازش داده‌های مشابه را به طور قابل توجهی کاهش داد.

مثال کد :

  • با استفاده از SIMD در پردازش آرایه‌ها :
				
					#include <arm_neon.h>

void process_data(int *data, int length) {
    int32x4_t data_vec;
    for (int i = 0; i < length; i += 4) {
        data_vec = vld1q_s32(&data[i]);
        data_vec = vaddq_s32(data_vec, vdupq_n_s32(1));  // افزایش مقدار هر عنصر به 1
        vst1q_s32(&data[i], data_vec);
    }
}

				
			

در این مثال، با استفاده از دستورالعمل‌های SIMD در پردازنده ARM، عملیات روی 4 داده به‌طور همزمان انجام می‌شود.

استفاده از Inline Functions برای سرعت بیشتر استفاده از Inline Functions می‌تواند به‌طور چشمگیری زمان اجرا را کاهش دهد.

این توابع به‌جای فراخوانی، در کد جایگزین می‌شوند.

مزایای استفاده از Inline Functions :

کاهش زمان فراخوانی تابع :

  • به‌جای اجرای یک تابع معمولی که به حافظه و پردازنده نیاز دارد، کد تابع مستقیماً در محل فراخوانی قرار می‌گیرد.

افزایش سرعت :

  • در کدهایی که نیاز به فراخوانی توابع کوچک دارند، استفاده از Inline باعث کاهش تأخیر و افزایش سرعت اجرا می‌شود.

مثال کد :

  • با استفاده از Inline Function :
				
					inline int square(int x) {
    return x * x;
}

int main() {
    int a = square(5);  // بدون نیاز به فراخوانی تابع، مستقیماً در اینجا انجام می‌شود
    return 0;
}

				
			

مدیریت حافظه برای عملکرد بهتر برای بهینه‌سازی عملکرد سیستم‌های تعبیه‌شده، مدیریت حافظه بسیار اهمیت دارد.

تخصیص نادرست حافظه می‌تواند باعث کندی سیستم شود.

نکات مهم در مدیریت حافظه :

استفاده از حافظه پشته (Stack) به‌جای هپ (Heap) :

  • تخصیص حافظه از پشته معمولاً سریع‌تر است و بهتر است از آن برای داده‌های با عمر کوتاه استفاده کنید.

اجتناب از تخصیص مکرر حافظه :

  • هر بار که حافظه تخصیص می‌دهید و آزاد می‌کنید، عملکرد سیستم تحت تأثیر قرار می‌گیرد. سعی کنید تخصیص‌ها و آزادسازی‌ها را به حداقل برسانید.

استفاده از حافظه‌ای با مدیریت خودکار :

  • در سیستم‌های پیچیده، برای مدیریت حافظه می‌توان از سیستم‌های تخصیص حافظه خودکار استفاده کرد.

مثال کد :

  • تخصیص حافظه به‌طور مؤثر با پشته :
				
					void process_data() {
    int arr[100];  // استفاده از حافظه پشته برای داده‌های کوتاه‌مدت
    // پردازش داده‌ها در اینجا
}

int main() {
    process_data();
    return 0;
}

				
			

بهینه‌سازی زمان‌بندی تسک‌ها برای پردازش بهتر در سیستم‌های چندوظیفه‌ای، استفاده بهینه از زمان‌بندی تسک‌ها (Task Scheduling) می‌تواند عملکرد را بهبود بخشد.

مزایای بهینه‌سازی زمان‌بندی :

تخصیص کارآمد منابع :

  • زمان‌بندی درست باعث می‌شود منابع به‌طور بهینه بین تسک‌ها تقسیم شوند.

کاهش تأخیر:

  • زمان‌بندی مناسب می‌تواند تأخیر در پردازش‌های حساس به زمان را کاهش دهد.

مثال کد :

  • با استفاده از FreeRTOS برای زمان‌بندی تسک‌ها :
				
					void Task1(void *pvParameters) {
    while (1) {
        // انجام کارهای مربوط به Task1
        vTaskDelay(100 / portTICK_PERIOD_MS);  // تأخیر 100 میلی‌ثانیه
    }
}

void Task2(void *pvParameters) {
    while (1) {
        // انجام کارهای مربوط به Task2
        vTaskDelay(200 / portTICK_PERIOD_MS);  // تأخیر 200 میلی‌ثانیه
    }
}

int main(void) {
    xTaskCreate(Task1, "Task1", 100, NULL, 1, NULL);
    xTaskCreate(Task2, "Task2", 100, NULL, 1, NULL);
    vTaskStartScheduler();
    return 0;
}

				
			
error: Content is protected !!
پیمایش به بالا