Uploaded avatar of iHiD

نوامبرِ نیم‌بایتی!

@iHiD
حدود 3 سال پیش
ویدیو

مقدمه

سلام به همه. به نوامبر خوش آمدید. امیدوارم حالتان خوب باشد.

اکتبر بسیار پرمشغله‌ای داشتیم. همین حالا یک بهبود بزرگ در راه‌حل‌های جامعه را منتشر کردیم. الان تکراری‌زاییشان می‌کنیم تا راه‌حل‌های مشابه فقط یک بار نشان داده شوند، گزینه‌های تازه‌ی مرتب‌سازی و امکان جست‌وجو بر اساس کد را هم اضافه کرده‌ایم، و اگر C# را ببینید، متوجه می‌شوید که امکان فیلتر کردن بر اساس مفاهیم مختلف برنامه‌نویسی را هم اضافه کرده‌ایم. پس می‌توانید به دنبال راه‌حل‌هایی بگردید که از جابه‌جایی بیت، بازگشت یا هر روش دیگری که دوست دارید استفاده کرده‌اند. این قابلیت را در هفته‌ی جامعه به مسیرهای دیگر هم می‌آوریم.

اما فعلاً روی #12in23 تمرکز کنیم. اکتبر ماه جالبی بود و زبان شیءگرا را بررسی کردیم، اما این ماه قرار است سخت و جدی باشیم. روی زبان‌های اسمبلی تمرکز می‌کنیم؛ به‌طور مشخص MIPS Assembly، x86-64 Assembly و WebAssembly. طبق معمول، Erik برایمان توضیح می‌دهد که چه چیزی این زبان‌ها را جالب و یگانه می‌کند.

نشان‌ها

مثل همیشه، با کامل کردن هر ۵ تمرین در این زبان‌ها نشان نوامبر نیبلی را به دست می‌آورید. نشان یک‌ساله را هم داریم که می‌دانم بسیاری از شما برای گرفتنش تلاش می‌کنید. برای آن ۵ تمرین ویژه داریم که باید کامل کنید. این‌ها عبارت‌اند از:

  • شمارش بیت‌ها: بیت‌های ۱ را در یک عدد بشمارید
  • دانه‌ها: تعداد دانه‌های روی صفحه‌ی شطرنجی را حساب کنید که هر خانه‌اش دو برابر خانه‌ی قبلی است
  • رنگ مقاومت: رنگ نوار مقاومت را به نمایش عددی‌اش تبدیل کنید
  • رمز چرخشی: رمز چرخشی (معروف به رمز سزار) را پیاده‌سازی کنید
  • شمارش نوکلئوتید: حساب کنید هر نوکلئوتید چند بار در یک رشته‌ی DNA ظاهر می‌شود

پیشینه

چرا اسمش را نوامبر نیبل گذاشتیم؟

خب، همان‌طور که احتمالاً می‌دانید، یک بایت ۸ بیت است. و یک «نیبل» ۴ بیت است. اسمش وقتی معنا پیدا می‌کند که y را در واژه‌ی «byte» به i تغییر دهید و به «bite» برسید. آن‌وقت «nibble» یعنی یک گاز کوچک.

زبان اسمبلی چیست؟

خب، بیایید با چند نکته‌ی پایه شروع کنیم. یک CPU دستورها را اجرا می‌کند، مثل «دو عدد را جمع کن» یا «بیت‌ها را به چپ ببر» و غیره. به این دستورها، دستورهای کد ماشین می‌گویند و چیزی بیشتر از دنباله‌های مشخصی از بیت‌ها نیستند. اجرای یک برنامه، به‌قول‌معروف، همان پردازش و اجرای این دنباله‌های بیتی توسط CPU است.

از آنجا که نوشتن مستقیم دستورها به شکل دنباله‌های بیتی دشوار و مستعد خطاست، Kathleen و Andrew Donald Booth در سال ۱۹۴۷ زبان انسان‌پسندتری برای نمایش دستورهای کد ماشین ابداع کردند. به چنین زبانی که دستورهای کد ماشین را نمایش می‌دهد، زبان اسمبلی می‌گویند. این زبان اسمبلی سپس به کمک یک «اسمبلر» به دستورهای کد ماشین تبدیل می‌شود.

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

راستی، اگر کارت‌های پانچ را به یاد دارید، همان تکه‌های بزرگ مقوایی که در نخستین رایانه‌ها برای اجرای برنامه‌ها به کار می‌رفتند، آن‌ها هم یک زبان اسمبلی بودند!

زبان اسمبلی چه تفاوتی با زبان‌هایی دارد که امروز با آن‌ها برنامه می‌نویسیم؟

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

یک واقعیت جالب: ۹۹٪ از کد منبع RollerCoaster Tycoon با دست و به زبان اسمبلی نوشته شده بود! دستاورد حیرت‌انگیزی است و وقتی خودتان کمی اسمبلی بنویسید، تازه قدرش را بیشتر می‌دانید.

آیا هنوز با کد اسمبلی می‌نویسند؟

خب، کمتر از قبل. قبلاً این‌طور بود که اسمبلی دست‌نویس اغلب از کد ماشینی که کامپایلر تولید می‌کرد بهتر عمل می‌کرد (و همین یکی از دلایلی است که زبان C++ اجازه می‌دهد کد اسمبلی را مستقیم درون کد جا بدهید)، اما کامپایلرها حالا آن‌قدر در تولید کد ماشین خوب شده‌اند که این موضوع به‌ندرت درست است. با این حال، همچنان زبان اسمبلی را در محیط‌هایی می‌بینید که عملکرد حیاتی است یا منابع محدودند.

مرور کلی

MIPS

  • MIPS (Microprocessor without Interlocked Pipelined Stages) خانواده‌ای از معماری‌های مجموعه‌دستورِ رایانه‌ی مجموعه‌دستور کاهش‌یافته (RISC) است
  • توسط MIPS Computer Systems ساخته شد و نخستین بار در سال ۱۹۸۵ منتشر شد
  • نسخه‌های متعدد: MIPS I، II، III، IV، V و MIPS32/64. دو نسخه‌ی اول فقط ۳۲ بیتی بودند، اما MIPS III پشتیبانی از ۶۴ بیت را معرفی کرد.
  • چندین افزونه‌ی اختیاری، مثل دستورهای SIMD و فشرده‌سازی
  • بر معماری‌های RISC بعدی بسیار تأثیرگذار بود
  • MIPS در سال ۲۰۲۱ اعلام کرد که معماری MIPS دیگر توسعه نمی‌یابد و به RISC-V مهاجرت کرد (که معماری‌ای متن‌باز و بدون حق امتیاز است)
  • بیشتر در سیستم‌های نهفته (مثل روترها) و سرورها (رایانه‌های Silicon Graphics از آن استفاده می‌کردند و مشهور است که برای جلوه‌های ویژه‌ی فیلم‌ها به کار می‌رفتند)، ابررایانه‌ی NEC Cenju-4، خودروی Tesla Model S، کاوشگر New Horizons ناسا و همچنین برای آموزش اسمبلی در دانشگاه‌ها و در چند کنسول بازی (مثل PlayStation اصلی، Playstation Portable و Nintendo 64) به کار می‌رود

x86-64 Assembly

  • طراحی AMD و در سال ۱۹۹۹ با نام معماری AMD64 منتشر شد
  • نسخه‌ی ۶۴ بیتی مجموعه‌دستور x86 است که ریشه‌اش به سال ۱۹۷۸ برمی‌گردد، زمانی که Intel ریزپردازنده‌ی 8086 خود را روانه‌ی بازار کرد. آن پردازنده ۱۶ بیتی بود، اما بعدها 80386 دستورهای ۳۲ بیتی را اضافه کرد و آن مجموعه‌دستور با x86 مترادف شد.
  • مهم‌ترین چیزی که ۶۴ بیت ممکن کرد، آدرس‌دهی به حافظه‌ی بیشتر بود (آدرس‌دهی ۳۲ بیتی به ۴ گیگابایت محدود است) که به گلوگاه تبدیل شده بود. ۶۴ بیت به‌لحاظ نظری می‌تواند ۱۶ اگزابایت را آدرس‌دهی کند، اما در حال حاضر فقط ۴۸ بیت به کار می‌رود که امکان آدرس‌دهی ۲۵۶ ترابایت را می‌دهد (بعداً در صورت نیاز می‌توان آن را گسترش داد)
  • AMD64 مجموعه‌دستور x86 را گسترش می‌دهد و طوری طراحی شد که با حالت سازگاری، کاملاً با برنامه‌های موجود ۱۶ و ۳۲ بیتی سازگار باشد
    • Intel بدون دخالت AMD معماری IA-64 را طراحی کرده بود. مجموعه‌دستور ۶۴ بیتی تازه، بسیار متفاوت و ناسازگار با گذشته بود. در نهایت AMD64 پیروز شد و Intel نسخه‌ی خودش را از آن پیاده‌سازی کرد (تنها با تفاوت‌های معنایی جزئی)
  • همه‌جا استفاده می‌شود. از ایستگاه‌های کاری تا سرورها (از جمله ابررایانه‌ها)، از سیستم‌های نهفته تا کنسول‌های بازی (مثل PS5 و Xbox Series X).

WebAssembly

  • توسط W3C طراحی شد، نهادی استانداردگذار برای فناوری‌های وب
  • اهداف طراحی‌اش عبارت‌اند از:
    • سریع، ایمن و قابل حمل
    • نمایشی کارآمد و قابل حمل
  • در آن روزها، اجرای سریع روی وب معمولاً با افزونه‌های تخصصی مرورگر مثل Flash و Silverlight ممکن می‌شد، چون خود JavaScript چندان برای محاسبات پربازده مناسب نیست. بزرگ‌ترین عیب این افزونه‌ها این بود که معمولاً مشکلات امنیتی زیادی داشتند و استاندارد نبودند.
    • Mozilla زبان asm.js را طراحی کرد، زیرمجموعه‌ای از JavaScript که هدفش اجرای کد در مرورگر با کارایی بالا بود و این را از طریق یک‌دستی نوع‌ها (بدون تغییر پویای نوع) و بدون جمع‌آوری زباله به دست آورد. زبان‌ها می‌توانستند به asm.js کامپایل شوند و همچنان هنگام اجرا روی وب کارایی خوبی بگیرند. اما هنوز JavaScript بود، پس دستاوردهایش محدود می‌ماند. از همین رو، پیشنهاد زبان تازه‌ای مطرح شد: WASM.
  • زبانی شبیه اسمبلی، از این جهت که مجموعه‌ای از دستورها را برای اجرا فراهم می‌کند. نکته‌ی مهم این است که به CPU خاصی گره نخورده، پس از پلتفرم مستقل است و برای هر پلتفرم به یک پیاده‌سازی (یک ماشین مجازی) نیاز دارد. این یعنی WebAssembly در واقع «بایت‌کد» است، نه کد ماشین
  • دارای نوع ایستا (تفاوت مهم با JS)
  • معمولاً از کامپایل پیش از اجرا یا در لحظه استفاده می‌کند (اما می‌تواند تفسیر هم شود)
  • استانداردی باز است و دو چیز را تعریف می‌کند:
    • یک قالب دودویی
    • یک قالب متنی (که به قالب دودویی کامپایل می‌شود)
  • پیاده‌سازی در همه‌ی مرورگرهای اصلی
  • در بسیاری از صفحات وب که کارایی بالایی می‌خواهند به کار می‌رود، مثل Google Earth، Figma، Unity و Autocad. در سمت سرور هم دارد جا باز می‌کند؛ مثلاً برای اجرای ریزسرویس‌ها، روی پلتفرم‌های SaaS (مثل CloudFlare workers) یا در Docker

و از دید برنامه‌نویسی، چه تفاوتی با هم دارند؟

MIPS

  • از معماری بارگذاری-ذخیره (معروف به ثبات-ثبات) استفاده می‌کند، که در آن هر دستور یا به حافظه دسترسی دارد یا محاسباتی است، اما همیشه روی داده‌های داخل ثبات‌ها کار می‌کند

x86-64 Assembly

  • از معماری ثبات-حافظه استفاده می‌کند که اجازه می‌دهد عملیات هم روی حافظه (یا از حافظه) و هم روی ثبات‌ها انجام شود

WebAssembly

  • از برنامه‌نویسی مبتنی بر پشته استفاده می‌کند (بدون ثبات) و امکان خواندن داده از حافظه و نوشتن در آن را دارد

چه ویژگی‌هایی این زبان‌ها را عالی می‌کند؟

MIPS

  • کوچک است. مجموعه‌دستور MIPS می‌تواند همه‌ی دستورهایش را در یک صفحه جا بدهد
  • قواعد فراخوانی تثبیت‌شده کمک می‌کند بفهمید چطور از ثبات‌های موجود استفاده کنید؛ مثلاً کدام برای فرستادن آرگومان‌ها و کدام برای بازگرداندن نتیجه به کار می‌رود.
  • پایدار است. آخرین نسخه‌اش در سال ۲۰۱۴ منتشر شد
  • مستندات گسترده، به‌ویژه در متون دانشگاهی
  • کاربرد فراوان در دنیای واقعی. میلیاردها دستگاه

x86-64 Assembly

  • هرچند گسترشی از x64 است، ویژگی‌های تازه‌ی بسیاری به آن اضافه شد، از جمله:
    • پشتیبانی از عدد صحیح ۶۴ بیتی
    • ثبات‌های بیشتر
    • دستورهای SSE (دستورهای برداری)
    • دسترسی نسبی به داده (کارآمدتر هنگام استفاده از کتابخانه‌های اشتراکی)
    • بیت عدم اجرا (ویژگی امنیتی برای جلوگیری از اجرای کد در صفحات خاصی از حافظه)
  • آشنا. چون مجموعه‌دستور x86 را گسترش می‌دهد، برای کسانی که با آن آشنا هستند یادگیری‌اش نسبتاً آسان است مستندات گسترده و دقیق
  • پایدار. هرچند نسخه‌های تازه مرتباً اضافه می‌شوند، هسته‌ی آن بسیار پایدار و سازگار با گذشته می‌ماند

WebAssembly

  • ماشین مجازی WebAssembly که مبتنی بر پشته است، در مقایسه با زبان‌های اسمبلی پردازنده‌های فیزیکی (از جمله RISC) سبک و ساده است. همین کامپایل کردن برایش را نسبتاً آسان می‌کند.
  • قالب متنی WebAssembly از «شکر نحوی» S-Expression استفاده می‌کند تا به سبک دستوری آشنایی برسد که سپس به کد مبتنی بر پشته تبدیل می‌شود. به شکلی که S-Expression دارد «شکل شکری» می‌گویند و با «شکرزدایی» به شکل دیگری تبدیل می‌شود که معادل محتوای درون فایل دودویی است. S-expression برای هر کسی که با LISP کار کرده باشد آشناست
  • همکاری قوی با JavaScript. انتقال داده به JavaScript و از آن ساده است. یک هشدار مهم: WASM (هنوز) اجازه‌ی تعامل با DOM را نمی‌دهد
  • مدام بهتر می‌شود. نه فقط ماشین‌های مجازی WASM بهبود می‌یابند، بلکه خود استاندارد هم فعالانه در حال توسعه است. ویژگی‌های تازه‌ی بی‌شماری در حال طراحی و کار هستند، مثل دستورهای مرتبط با SIMD، جمع‌آوری زباله، ریسمان‌ها، بهینه‌سازی فراخوانی دنباله‌دار و بیشتر
  • ایمن. کد اعتبارسنجی می‌شود و در محیطی جعبه‌شنی اجرا می‌گردد که درجه‌ی بالاتری از اعتبارسنجی ایستا را در مقایسه با JavaScript یا زبان‌های اسمبلی بومی فراهم می‌کند. معناشناسی‌اش به‌خوبی تعریف شده و بررسی و استدلال درباره‌ی آن آسان است

ویژگی‌های برجسته

MIPS

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

x86-64 Assembly

  • قدرتمند. x86-64 در طول دهه‌ها دستکاری شده و دستورهای بی‌شماری دارد که به کارایی کمک می‌کنند. یک نمونه‌اش SIMD (Single Instruction, Multiple Data) است؛ دستورهایی که، خب، می‌توانند یک دستور را به‌طور موازی روی چند داده اجرا کنند.
  • همه‌جا حاضر. دستگاه‌هایی که x86-64 اجرا می‌کنند همه‌جا هستند. CPUهای Intel و AMD آن را پیاده‌سازی می‌کنند. مدت‌هاست استاندارد عملی است
  • مرتباً به‌روزرسانی می‌شود. مثلاً دستورهای برداری تازه، با SSE3-5، AVX، AVX-512 و بیشتر

WebAssembly

  • کارآمد. قالب دودویی‌اش فشرده است و می‌توان آن را در یک گذر سریع رمزگشایی، اعتبارسنجی و کامپایل کرد. همچنین جریانی است، یعنی رمزگشایی، اعتبارسنجی و کامپایل می‌توانند پیش از رسیدن همه‌ی داده شروع شوند. و موازی‌سازی هم می‌شود. همین آن را برای برنامه‌های وب پربازده بی‌نقص می‌کند.
  • هدف کامپایل عالی. اجازه می‌دهد کد بسیاری از زبان‌ها روی وب اجرا شود. بیشتر زبان‌های اصلی از کامپایل به فایل‌های دودویی WebAssembly پشتیبانی می‌کنند، یعنی کدتان می‌تواند بدون نوشتن JavaScript روی وب اجرا شود. بعضی زبان‌ها کد شما را به WebAssembly کامپایل نمی‌کنند، بلکه زمان اجرا را به WebAssembly کامپایل می‌کنند که آن‌وقت می‌تواند بایت‌کد بدون تغییر را اجرا کند.
  • استقرار و اجرای آسان. تنها چیزی که لازم دارید یک ماشین مجازی است که بتواند بایت‌کد را تفسیر کند، و همه‌ی مرورگرهای اصلی یکی دارند.
  • به وب گره نخورده و می‌تواند در سمت سرور هم اجرا شود. WebAssembly System Interface (WASI) رابطی (ABI و API) است که طوری طراحی شده تا روی هر پلتفرمی قابل حمل باشد. شبیه POSIX (رابط‌های استاندارد برای سیستم‌های یونیکسی) است و قابلیت‌هایی مثل I/O را فراهم می‌کند. امنیت بخش کلیدی طراحی‌اش است که شامل جعبه‌شنی بودن و قابلیت‌محور بودن می‌شود (باید برای مواردی مثل فایل‌ها یا سوکت‌ها صریحاً مجوز بگیرید). WASI همچنین این توان را دارد که ارتباط بین زبان‌ها را آسان کند. Solomon Hykes، یکی از بنیان‌گذاران Docker، در سال ۲۰۱۹ نوشت: «اگر WASM+WASI در سال ۲۰۰۸ وجود داشت، نیازی به ساختن Docker نداشتیم»

کدام را انتخاب کنیم

  • اگر هرگز با زبان اسمبلی کار نکرده‌اید، WebAssembly احتمالاً ساده‌ترین زبان برای شروع است. با این حال، اگر می‌خواهید زبان اسمبلی‌ای یاد بگیرید که به کد ماشین کامپایل می‌شود، MIPS Assembly را امتحان کنید
  • اگر روی ماشین x86-64 کار می‌کنید (که خیلی محتمل است)، x86-64 Assembly را امتحان کنید
  • اگر با LISP آشنا هستید، استفاده‌ی WebAssembly از S-expression را قدر می‌دانید
  • اگر با برنامه‌های وب کار می‌کنید، WebAssembly منطقی‌ترین گزینه است
  • اگر کارایی برایتان مهم است، x86-64 و MIPS گزینه‌های عالی‌اند. و اگر کارایی وب مهم است، WebAssembly را امتحان کنید
1 نوامبر 2023 · برایتان مفید بود؟