توصیه شده, مسابقه

مسابقه چهارم: کدام حلقه سریع‌تر است؟

حدود ۷ ماه پیش، مسابقه سوم سیسوگ رو برگزار کردیم و کلی نکته در مورد خواندن رشته‌های ورودی را بررسی کردیم. فکر کردم که بد نیست یک چالش جدید داشته باشیم! البته چالش‌ها هیچ وقت بی حاشیه نیستند مثل سه مسابقه قبل، در واقع مسئله آینه که کنار هم چیزهای جدیدی یاد بگیریم نه اینکه بخواهیم بگیم کی بهتره!‌ هرکسی هرچقدر هم عالی باشه توی سطح خودش باز چیزهای جدید برای فراگرفتن هست.

 

مقدمه

اگر شبکه‌های اجتماعی سیسوگ رو دنبال می‌کنید (اگر دنبال نمی‌کنید توصیه می‌کنم دنبال کنید اینستاگرامتلگرامتوییتر) احتمالاً میدانید که چند هفته‌ای هست درگیر ساخت یه هوش مصنوعی شطرنج هستم که روی میکروکنترلر stm32 قابل اجراست و اولین قسمت این پروژه رو تحت عنوان “پیاده سازی هوش مصنوعی شطرنج” منتشر کردم. نکته‌ای که توی این پیاده سازی خیلی مهمه دریافت بهترین پرفورمنس از میکروکنترلر است. برای این که عملکرد قابل قبولی داشته باشه لازمه که یه سری بهینه سازی‌ها روی کد انجام بشه نظیر این که مثلاً این که توابع پر استفاده به حافظه RAM منتقل بشن یا تا جای ممکن کد بهینه بشه. ایده این مسابقه هم دقیقاً از همینجا میاد. برای چالش چهارم با سیسوگ همراه باشید.

 

صورت مساله

با فرض این که از میکروکنترلر STM32Fxxx استفاده می کنیم سرعت اجرای حلقه‌های زیر به چه صورت است؟

یا

فکر می‌کنید کدام حلقه سریع‌تر اجرا می‌شود؟ یا شاید سرعت اجرای برابری دارند! شما چه فکر می‌کنید؟

 

شرایط داوری و جایزه

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

به دو نفر از کسانی که بتوانند جواب صحیح را با ذکر دلیل ارائه دهند به قید قرعه مبلغ دو میلیون ریال جایزه نقدی تعلق خواهد گرفت.

 

ارسال جواب

پاسخ‌های خود را در زیر همین پست کامنت کنید.

ممکن است که لازم باشد کدی را برای ما کامنت می‌کنید، در قسمت کامنت نظم کد به هم می‌ریزد، بهتر است که ابتدا به سایت paste.ubuntu.com بروید، Syntax را زبان C انتخاب کنید و کد خود را در قسمت Content کپی کرده و بر روی Paste کلیک کنید و در نهایت فقط URL را در قسمت کامنت برای ما ارسال کنید.

مهلت پاسخ هم تا آخر روز شنبه 16 اسفند ماه ۱۳۹۹ است.

 

پایان و جواب چالش سوم سیسوگ

پاسخ این مسابقه در ادامه هم به صورت ویدئو و هم به صورت متن وجود دارد.

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

 

 

پاسخ متنی:

سوال مسابقه این بود که آیا نوع متغیر استفاده شده در حلقه، تاثیری در سرعت اجرای حلقه دارد یا خیر؟ برای همین شمارنده یک حلقه را از نوع char که یک بایتی است و حلقه دیگر را از نوع int که چهار بایتی است انتخاب کردیم.

و به عنوان یک کار مهم با استفاده از کلمه کلیدی volatile، کامپایلر را مجبور کردیم که متغیرها را درون RAM قرار بدهد. در واقع اگر ما از کلاس volatile استفاده نمی‌کردیم، ممکن بود کامپایلر متغیرها را درون رجیسترهای CPU قرار بدهد، که این موضوع مدنظر ما نبود.

خب برای تست اجازه بدهید برنامه را به صورت عملی بر روی یک میکروکنترلر اجرا کنیم.

برای این کار هر دو حلقه loop_x و loop_y را در main برنامه فراخوانی می‌کنیم، تا هر حلقه یک بار اجرا شود.

پس از این کار وارد محیط دیباگ برنامه می‌شویم تا کدهای اسمبلی معادل را بررسی کنیم.

 

کد اسمبلی:

 

خب می‌دانیم که قرار است متغیر i درون یک خانه از حافظه تعریف بشود، حال این خانه حافظه در کجا قرار دارد؟ این خانه حافظه در جایی قرار داد که SP به آن اشاره می‌کند. در ابتدا مقدار 200 را درون رجیستر R3 قرار می‌دهیم، سپس مقدار R3 را درون آن خانه از حافظه که گفتیم بارگذاری می‌کنیم. در واقع این عمل مقداردهی اولیه متغیر را انجام می‌دهد.

پس از آن وارد روتین حلقه می‌شویم، در روتین حلقه اولین کاری که نیاز است انجام بدهیم این است که مقدار متغیر را از حافظه بارگذاری و آن را درون رجیستر R3 قرار بدهیم.

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

همانطور که می‌دانید میکروکنترلر ما 32 بیتی است و محاسباتی که ALU یک میکروکنترلر 32 بیتی می‌تواند انجام بدهد، حتما باید 32 بیت باشد. برای همیسن قبل از اینکه ما بخواهیم مقداری را از رجیستر R3 کم بکنیم، نیاز است که متغیر یک بایتی را به یک متغیر 32 بیتی تبدیل بکنیم.

از آنجایی که متغیر ما علامتدار است، در این مرحله از دستور اسمبلی sxtb استفاده می‌شود که این دستور یک متغیر 8 بیتی علامت‌دار را به یک متغیر 32 بیتی علامت‌دار تبدیل می‌کند.

پس از این مرحله، ALU یک واحد از متغیر کم می‌کند و این متغیر در رجیستر R2 ذخیره می‌شود. و در نهایت R2 را در خانه حافظه ذخیره می‌کنیم.

حال اجازه بدهید ببینیم وقتی متغیر شمارنده 32 بیتی است چه اتفاقی می‌افتد. در اینجا در روتین حلقه، پس از اینکه متغیر بارگذاری شد، بدون اینکه طول متغیر افزایش یا کاهش یابد، متغیر یک واحد کاهش می‌یابد و در دوباره درون حافظه نوشته می‌شود.

در واقی تبدیل 8 به 32 و 32 به 8 بیت، سیکل‌های اضافی هستند که ما با تعریف متغیر یک بایتی به CPU تحمیل می‌کنیم. برای بهبود راندمان در اینجور مواقع ما می‌توانیم شمارنده حلقه را 32 بیتی تعریف کنیم.

اما اگر ما متغیرها را درون RAM تعریف نکنیم چه اتفاقی می‌افتد؟

برای اینکه متغیرها درون RAM قرار نگیرند، باید کلاس volatile را از ابتدای تعریف متغیر یک بایتی و چهار بایتی حذف کنیم.

 

کد اسمبلی:

در این حالت متغیر دیگر گسترش پیدا نمی‌کند، چون دیگر قرار نیست داخل RAM نوشته بشود. یکی از رجیسترهای CPU به عنوان متغیر در نظر گرفته می‌شود و بر روی آن محاسبات ریاضی انجام می‌شود. اما باز متغیر 32 بیتی سرعت اجرای بالاتری دارد چرا؟

چون برای یک متغیر 8 بیتی نیاز است که بررسی کنیم آیا سرریز رخ داده است یا نه، اما کنترل سرریز در متغیر 32 بیتی نیاز به بررسی توسط نرم‌افزار ندارد و به صورت سخت‌افزاری انجام می‌شود و نیاز به دستور اسمبلی ندارد.

پس توصیه می‌کنیم اگر میکروکنترلر شما 32 بیتی است، حتما از یک متغیر 32 بیتی برای شمارنده حلقه استفاده بکنید.

انتشار مطالب با ذکر نام و آدرس وب سایت سیسوگ، بلامانع است.

شما نیز میتوانید یکی از نویسندگان سیسوگ باشید.   همکاری با سیسوگ

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

89 دیدگاه در “مسابقه چهارم: کدام حلقه سریع‌تر است؟

  1. علی گفت:

    سلام جناب زئوس
    میبینم از خفا در امدید😂😂
    اتفاق خاصی افتاده قاعده رو عوض کردید🤨🤨
    خوب لااقل چشممون به جمالتون روشن شد☺☺

    1. Sisoog Os Sisoog Os گفت:

      سلام دوست عزیز
      البته ایشون خفا هم نبودند و قاعده خاصی هم نبوده:-)

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

      1. علی گفت:

        بله بله صد البته
        موفق باشید

  2. حلقه loop_y سریع‌تر است، دلیلش هم به Zero extending برمی‌گردد.
    اگر به کد اسمبلی این دو حلقه توجه کنیم، می‌بینیم که در حلقه اول دستور اسمبلی UXTB وجود دارد که در حلقه دوم وجود ندارد.
    دستور اسمبلی UXTB در واقع عمل Zero extending یا همون اضافه کردن 0 به سمت چپ عدد را انجام می‌دهد (به دلیل مثبت بودن، عدد 0 اضافه می‌شود. اگر عدد ما منفی بود، عدد 1 اضافه می‌شد البته با یک دستور اسمبلی دیگر.)
    حالا چرا این کار را انجام می‌دهد؟ چون نوع char با طول 8 بیت است، برای انجام عملیات ریاضی two’s complement که مرجع آن بالاتر از 8 بیت، خواه 16 بیت یا 32 بیت باشد در واقع این یک اجبار است و حتما باید عمل Zero extending انجام بشود.

    اما در نهایت دلیل اصلی این که چرا این کار در ARM انجام می‌شود برمی‌گردد به تئوری two’s complement که بحثش کمی مفصل است. اما به طور خلاصه برای انجام عملیات ریاضی در این سیستم اعداد باید طول اعداد از قبل مشخص باشد و اگر طول عددی کمتر از مرجع در نظر گرفته شده است حتما باید با توجه به علامتش اکستند شود.

    خب می‌شه خیلی بیشتر در این مورد توضیح داد. این توضیحات شاید بنا به کوتاه بودن کمی گنگ باشند، اما سعی کردم خلاصه و مفید دلیلش رو بگم.

    1. Zeus . گفت:

      دقیقا این اتفاق بخاطر ۳۲ بیت بودن پردازنده می افته و جواب شما درسته

  3. mobina mosannafat گفت:

    با توجه به نوع میکروکنترلر گفته شده که 32 بیتی است ، سرعت اجرای دومین حلقه که با int است بیشتر است. زیرا سخت افزار به گونه ای طراحی شده که برای داده های ۳۲ بیتی کارایی بهتری دارد.
    متغیر int ،
    32 بیت و متغیر char ،
    8 بیتی است … cpu ممکنه ممکن است دستور العمل هایی جهت load , store و opration روی داده های ۳۲ بیتی داشته باشد و چنین دستورالعمل هایی برای داده از نوع char وجود نداشته باشد ، حال برای بارگزاری یک مقدار 8 بیتی ، باید یک مقدار 32 بیتی را بارگزاری کند پس‌مجبور است این 8 بیت را extract کند که خود زمان میبرد …
    همچنین برای ذخیره char که 8 بیتی است ممکن است مجبور باشد ۳۲ بیت را بارگزاری کند ، پس برای تنظیم زیرمجموعه ۸ بیتی مورد نظر باید از عملیات bitwise استفاده کند و سپس آن را ذخیره کند

    1. Zeus . گفت:

      بله کاملا درست است.

  4. حسین گفت:

    سرعت لوپی که کانتر اون int هستش بیشتره، دلیلش هم اینه که دستورالعمل هایی برای عملیات ها وجود داره که به صورت ۳۲ بیتی کار میکنن، حالا ما از داده ۸ بیتی استفاده کنیم باید تبدیل داده انجام بدیدم که زمان میبره

    1. Zeus . گفت:

      بله همینطوره 🙂

  5. سرباز وطن گفت:

    ببخشید عجله کردم در ارسال دیدگاه
    دلیلش رو کامل توضیح ندادم
    یکی از دلایلی که دارم ساختار ALU هست وقتی واحد محاسبات منطقی هشت بیتی باشه برای یک داده 16 بیتی نیاز به دوبار پردازش اون داره ولی زمانی که این واحد 16 یا 32 بیتی باشه مسلما فرقی نمیکنه داده 16 32 یا 8 بیتی هست.

    خیلی دوست داشتم شرایط تستش رو سخت افزاری داشتم تا این تئوری رو ازمایش کنم

    به نظر اگر در یک شبیه ساز مانند پروتیوس میشد یک لایبرری قابل شبیه سازی پیدا کردبرای STM32 حتما یک بنچمارک میگرفتم بعد جواب میدادم با اینحا 3 ماه دیگه که خدمتم تموم شد حتما یه سر به این URL میزنم خیلی مشتاق پاسخ درست هستم…

    در سخن پایانی باید بگم وجود یک واحد محاسبه گر منطقی 32 بیتی فقط یکی از دلایل پردازش سریعتر حافظه میتونه باشه! برای اینکه پاسخ سریح تری بدم به وقت بیشتری برای مطالعه دیتاشیت یک میکروکنترلر پیچیده مانند ARM با اینهمه پیشرفت اونم برای اولین بار نیاز دارم

    پاینده و پیروز باشید زنده باد Open sorce

    1. Zeus . گفت:

      برای انجام محاسبات باید عدد توی یکی از رجیسترهای پردازنده لود بشه – و تمام رجیسترها ۳۲ بیت هستند- پس نیازه که برای لود یک عدد یک بایتی اونو کست کرد به ۳۲ بیت که همین کار زمان بیشتری رو در پردازنده میگیره.

  6. سرباز وطن گفت:

    ببخشید عجله کردم در ارسال دیدگاه

  7. سرباز وطن گفت:

    با سلام و خسته نباشید
    بنده تازه از AVR به Stm32 مهاجرت کردم (مهاجرت کلمه مناسبی نیست هردو میکروکنترلر عالی هستند) در risc های 8 بیتی داده نوع هشت بیتی زودتر پردازش میشه اما در مورد STM32 به نظرم با اینکه هنوز برگه اطلاعاتی اون رو مطالعه نکردم بخاطر معماریش زمان اجرای دو حلقه برابر هست یقین دارم

    1. Zeus . گفت:

      سلام دوست عزیز
      در واقع به دلیل این که ثبات ها و رجیسترهای میکروکنترلر ۳۲ بیتی هست باید عدد خوانده شده از روی حافظه به یک عدد ۳۲ بیتی تبدیل بشه تا محاسبات ریاضی روی اون انجام بشه و همین امر زمان بیشتری خواهد برد.
      در واقع حلقه دوم سریعتر اجرا میشه چون از همون ابتدا به شکل ۳۲ بیتی است و این عمل ۳۲ بیتی کردن داده نیاز نیست روی اون انجام بشه.

  8. محمد امراللهی گفت:

    عرض سلام و ادب
    حلقه دوم سریع تر است.
    با توجه به اینکه در stm32 باس حافظه و حافظه 32 بیتی است، در حلقه دوم int 32 بیتی است و بدون هیچ عملیات اضافه ای به آن دسترسی پیدا می کند اما در حلقه اول char یک بایت است و برای دسترسی به آن ابتدا باید تبدیل به 32 بیت شود تا در باس حافظه که 32 بیتی است قرار گیرد و در دسترس CPU قرار گیرد. بنابراین چند عملیات اضافه انجام می دهد.

    1. Zeus . گفت:

      بله کاملا درسته 🙂

  9. سینا فقیهی گفت:

    سرعت اجرا حلقه با متغیر اینتجر سریعتر است. متغیر اینتجر در کامپایلر های معمول اس تی ام ۳۲ به صورت یک عدد ۳۲ بیتی با علامت دیده میشه، با توجه به معماری کورتکس ام ۳ که از دستورات فشرده ۱۶ بیتی استفاده میکنند (بر خلاف نسل های قبلی مثل آرم ۷ ها که هم دستورات قوی ۳۲ بیتی داشتند و هم دستورات فشرده ۱۶ بیتی) محاسبه های ریاضی و منطقی فقط به صورت ۳۲ بیتی انجام میشود. در نتیجه عملیات های ۱۶ بیتی و ۸ بیتی(متغیر کاراکتر) با چند محاسبه و مقایسه به صورت ۳۲ بیتی انجام میشود که سرعت اجرا حلقه را کندتر میکند . برای اطمینان از این عملکرد میکروکنترلر میتوان سرعت اجرا برنامه را با یک تایمر یا حتی سیستیک تایمر هم استفاده کرد.

    1. Zeus . گفت:

      بله درسته همینطوره

  10. متغییر نوع char نمیتونه عدد ۲۰۰ رو ذخیره کنه و بجاش عدد منفی ۶۳ جاش میشینه. بعد اون حلقه char حدود ۶۴ بار اجرا میشه که به مراتب کمتر از حلقه int هست و این حلقه سریعتر اجرا میشه

    1. Zeus . گفت:

      متغییر char به شکل پیش فرض بدون علامت در نظر گرفته میشه !

      1. مصطفی حیدری گفت:

        نه عزیز. در تمام پلتفرم ها پیشفرض signd هست. از زمان z80 تا امروز صبح !!!!!

        1. Zeus . گفت:

          اوه – بله حق با شماست، توی ادیتوری که من استفاده میکنم به شکل پیش فرض تیک بی علامت بودن خورده
          با این حال شک نکنید حلقه ۲۰۰ بار اجرا خواهد شد – چرا ؟
          شما وقتی از منفی ۵۷ یکی کم کنید میشود منفی ۵۸ و این تا منفی ۱۲۸ ادامه خواهد داشت بعد به مثبت ۱۲۷ خواهید رسید و تا صفر ادامه خواهد داشت.
          پس حلقه شما همچنان ۲۰۰ بار اجرا خواهد شد.

          کاری که اغلب دوستان انجام دادند 🙂

        2. در واقع نه. signed یا unsigned بودن char بسته به پیاده‌سازی و معماری داره:
          استاندارد C99:
          https://i.imgur.com/NlP7fWK.png
          https://stackoverflow.com/a/40772217/1506761

  11. ابتدا باید توجه کرد که برای مقایسه معنادار باید بهینه‌سازی کامپایلر خاموش شود (سوییچ O0). volatile کردن متغیرها هم به این موضوع کمک کرده است.
    تفاوت دو روش مورد اشاره این است که 200 در واقع یک int هست. هنگامی که این مقدار به پارامتر حلقه cast میشود، از آنجایی که نوع پارامتر i از نوع char هست، مقداری که در واقع درون i وجود دارد اصطلاحاً implementation defined است.
    در معماری Arm Cortex M، در حالت char به جای اینکه مقدار ۲۰۰ که قرار بود در یک رجیستر ۳۲ بیتی ذخیره شود،مقدار منفی ۵۵ در یک بایت قرار گرفته است. این باعث می‌شود که پروسسور برای جابه جایی بایت ها، از دو instruction اضافی استفاده نماید. در نتیجه loop باید زمان بیشتری را برای صفر کردن این عدد صرف کند.
    در تصویر زیر، تفاوت disassembly این دو روش (char و int) نشان داده شده است: (خطوط .loc مربوط به دیباگ هستند و حذف شده اند)
    https://i.imgur.com/VcvpnwN.png
    این کد در STM32F103 تست شد (صدهزار مرتبه) :
    https://dpaste.com/GFDQ6YECJ
    تفاوت زمانی به شکل زیر است:

    tests: 100000, total char_version(): 3407 millisecs.
    tests: 100000, total int_version(): 2826 millisecs.

    1. Zeus . گفت:

      بله درسته در واقع نیازه که متغششر قبل از بارگیری کست بشه.

  12. مجید گفت:

    سلام، اول اینکه باید به بررسی معماری و عرض بیت هسته ها پرداخته بشه، عرض بیت هسته و رجیستر های CortexM دارای 32 بیت هست، این به این معنی هست که وقتی متغیری تعریف میکنیم مقدارش درون رجیستر های 32 بیتی پردازش میشه، حالا میریم سراغ متغیر های تعریف شده:

    1. متغیر اول از نوع int هست و به صورت پیشفرض 32 بیتی به حساب میاد و طولش به اندازه طول رجیستر ها است.
    2. متغیر دوم از نوع char است و طول آن برابر با 8 بیت است.

    تا اینجا متوجه میشیم که متغیر دوم کل بیت های رجیستر را استفاده نمیکند، پس پردازش روی این متغیر به چه صورت هست؟
    در Cortex-M وقتی پردازش روی متغیر هایی کوچکتر از سایز رجیستر هاش انجام میشه کامپایلر بیت های اضافی را با 0 پر میکند! (جهت تبدیل به سایز بزرگتر)

    بررسی کد:
    تا قبل از حلقه while میزان پردازش را میتوان برابر در نظر گرفت (با توجه به اینکه دستورات پردازنده برای هردو وجود دارد)، اما وقتی وارد حلقه میشویم روی متغیر پردازش انجام میدهیم، در کد اول با متغیر int نیازی به پر کردن بیت های دیگر نیست ولی در کد دوم بدلیل استفاده از char پس از بارگزاری داده در رجیستر و بعد از کاهش مقدار متغیر نیاز به پر کردن بیت های 31~8 با مقدار 0 داریم، در نتیجه دو دستور دیگر نیز به ساختار کد اضافه میشود.

    در نتیجه تابع loop_y سرعت بیشتری در اجرا دارد!

    1. Zeus . گفت:

      بله کاملا درسته 🙂

  13. مهرداد رستمی گفت:

    حلقه دوم صحیح است
    در arm ثبات ها و پشته و تمام ورودی های آن اکثر دستورهای پردازش داده arm ۳۲ بیتی هستند.به همین دلیل بایستی درصورت امکان از انواع داده ۳۲ بیتی مثل int برای متغیر های محلی استفاده کنیم (این تفکر اشتباه است چون char فضای کمتر اشغال میکند بهتر است از آن استفاده کنیم….)

    1. Zeus . گفت:

      بله درسته – در اوقع باید ببینیم معماری سخت افزار چی هست و بر اساس اون معماری انتخاب انجام بدیم.

  14. امیر گفت:

    سلام
    چرا جواب من پاک شده؟
    :-((((

  15. ابتدا فکر کردم شاید 32-bit بودن باعث شود که فرقی بین 2 کد نباشد. بنابراین گفتم از این مسئله مطمئن شوم و جواب را ارسال کنم. با استفاده از DMA به منظورتغییر وضعیت یه پین با هربار تکرار شدن حلقه، متوجه تفاوت ناچیز فرکانسی 2 حلقه شدم. حلقه ای که از int استفاده میکرد کمی سریعتر بود و تقریبا 3% فرکانس بالاتری داشت.
    برای بررسی بیشتر از تبدیل کد c به اسمبلی استفاده کردم و متوجه شدم کاهش یک واحد توی 4 بایت (DWORD) یک مرحله کوتاه تر از یه تک بایته.
    برای بررسی صحت این قضیه، کد زیر رو توی https://godbolt.org/ با هر کامپایلر c به اسمبلی موجود بررسی کنید.

    void loop_x()
    {
    volatile char i=200;
    while(i–)
    __NOP();
    }

    void loop_y()
    {
    volatile int i=200;
    while(i–)
    __NOP();
    }

    1. Zeus . گفت:

      بله درسته حلقه int سریعتر است.

  16. سلام با توجه به استفاده از تابع اصلاح کننده volatile سرعت مقدار دهی متغییر به دلیل نیاز نبودن به درخواست ها و حلقه ها بیشتر میشود هرچند که در اینجا بی تاثیر هست چون داخل یک حلقه فقط برنامه اجرا شده و دوم اینک در هر دو مورد سوال از این تابع استفاده شده
    مثلا بعدی متغیر char هست که ۸ بیت فضا دارد در حالی که متغیر int فقط ۲ بیت فضا دارد و مقدار دهی int به دلیل کم بودن فضا بیشتر است
    به نظر من مورد دوم سوال که با int انجام شده سرعت بیشتری دارد

    1. Zeus . گفت:

      بله حلقه ای که با int است سریعتر است
      و این به دلیل volatile بودن متغیر هاست که کامپایلر را مجبور میکند متغییرها رو داخل رم ذخیره کند.

  17. فرید ابراهیمی گفت:

    سرعت اجرای تابع loop_x کمتر از تابع loop_y است . ولی حلقه های تعریف شده در هر کدام با سرعتی یکسان اجرا میشوند .

    1. Zeus . گفت:

      من فکر میکنم سرعت اجرای حلقه ها هم متفاوت خواهد بود چون متغییرها درون رم تعریف میشوند

      1. فرید ابراهیمی گفت:

        در اینجا ما در هر فراخوانی تابع یک ارجاع به آدرس دیگر داریم . به آدرس حلقه . و این ربطی به بیرون حلقه نداره . int یا char بودن متغیر در حلقه تاثیر گذار نیست . چرا که خود char یک نوع int . باید توجه داشته باشیم که حلقه پس از فراخوانی تابع ، فراخوانی میشه .

        1. Zeus . گفت:

          دوست عزیز تاثیر گذار هست – اجازه بدید جواب تفصیلی رو منتشر کنیم – خواهید دید

  18. علی گفت:

    کدی که با int نوشته شده سریعتر خواهد بود
    چونکه کامپایلر نیازی به تبدیلش نداره تا عملیت ریاضی رو انجام بده
    حتی توی کامپیوتر هم همینه
    دلیل وجودی bool هم همینه

  19. مهدی گفت:

    درود
    باهم فرقی ندارن از لحاظ سرعت اجرا
    معماری آرم ورژن ۷ طبق مستنداتش برای خوندن و نوشتن مقادیر ۸ بیتی ۱۶ بیتی و ۳۲ بیتی میتونه از دستورالعمل های اتومیک که یک سیکل ماشین زمان میخوان استفاده کنه ،تعریف متغیر شما و مقدار دهی بهش یک سیکل زمان میگیره
    ولی احتمال میدم اگه متغیر رو ۳۲ بیتی تعریف میکردین
    حلقه وایل متفاوت تر ترجمه میشد و سرعت اجرا بیشتر میشد.
    کد رو تست نکردم دسترسی ندارم
    😄

    1. Zeus . گفت:

      در واقع مساله اینه که حلقه int سریعتر اجرا میشه
      بخاطر این که متغیرها توی رم هستند و هنگام لودشدن توی رجیسترهای cpu نیازه که به سی و دو بیت کست بشه و همین زمان بر خواهد بود.

  20. حسین حسینی گفت:

    من فکر میکنم برای میکروکنترلر stm32 فرقی نداره سرعتشون تو اجرای این حلقه ها.
    چون نوع int دو بایت هست میشه ۱۶ بیت ، و میکرو میتونه ۳۲ بیت رو تو یه کلاک پالس بنویسه ، حالا چه فرقی داره داده char یه بایتی یا ۸ بیتی باشه یا int دو بایتی یا ۱۶ بیتی.

    1. Zeus . گفت:

      خوب توی میکروکنترلرهای ۳۲ متغییر int به شکل ۳۲ بیتی تعریف میشه
      مساله دقیقا برمیگرده به لود کردن مقادیر از توی رم که برای int نیاز به کست کردن نداره ولی برای char نیازه این اتفاق بیفته.

  21. حسین گفت:

    با سلام .
    من فکر میکنم اون کدی که متغیر رو به صورت int تعریف میکنه سریعتر هست .
    stm32 ها 32 بیتی هستن .
    من در حالت دیبا گ کدهای اسمبلی رو در یک مورد نگاه میکردم .
    وقتی متغیر رو 8 بیتی تعریف میکردم برای دسترسی به اون دستورات اسمبلی بیشتری استفاده میشد .
    ولی برای دسترسی به متغیر 32 بیتی فرمانهای کمتری استفاده شده بود. فکر میکنم به خاطر اینکه باس دیتای cpu طولش 32 بیت هست این اتفاق میفته .البته کمپایلری هم که من استفاده کردم GCC بود.
    به نظر میرسه برای stm32 دسترسی 8 بیتی به صورت مستقیم امکانپذیر نباشه و علت این باشه.

    1. Zeus . گفت:

      بله درسته کاملا 🙂

  22. آرش گفت:

    با توجه به ۳۲ بیت بودن این سری میکرو ها، استفاده از متغییر int در تبدیل به دستورات اسمبلی به کدهای کمتری تبدیل می شود همچین به معماری حافظه این میکرو سازگارتر، بطوریکه مدیریت این کدها کمتر و سریع خواهد بود.
    لذا حالت دوم سریعتر هست.

    1. Zeus . گفت:

      بله کاملا درسته 🙂

  23. مجید باقری گفت:

    درکل ۳۲ بیت در stmما هست ک یک ریجستر کامل رو تشکیل بده که ب نامgeneral purposeهست و ثبات هایی ب نام R0,R1,..در ان ها وجود داره هر R ۵بیت هست در حالتchar ۲ تا از Rها درگیر میشن و در حالت کلی برای ریجستر *۳(۲×۵) که میشه ۳۰ بیت پس ۷تا باید R وجود داشته باشد
    چون تو حالت char که 8بیتیه باید 7 تا rداشته باشیم ولی تو int که ۱۶ بیتی هست و ۵*۳ و ۳تا ثبات rنیازه ولی در ۳۲ بیت ۶ تا ثبات r نیاز هست
    6تا rداریم پس در هر صورت intسرعت بیشتری داره

    1. Zeus . گفت:

      چطور به سه تا ثبات برای نگه داری یه عدد ۱۶ بیتی رسیدید ؟
      در ضمن متغییر int در خانواده های ۳۲ بیتی به شکل ۳۲ بیتی تعریف میشه

  24. مجید باقری گفت:

    چون تو حالت char که 8بیتیه باید 4 تا rداشته باشیم در قسمت ریجیستر (general purpose )
    ولی تو int که ۱۶بیتی هست
    3تا rداریم
    پس intسرعت بیشتری داره

    1. Zeus . گفت:

      ببینید این که حلقه int سریعتره درسته
      ولی توضیح شما رو متوجه نشدم :/

  25. امیر علی گفت:

    قطعا این کد اصلا اجرا نمیشه چون توی حلقه وایل هیچ شرطی نوشته نشده

    1. Zeus . گفت:

      😐
      چرا اجرا نشه دوست عزیز – همین که i مقداری غیر صفر داشته باشه حلقه صحیح است و تا وقتی که i مقداری غیر صفر داشته باشه اجرا میشه :/

  26. مهدی گفت:

    سلام . توی یه پست اموزشی داخل سایت در مورد بهینه سازی کد توی میکروهای avr گفته بودید که استفاده از متغیر هشت بیتی برای این پردازنده های هشت بیتی کارامد تر هستش نسبت به استفاده از متغییر 16 بیتی . پس اینجا هم چون میکروی مورد نظرمون 32 بیتی هست ، اسفاده از int کار امد تر هست. انشاا…

    1. Zeus . گفت:

      بله منطق و جواب درستی است 🙂

  27. فرزین گفت:

    از مقایسه ی اینستراکشن های دوتابع بصورت disassemble شده، میتوان دید که در حلقه ی while موجود در تابع loop_x برای لود کردن متغیر i از حافظه، کم کردن یک واحد از آن و ذخیره ی متغیر درحافظه، علاوه بر دستورات ldrb، strb، cmp که متناظر این دستورات در حلقه ی while موجود در تابع loop_y نیز مشاهده میشود، شاهد دو دستور and هستیم. از انجایی که این حلقه 201 بار اجرا میشود، در تابع loop_x هزینه ی اجرای 402 (201 * 2) دستور and وجود دارد که این هزینه در تابع loop_y وجود ندارد. در نتیجه میتوان گفت که تابع loop_y سریع تر از تابع loop_x اجرا خواهد شد.
    لینک اینستراکشن توابع:
    https://paste.ubuntu.com/p/2JxszDMgxn

    1. فرزین گفت:

      برای کامپایل و disassemble کردن این توابع بصورت زیر عمل کردم:
      اول برنامه ای نوشتم که در اون فقط دو تابع رو پیاده سازی کردم و تابع main رو صدا زدم(نیازی به فراخوانی توابع در تابع miain نبود). سپس با کامند زیر برنامه رو کامپایل کردم.

      ( arm-none-eabi-gcc –specs=nosys.specs loop.c -o loop.out )

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

      (arm-none-eabi-objdump –no-show-raw-insn –visualize-jumps –disassemble=loop_x ./loop.out > loopx.objdump )

      (arm-none-eabi-objdump –no-show-raw-insn –visualize-jumps –disassemble=loop_y ./loop.out > loopy.objdump )

    2. Zeus . گفت:

      بله درست است 🙂

  28. پوریا گفت:

    این نیاز به تست روی معماری همون میکرو داره و همچنین وابسته به کامپایلر هستش ولی با فرض اینکه با -O0 کامپایل میکنید zero extension میتونه overhead داشته باشه و لوپ با uint_8 کندتر باشه

  29. amirhos_esm گفت:

    حلقه دوم سریع تر است
    شاید به این فکر کرده باشید که اگر این کد را در یک سیستم 8 بیتی مانند avr تست کنیم ، کدام حلقه سریع تر است؟ بله حلقه 1 قطعا در این سیستم سریع تر است چون یک تفریق 32 بیتی در یک سیستم 8 بیتی به چند کلاک متوالی نیاز دارد و این کار باعث کند شدن سیستم می شود
    اما در یک سیستم 32 بیتی چی طور ؟ شاید بگید چون سیستم 32 بیتی هست ، پس یک تفریق 32 بیتی را می تونه تو یک کلاک انجام بده . بدین ترتیب پس یک عملیات 8 بیتی رو هم باید در یک کلاک انجام بده . بله جواب درسته . پس چرا دو حلقه بالا باهم متفاوتند ؟
    جواب به قبل تفریق بر می گرده وقتی می خواد پردازنده دیتا رو از رم به ریجستر ها خودش منتقل کنه. می دونید که در stm32 رجیستر های cpu طولشون 32 بیت هستش . پس وقتی یک داده 32 بیتی رو از رم به یکی از رجیستر های cpu منتقل می کنیم در واقع 32 بیت داده رم ، رو 32 بیت داده رجیستر ریخته میشوند. اما اگه دیتا 8 بیتی داشتیم چی ؟ خوب اون دیتا می یاد رو 8 بیت اول ریجستر cpu ریخته میشه . اما تکلیف 24 بیت بعدی چی میشه ؟ آیا تضمینی وجود داره که اونا صفر باشن ( مشکل ایجاد نکنن ) جواب خیر هستش در واقع کد باید کاری کنه که وقتی یک دیتا 8 بیتی از رم به cpu منتقل میشه یک فکری به حال 24 بیت بعدی بکنه اگر دیتا بی علامت باشد باید عملیات zero extention انجام بده یعنی 24 بیته بعدی رو صفر بکنه اما اگر عدد علامت دار باشه باید signed extention بکنه یعنی مثلا عدد 11110001 که برابر -15 هستش به 32 بیت گسترش بده که مقدار در 32 بیت همون -15 بشه ( 0xfffffff1) . این عملیات ها هستن که باعث میشه پردازش یک داده 8 بیتی در سیستم 32 بیتی با سرعت کمتری به نسبت داده 32 بیتی انجام بشه

    در ادامه با تست عملی مشخص شد که حلقه دوم در cortex m3 و با فعال بودن بهینه سازی رو سایز ، حدود 2017 کلاک و حلقه اول با حدود 402 کلاک بیشتر اجرا می شوند.
    چون حلقه 201 بار اجرا می شود و اختلاف 402 تایی کلاک دو حلقه = دو دستور extetion برای گسترش دادن دیتای 8بیتی توسط کامپایلر ایجاد شده است

    خروجی اسمبلی کامپایلر gcc را برای cortex m3
    https://paste.ubuntu.com/p/zX9hfxQHNG/

    1. Zeus . گفت:

      بله درسته – یکی از کامل ترین جواب هایی که دیدم 🙂

  30. Ramin گفت:

    با عرض سلام خدمت گروه بسیار فعال و ارزشمند سیسوگ.
    من این دو حلقه رو در دیباگر و محیط دیس اسمبلر مشاهده کردم و طبق مطالعه ای که روی دستورات instruction set Cortex انجام دادم
    به این نتیجه رسیدم که loop_y که متغیر آن از نوع int تعریف شده دارای سرعت بیشتریه.
    چون کامپایلر برای تفسیر کدی که از متغیر char استفاده شده میاد از دستورات اسمبلی مربوط به byte استفاده میکنه مثل دسترور STRB و UXTB
    و … تا بتونه با مقادیر به صورت byte کار بکنه در صورتی که تابعی که با متغیر از نوع int تعریف شده چون سایز متغیر 32 بیتیه و چون آدرس دهی در پردازنده ی ما هم 32 بیتی هست خیلی راحت و با دستورات کمتر کامپایلر میتونه کد رو به اسمبلی ترجمه کنه (همچنین حجم کد اسمبلی while در تابعی که از متغیر int استفاده شده بود کمتر از تابعی بود که از متغیر char استفاده شده )
    با تشکر.

    1. Zeus . گفت:

      بله کاملا درسته 🙂

  31. qwerty13 گفت:

    تابع اولی سریعتر باید باشه چون Char یک بایتیه ولی int دو بایتی…

    1. Zeus . گفت:

      البته توی میکروی ۳۲ بیتی اینت هم ۳۲ بیت خواهد بود
      و چون رجیسترها و ثبات ها ۳۲ بیتی هستند در واقع حلقه int سریعتر اجرا میشه

  32. سلام
    من فکر میکنم حلقه ی دوم جواب درست باشه
    چون که متغیر از نوع int برای این منظور مناسب تره
    چون متغیر از نوع char حجم بیشتری از رم رو اشغال میکنه در صورتی که نیازی نیست و متغیر از نوع int برای این حلقه کفایت میکنه

    1. Zeus . گفت:

      این که حلقه دوم سریعتر اجرا میشه درسته ولی متغییر char رم کمتری میگره ها !

  33. Amir گفت:

    سلام
    با توجه به اینکه هسته از معماری ۳۲ بیت استفاده میکنه و متغیر ها ۳۲ بیتی آدرس دهی میشن، قاعدتا برای تعریف نوع int یا char چهار بایت در نظر گرفته میشه و با توجه به اینکه برای اجرای دستور x– یا y– یه کلاک صرف خواهد شد، سرعت اجرای هر دو تابع برابر خواهد بود.
    ارادتمند.

    1. Zeus . گفت:

      سلام دوست عزیز
      نکته که ای که باید بهش توجه میکردید این هست که متغییرها قرار از توی رم لود بشن و ذخیره بشن
      همین نکته کوچک معادلات رو بهم میریزه 🙂

  34. متغیر char هشت بیت و متغیر int شانزده بیت است پس فضای کمتری اشغال میکنه و نصف اونه، اما اگر از میکرو ۳۲ بیتی استفاده کنید فرقی نداره چون در هر کلاک میتونه تا ۳۲ بیت رو پردازش یا منتقل کنه اما اگر از میکرو ۸ بیتی استفاده می‌کردید تفاوت در سرعت وجود داشت و استفاده از char سریعتر بود. زیرا برای خواندن int دوبار مراجعه به ram نیاز بود.

    1. Zeus . گفت:

      در میکروکنترلرهای ۳۲ بیتی متغییر اینت نیز ۳۲ بیت خواهند بود
      درسته که عملیات ریاضی مورد نظر بر روی هر کدام از این متغییرها یک سیکل نیاز داره ولی بارگذاری اونها توی رجیستر پردازنده متفاوت خواهد بود.

  35. دانیال حیدری مقدم گفت:

    سلام
    جواب سوال حلقه x هست، به دلیل استفاده از char که یک بایتی هستش.

    1. Zeus . گفت:

      اما حلقه y سریعتر اجرا میشه 🙂
      دقیقا به خاطر معماری ۳۲ بیتی پردازنده و ۳۲ بیتی بودن متغییر ذخیره شده !

  36. Omid Azadeh Omid Azadeh گفت:

    من فکر کنم میکرو حلقه ای رو که متغیرش intهستش رو سریعتر تموم میکنه … بخاطر اینکه نیازی نیست متغیر رو cast کنه …بخاطر ۳۲ بیتی بودن میکرو … چون توابع رو هم اگر ورودی و خروجی تابع int باشه سریعتر اجرا میکنه ( فکر کنم از نوشته های خود شما بود ، مطمئن نیستم ) … امید وارم اشتبا نکرده باشم … ممنون … به امید دیدار ….

  37. مریم بایرام دوست گفت:

    حلقه دوم سریع تر است

    1. Zeus . گفت:

      بله درسته ای کاش دلایل خودتون رو ارائه می کردید.

  38. مریم بایرام دوست گفت:

    حلقه دوم

  39. حسین گفت:

    سلام
    من دقیقا از معماری و دستورات اسمبلی STM32 خبر ندارم ولی به صورت کلی به نظرم کد دوم (loop_y) که متغیر از نوع int داره سریع تر هستش. دلیلش هم اینه که STM32 همونطور که از اسمش مشخصه باس 32 بیتی داره و نوع int هم 32 بیت پهنا داره. پس با کمینه کلاک میتونه به متغیر int دسترسی داشته باشه و روی اون عملیات انجام بده. اما در عوض نوع char پهنای 8 بیتی داره و لازمه علاوه بر این که به این متغیر دسترسی پیدا میکنه، محتویات حافظه مجاور رو هم تغییر نده که این خودش احتیاج به پردازش و کلاک بیشتر داره.

    1. Zeus . گفت:

      بله درسته از منطق خوبی استفاده کردید 🙂

  40. Omid گفت:

    من با میکروکنترلر و کامپایلرش آشنایی ندارم ولی یک حالت اینه که char یک بایت هست پس عملیات کم‌کردنش باید سبک‌تر باشه و سریعتر. حالت دیگه که احتمال بیشتری میدم، اپتیمایز کردن کامپایلر هست که با دیدن nop یا در حقیقت ندیدن هیچ عملیات داخل حلقه، کل اون را با ست کردن i به 0 جایگزین می‌کنه و در عمل دو تا حلقه یک پرفورمنس خواهند داشت.

    1. Zeus . گفت:

      از اونجایی که عملیات باید به شکلی باشه که روی خونه های دیگر حافظه تغییری ایجاد نکنه توی نوع char پس باید کار بیشتری رو انجام بده منطقا و حلقه char کندتر خواهد بود.

  41. نیما گفت:

    با توجه به اینکه معماری 32 بیتی هست loop_y باید سریع تر باشه . اگر کامپایلر int رو 32 بیتی بگیره و معماری هم 32 بیتی باشه سریعترین ادرسدهی هست

    1. Zeus . گفت:

      بله منطق شما درسته

  42. علی گفت:

    با int سریع تر است چون میکرو stm32 پردازنده ۳۲ بیتی دارد و محاسبات در همان نوع سریع تر از ۸ بیتی است

    1. Zeus . گفت:

      جوابتون درسته ولی نه به این دلیل که محاسبات نوع ۳۲ بیتی سریعتره به دلیل که دسترسی به حافظه ۳۲ بیتی سریعتره