کیل یا gcc! از کدام برای برنامهنویسی استفاده میکنید؟ سؤال خیلی از دوستان تازهکار و حتی حرفهای این است که کدام کامپایلر بهتر است و بهترِ از کدامیک استفاده کرد؟ البته لازم است یک پرانتز باز کنم و به این مهم اشارهکنم که همیشه این چالش وجود داشته که با چه چیز و از کجا شروع کنیم! و همیشه این جواب رو خیلی صریح و واضح دادم که شما شروع کن بعد مسیر را اصلاح کن! متأسفانه این مقوله تبدیل به وسواس فکری برای عدهای شده است و بهانهای برای اینکه شروع نکنند! همیشه در نخ این هستند که کدام میکرو بهتر است یا کدام نرمافزار بهتر کار میکند – واقعاً میکرو و کامپایلر چند درصد از یک پروژه رو به خودش اختصاص میدهند؟ اما اگر بهعنوان کسی که شروع کرده و در حال کار است و الان قصد دارد کار و مهارتش رو اصلاح کند و ارتقاء دهد این مقاله را مطالعه می کنید باید بگویم که بهترین راه همین راهی است که شما تاکنون طی کردهاید.
چی شد که این مقاله را نوشتم
وقتیکه الکترونیک را شروع کردم هنوز میکروکنترلر وجود نداشت (حداقل در دسترس من) و من اولین کامپیوتر را خودم با اتکا به کتاب Build Your Own Z80 Computer آن هم با چه مکافاتی ساختم و این برای من شروع دنیای دیجیتال امروزی بود. برنامهنویسی را هم به شخصه با basic شروع کردم آن هم با کمک موجودی به اسم Comodor 64 (همین عکسی که میبینید) ! این موجود یک کامپیوتر واقعی نبود یک مفسر زبان بیسیک بود!، روشن که میشد تنها میتوانست دستورات بیسیک را درک کند (البته اخیرا فهمیدم که دوستان تنها آن را به اسم کنسول بازی قدیمی میشناسند) اما آیا هنوز دارم با مفسر بیسیک کار میکنم یا از Z80 استفاده میکنم ؟ قطعا خیر! اینها واقعا حس نوستالوژی برای من دارند ولی همیشه باید در مسیر پیشرفت بود و از اشتباه نترسید! بله من هم ده سال پیش وقتی که کدویژن ورژن ۱٫۰٫۴ بود از آن استفاده میکردم. فهمیدم کدویژن چیز جالبی نیست و از کامپایلر gcc (اگه کنجکاو هستید بدانید چرا کدویژن خوب نیست مقاله کامپایلر Codevisionavr در مقابل کامپایلر GCC و مقایسه تخصصی آنها و چرا کدویژن نه! را مطالعه کنید.) استفاده کردم. تقریبا تا الان تنها از gcc استفاده کردهام . برای هر میکروکنترلری که لازم بوده استفاده کنم نسخه رایگان داشته و چی بهتر از این. در ضمن امیدوارم که این مقاله بتواند در تصمیم گیری به شما کمک کند تا راه درست را انتخاب کنید! دقت کنید راه درست لزوما انتخاب keil یا gcc نیست بلکه شناخت شرایط پیش رو است.
معرفی کامپایلر کیل
احتمالا اسم کیل را که بشنوید بیتردید به یاد میکروکنترلرهای ARM خواهید افتاد! احتمالا بسته به شرایط موجود در ایران بیشتر stm32 را در ذهن شما تداعی کند! ولی خوب همیشه اینطور نبوده است! ممکن است شما به یاد نداشته باشید ولی در واقع keil یکی از اولین کامپایلرهای c موجود برای 8051 بود و البته فکر میکنم هنوز هم باشد! البته قبل از این که توسط شرکت ARM خریداری شود و برای برنامهنویسی هسته ARM توسعه داده شود! (این ها را که میگویم احساس پیری بهم دست میده ! چون احتمالا کسی دیگه 8051 کار نمیکنه و احتمالا خیلی ها هم حتی نشناسنش ? ) همانطور که احتمالا تا الان متوجه شدهاید این کامپایلر و ادیتور توسط شرکت ARM برای استفاده از میکروکنترلرهای جدید ARM توسعه داده شده است! شاید معتقد باشید که چون این کامپایلر توسط شرکت ARM توسعه داده میشود بهترین گزینه برای برنامهنویسی این میکروکنترلرها است! و البته در ایران به دلیل این که بیشتر آموزشها از این کامپایلر استفاده میکنند بیشتر افراد این کامپایلر را به صورت پیشفرض برای برنامه نویسی انتخاب میکنند.
شرایط مقایسه کامپایلرها
ممکن است خیلیها بنا به سلیقه یا اعتقادات غیر علمی یک کامپایلر را از دیگری بهتر بدانند و حتی حاضر به مجادله باشند. ما قرار نیست چنین رویکردی داشته باشیم! و تنها چیزی را ارائه خواهیم کرد که قابل استناد و تکرار در شرایط یکسان باشد (شرایط استاندارد یک آزمایش علمی) برای همین هفت چالش (شما بخوانید برنامه) ساده طراحی کردم و برنامه را هم با کامپایلر KEIL و هم GCC کامپایل کردم و نتیجه را با در مقایسه با هم بررسی کردم. برای انجام این مقایسه از کیل ورژن 5.15.0 و gcc ورژن 9.2.1 (که فکر میکنم شاید نتیجهها روی ورژن ۴ بهتر هم باشه) استفاده کردم در کدها از هیچ کتابخانهای نظیر HAL یا SPL یا LL استفاده نشده است تا نتایج واقعیتر باشند، تنظیمات هر دو کامپایلر بر روی اپتیمایز برای حجم (Os-) تنظیم شده است.
تمام نتایج از تست بر روی میکروکنترلر STM32F103CBT6 که با فرکانس ۷۲ مگاهرتز (کریستال و PLL) تنظیم شده است منتج گردیده است.
نحوه انجام تست ها به این شکل است که ما یک سری عملیات (ریاضی، منطقی، شرطی) را انجام میدهیم بعد وضعیت یک پایه رو تغییر میدهیم و این کار را مداما تکرار میکنیم – هرچه فرکانس ایجاد شده بر روی پایه مورد نظر بیشتر باشد به این معنی خواهد بود که کیفیت کد تولید شده بهتر است و با سرعت بیشتری در حال اجراست.
چالش نخست
در این چالش که در واقع ساده ترین چالش ممکن است – بعد از مقدار دهی اولیه پورت یک بیت را صفر و یک میکنیم! صفر و یک کردنها نه با استفاده از رجیسترهای مربوطه(BSRR , BRR) بلکه با استفاده انجام عملیات منطقی است.
int main(void) { /*GPIOA Enable Clock*/ RCC->APB2ENR |= (1<<2); /*GPIO A.0 As Output*/ GPIOA->CRL &= ~(0x0000000F); GPIOA->CRL |= 0x00000003; // Infinite loop while (1) { GPIOA->ODR |=(1<<0); GPIOA->ODR &=~(1<<0); } // Infinite loop, never return. }
و نتیجه غافلگیر کننده بود، واقعا انتظار نداشتم که در این مرحله تفاوت چندانی وجود داشته باشد:
همانطور که در نمودار بالا میبینید کد تولید شده توسط کامپایلر gcc با سرعت بیشتری اجرا شده است و توانسته روی پایه میکروکنترلر فرکانس ۴ مگاهرتز را ایجاد کند در حالی که keil تنها ۳٫۲ مگاهرتز را ایجاد کرده است.
چالش دوم اعداد اعشاری
در این چالش ما در حلقه یک محاسبه خیلی ساده (تنها جمع) بر روی متغیر float انجام دادیم البته مطابق برنامه زیر:
volatile float var = 0.0f; int main(int argc, char* argv[]) { /*GPIOA Enable Clock*/ RCC->APB2ENR |= (1<<2); /*GPIO A.0 As Output*/ GPIOA->CRL &= ~(0x0000000F); GPIOA->CRL |= 0x00000003; // Infinite loop while (1) { var += 0.01f; GPIOA->ODR ^=(1<<0); } // Infinite loop, never return. }
و باز نتیجه شگفتآور بود (حداقل برای من)
نکته مهم: همیشه سعی کنید تا جای ممکن از محاسبات اعشاری دوری کنید! محاسبات اعشاری فرایند زمان بری است حتی برای میکروکنترلر های ۳۲ بیتی! اگر نتیجه چالش قبل رو با این چالش مقایسه کنید متوجه خواهید شد که یک جمع اعشاری ده برابر سرعت برنامه را کاهش داده است.
چالش سوم محاسبات ریاضی و منطقی
در این چالش انجام محاسبات ریاضی بر روی متغییر های ۳۲ بیتی خواهیم داشت به همراه مقداری عملیات منطقی و البته صدا زدن تابع
uint8_t foo(uint32_t x) { x = x - ((x >> 1) & 0x55555555); x = (x & 0x33333333) + ((x >> 2) & 0x33333333); x = (x + (x >> 4)) & 0x0F0F0F0F; x = x + (x >> 8); x = x + (x >> 16); return x & 0x0000003F; } int main() { /*GPIOA Enable Clock*/ RCC->APB2ENR |= (1<<2); /*GPIO A.0 As Output*/ GPIOA->CRL &= ~(0x0000000F); GPIOA->CRL |= 0x00000003; uint32_t x = 0xFAFAAFAF; // Infinite loop while (1) { GPIOB->ODR = foo(x++); GPIOA->ODR ^=(1<<0); } // Infinite loop, never return. }
دراین چالش علاوه بر محاسبات ریاضی ساده (جمع) عملیات منطقی هم خواهیم داشت نظیر and و شیفت دادن متغییر و اما نتیجه:
چالش چهارم محاسبات ۶۴ بیتی
در این چالش دقیقا اعمال چالش قبل بر روی متغییرهای ۶۴ بیتی انجام شده است
uint8_t foo(uint64_t x) { x = x - ((x >> 1) & 0x5555555555555555); x = (x & 0x3333333333333333) + ((x >> 2) & 0x3333333333333333); x = (x + (x >> 4)) & 0x0F0F0F0F0F0F0F0F; x = x + (x >> 8); x = x + (x >> 16); return x & 0x000000000000003F; } int main() { /*GPIOA Enable Clock*/ RCC->APB2ENR |= (1<<2); /*GPIO A.0 As Output*/ GPIOA->CRL &= ~(0x0000000F); GPIOA->CRL |= 0x00000003; uint64_t x = 0xFAFAFAFAFAFAFAFA; // Infinite loop while (1) { GPIOB->ODR = foo(x++); GPIOA->ODR ^=(1<<0); } // Infinite loop, never return. }
و نتیجه زیاد غیر منطقی هم نبود
همانطور که طول متغییرها دوبرابر شده انتظار میرفت سرعت هم نصف شده باشد که باز gcc بهتر عمل کرده است و کمتر از نصف سرعتش کاهش پیدا کرده !!
چالش پنجم تابع بازگشتی
حتی عنوان این چالش هم ممکنه برای خیلی ها مبهم باشه چه برسه به کاربردش ! برای این چالش تابع فاکتوریل رو با تابع بازگشتی پیاده سازی کردم
long int foo(int n) { if (n>=1) return n*foo(n-1); else return 1; } int main() { /*GPIOA Enable Clock*/ RCC->APB2ENR |= (1<<2); /*GPIO A.0 As Output*/ GPIOA->CRL &= ~(0x0000000F); GPIOA->CRL |= 0x00000003; int x = 20; // Infinite loop while (1) { GPIOB->ODR = foo(x) & 0xFF; GPIOA->ODR ^=(1<<0); } // Infinite loop, never return. }
واقعا نتیجه این تست شگفت انگیزه فکر نمیکردم اینقدر اختلاف وجود داشته باشد gcc نزدیک به ۱۸۰۰ کیلوهرتز و keil نزدیک به ۶۰ کیلوهرتز :/
چالش ششم محاسبه CRC32
یکی از پر کاربرد ترین الگوریتمهای موجود در برنامهنویسی سختافزار، الگوریتمهای خطایابی دیتا هستند که شاید پر استفادهترین آنها در دنیای میکروکنترلرها الگوریتمهای crc باشند – برای همین در این چالش این الگوریتم رو انتخاب کردم
const uint8_t simpletxt[] = "Permission is hereby granted, free of charge, to any person\ * obtaining a copy of this software and associated documentation\ * files (the \"Software\"), to deal in the Software without\ * restriction, including without limitation the rights to use,\ * copy, modify, merge, publish, distribute, sublicense, and/or\ * sell copies of the Software, and to permit persons to whom\ * the Software is furnished to do so, subject to the following\ * conditions:\ *\ * The above copyright notice and this permission notice shall be\ * included in all copies or substantial portions of the Software.\ *\ * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\ * OTHER DEALINGS IN THE SOFTWARE."; uint32_t CRC32_function(uint8_t *buf, uint32_t len) { uint32_t val, crc; uint8_t i; crc = 0xFFFFFFFF; while(len--){ val=(crc^*buf++)&0xFF; for(i=0; i<8; i++){ val = val & 1 ? (val>>1)^0xEDB88320 : val>>1; } crc = val^crc>>8; } return crc^0xFFFFFFFF; } int main() { /*GPIOA Enable Clock*/ RCC->APB2ENR |= (1<<2); /*GPIO A.0 As Output*/ GPIOA->CRL &= ~(0x0000000F); GPIOA->CRL |= 0x00000003; // Infinite loop while (1) { GPIOB->ODR = CRC32_function((uint8_t*)simpletxt,sizeof(simpletxt)); GPIOA->ODR ^=(1<<0); } // Infinite loop, never return. }
و اما نتیجه
باید اشاره کنم مقداری ناامید کننده است برای gcc ! اینجا کیل با حدود ۱۰ کیلوهرتز بهتر عمل کرده !
چالش هفتم اشاره گر تابع
احتمالا باید با مفهوم اشارهگرها آشنا باشید – اشارهگر به تابع یکی از پرکاربردترین نوع اشارهگر است وقتی قصد داشته باشیم کتابخانههای سطح بالا نظیر HAL رو پیاده سازی کنیم.
uint8_t fun1(int x){return x>>0; } uint8_t fun2(int x){return x>>8; } uint8_t fun3(int x){return x>>16; } uint8_t fun4(int x){return x>>24; } int main() { /*GPIOA Enable Clock*/ RCC->APB2ENR |= (1<<2); /*GPIO A.0 As Output*/ GPIOA->CRL &= ~(0x0000000F); GPIOA->CRL |= 0x00000003; int x = 0xFFAAFFAA; uint8_t i = 0; uint8_t (*fun_ptr[])(int) = {fun1,fun2,fun3,fun4}; // Infinite loop while (1) { GPIOB->ODR = (*fun_ptr[i++%4])(x++); GPIOA->ODR ^=(1<<0); } // Infinite loop, never return. }
همونطور که قبلا اشاره کردم کتابخونه های سطح بالایی مثل hal که برای stm32 است با استفاده از قابلیت زبان پیاده سازی شدند و اگر شما از gcc استفاده کنید نتایج بهتری خواهید گرفت در مقایسه با keil و برای همین بود که در تست ها از هیچ کتابخانه ای استفاده نکردیم !
گام های بیشتر
احتمالا برای این که بتوانیم مقایسه کاملتری داشته باشیم باید نحوه رسیدگی به اینتراپتها و خیلی موارد دیگر را هم بررسی میکردیم! که با توجه به زمان و بحثهای فنی و نحوه صحیح انجام تست فعلا این موارد را کنار گذاشتیم ولی احتمالا در نتیجه نهایی فرق چندانی ایجاد نکند
جمع بندی نهایی
قبل از این که به جمع بندی نهایی برسیم – بگذارید نتایج تستها رو کنار هم داشته باشیم
همونطور که قبلا هم اشاره کردیم عدد بزرگتر در نمودار یعنی نتیجه بهتر – کیل تنها در مورد محاسبه crc و البته محاسبات ممیز شناور بهتر از gcc عمل کرده و در مابقی موارد gcc عملکرد بمراتب بهتری داشته است. در واقع اگر بخواهیم به برایند تست ها نگاه کنیم در مییابیم که gcc انتخاب عاقلانهتری است! ولی آیا این بدین معنا است که کیل کامپایلر خوبی نیست؟ قطعا خیر! به شخصه توصیه میکنم از keil استفاده نکنید نه از این منظر که کامپایلر خوبی نیست بلکه از منظر اخلاقی صحیح نیست! درسته کرک کردن نرم افزارهای پولی در ایران غیرقانونی نیست حتی اگر فرض کنیم که به علت عدم خدمات این شرکتها، کرک اون هم اخلاقی باشه ولی مشکلات عدیده ای که حتی میتونه این کرک ایجاد کنه یکی از مواردی هست که بنظرم باید درموردش بیشتر فکر کرد و میتونه در تصمیم گیری شما تاثیر گذار باشه فکر میکنم اگر به این مقایسه این گونه نگاه کنید که ابزار مورد استفاده شما در کجا ضعیف و در چه مواردی قوی است بتوانید بهترین خروجی را از آن دریافت کنید.
منبع : سیسوگ