farhad shiri
farhad shiri
خواندن ۷ دقیقه·۵ سال پیش

اثبات فنی کند بودن سرعت اجرای کدها در ویندوز نسبت به لینوکس

از آنجائیکه نحوه برنامه نویسی به زبان اسمبلی در سیستم عامل ویندوز با استفاده از اسمبلر MASM تعریف میشود، و از آنجائیکه اصولا ساختار کدنویسی در این اسمبلر نسبت به سایر اسمبلر ها مانند GASM,TASM برای پردازشگر های Intel در نحوه صرف دستورات تفاوت های عمده ای دارد، ولی طبعا فهمیدن و درک کدها در اسمبلرهای ویندوز به مراتب آسانتر هست ، متاسفانه برنامه نویس به جهت همین سهولت، در اغلب اوقات هزینه زیادی بابت بازدهی برنامه ها و سرعت عملکرد یک برنامه کاملا مشابه در دو محیط لینوکس و ویندوز را باید پرداخت کند! علی الخصوص که شما در حال نوشتن یک برنامه سیستمی باشید.

کاملا مشخصه که در این دست از برنامه ها(سیستمی) بازدهی و سرعت اجرای الگوریتم ها بسیار مهم و در برخی موارد هم بسیار حیاتی وایمنی می باشد، مانند سیستم های ناوبری , سیستم های حیاتی ایمنی نیروگاهها , ... و خیلی موارد دیگر که می توان ذکر کرد.

برای همین تصمیم گرفتم که این موضوع را به صورت یک مثال فنی توضیح دهم امیدوارم مفید فایده قرار گیرد.

تصور کنید که چنین کدی را در زبان C نوشته اید...

#ifdef _WIN64 #define INT64_PRINTF_FORMAT &quotI64&quot #else #define __int64 long long #define INT64_PRINTF_FORMAT &quotL&quot #endif #include <stdio.h> typedef struct { __int64 lo64; __int64 hi64; } my_i128; #define ADD128(out, in1, in2) \ __asm__(&quotaddq %2, %0; adcq %3, %1&quot : \ &quot=r&quot(out.lo64), &quot=r&quot(out.hi64) : \ &quotemr&quot (in2.lo64), &quotemr&quot(in2.hi64), \ &quot0&quot (in1.lo64), &quot1&quot (in1.hi64)); extern int main() { my_i128 val1, val2, result; val1.lo64 = ~0; val1.hi64 = 0; val2.hi64 = 65; ADD128(result, val1, val2); printf(&quot0x%016&quot INT64_PRINTF_FORMAT &quotx%016&quot INT64_PRINTF_FORMAT &quotx\n&quot, val1.hi64, val1.lo64); printf(&quot+0x%016&quot INT64_PRINTF_FORMAT &quotx%016&quot INT64_PRINTF_FORMAT &quotx\n&quot, val2.hi64, val2.lo64); printf(&quot------------------------------------\n&quot); printf(&quot0x%016&quot INT64_PRINTF_FORMAT &quotx%016&quot INT64_PRINTF_FORMAT &quotx\n&quot, result.hi64, result.lo64); return 0; }

همانطور که مشاهده میکنید در این برنامه از تکنیک Inline Assembly با نحو استاندارد AT&T استفاده شده است، در حقیقت کار این برنامه خیلی ساده است جمع کردن مقادیر 1- , 0 , 1 , 65 که مستقیما در کد اسمبلی نوشته شده جمع می شوند و نتیجه در خروجی مشاهده خواهد شد.

بنابراین در صورتی که این کد را در لینوکس x86-64 با کامپایلر GCC کامپایل کنید، خروجی کد اسمبلی که GCC برای برنامه ساخته چیزی شبیه برنامه زیر خواهد بود...

mov r13, 0xffffffffffffffff mov r12, 0x000000000 add r13, 1 adc r12, 65

که عملکرد این کد هم کاملا واضح می باشد.

ولی اگر برنامه بالا را در ویندوز 32 بیتی کامپایل کنید( شایان ذکر است وقتی کامپایلر فایل اسمبلی را بر روی ویندوز تولید می کند ، از نحوی Intel استفاده می کند ، حتی اگر از دستورات اسمبلی AT&T استفاده شده باشد.) بنابراین در صورتی که کد Inline اسمبلی برنامه مذکور خود را به نحو MASM بنویسم به شکل زیر...

#define ADD128(out, in1, in2) \ { \ __asm mov rax, in1.lo64 \ __asm mov rdx, in1.hi64 \ __asm add rax, in2.lo64 \ __asm adc rdx, in2.hi64 \ __asm mov out.lo64, rax \ __asm mov out.hi64, rdx \ }

همانطورکه مشاهده میکنید نحو صرف دستورات MASM به مراتب ساده تر هستند نسبت به نحو AT&T و اسمبلر GASM ، و بعد از کامپایل مجدد کد فوق در ویندوز خروجی اسمبلی زیر را خواهیم داشت...

mov QWORD PTR [rsp+32], -1 mov QWORD PTR [rsp+40], 0 mov QWORD PTR [rsp+48], 1 mov QWORD PTR [rsp+56], 65 ; Begin ASM mov rax, QWORD PTR [rsp+32] mov rdx, QWORD PTR [rsp+40] add rax, QWORD PTR [rsp+48] adc rdx, QWORD PTR [rsp+56] mov QWORD PTR [rsp+64], rax mov QWORD PTR [rsp+72], rdx ; End ASM mov rdx, QWORD PTR [rsp+72] mov r8, QWORD PTR [rsp+64]

بنابراین همانطور که مشاهده می شود عملیاتی که در لینوکس فقط با چهار دستورالعمل انجام داده شده است و همچنین هیچ مرجع حافظه ای با استفاده از inline اسمبلی در GNU انجام نشده است، به نسبت خروجی اسمبلی در ویندوز که از دوازه دستورالعمل ایجاد شده است برای همان الگوریتم با دوازده ارجاع به حافظه، واین یعنی دستورات کمتر در اسمبلی لینوکس مساوی است با کلاک کمتر در پردازشگر و همچنین سربار به مراتب کمتر در ارجاع به cach های پردازشگر و ... اگر اجرای هر دستور اسمبلی را در یک پردازشگر MIPS پنج کلاک در نظر بگیریم یعنی دستورات اسمبلی برنامه ما در لینوکس به 20 کلاک نیاز دارند، و در ویندوز به 60 کلاک نیاز دارند، بنابراین میتوان نتیجه گرفت که الگوریتم برنامه ما در لینوکس بسیار سریعتر اجرا خواهد شد تقریبا 3 برابر سریعتر از ویندوز.

و ذکر این موضوع هم که درست است که ویندوز کاربری ساده تری نسبت به لینوکس دارد اما در برخی مواقع سربارهایی که در نرم افزارها تحمیل میشود بسیار پر هزینه خواهد بود. و البته پیاده سازی خیلی از برنامه ها هم در لینوکس امکانپذیر نیست حتما باید در ویندوز انجام شود.

بنابراین هدف این مقاله ایراد گرفتن از سیستم عامل قدرتمند ویندوز نبوده است، بلکه هدف توضیح ساده ای از بازدهی یک الگوریتم مشابه در دوسیستم عامل متفاوت از نظر ساختار در دستورات کاملا مشابه و همچنین در یک سخت افزار کاملا مشابه، بوده است به این علت که در برخی موارد دانستن چنین مواردی در طراحی یک نرم افزار سیستمی بسیار مهم و حیاتی خواهد بود.

inline assemblyc programmingefficient code in gcclinux programming
یک توسعه دهنده نرم افزار
شاید از این پست‌ها خوشتان بیاید