خانه / fd / وب اسمبلی (WebAssembly) — به زبان ساده

وب اسمبلی (WebAssembly) — به زبان ساده

در این مقاله به بررسی وب اسمبلی خواهیم پرداخت. مفهوم آن را معرفی می‌کنیم و می‌گوییم که قرار است چه مشکلاتی را حل کند. وب‌اسمبلی که به اختصار WASM نامیده می‌شود، یک قالب دستورهای باینری است که برای ماشین مجازی مبتنی بر پشته استفاده می‌شود. وب‌اسمبلی به عنوان یک هدف پرتابل برای کامپایل زبان‌های سطح بالایی مانند C/C++/Rust طراحی شده است و به این ترتیب امکان توسعه اپلیکیشن‌های کلاینت و سرور را روی وب ممکن می‌کند.

ما در این راهنما برای این که درک بهتری از این فناوری داشته باشیم، یک الگوریتم نوشتیم که امکان مقایسه عملکرد WASM را در برابر جاوا اسکریپت محض (Vanilla JS) فراهم می‌سازد.

بازی زندگی (Life Game)

برای اثبات مفاهیم ارائه شده از بازی زندگی طراحی شده از سوی Conway به عنوان مسئله استفاده کرده‌ایم. این بازی بدون بازیکن است و قواعد ساده‌ای دارد:

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

بنابراین طرح ما این است که یک ماتریس بزرگ داشته باشیم و آن را با مقادیر تصادفی (۰ یا ۱) پر کنیم و این حالت اولیه را ارسال کرده و نتیجه را رندر کنیم، سپس حالت بعدی را محاسبه کرده و آن را مجدداً رندر کنیم و این مرحله اخیر را چندین بار تکرار کنیم.

ما می‌خواهیم این راه‌حل را با سه راهبرد پیاده‌سازی کنیم: جاوا اسکریپت خالص، وب‌اسمبلی، و وب ورکرها. پیچیدگی زمانی الگوریتم ما روی همه رویکردها برابر با (O(m*m است که n عرض دنیا و m ارتفاع آن است. از آنجا که رندر برای هر سه رویکرد یکسان است، آن را در اندازه‌گیری‌های خود لحاظ نمی‌کنیم.

جاوا اسکریپت محض

معماری زیرساختی برای این رویکرد شامل ایجاد یک بازی جدید و سپس ایجاد و ارسال حالت نخست (ماتریسی پر شده از ۰ و ۱) به آن است. کامپوننت game این حالت را نگه‌داری می‌کند و تابعی به نام next بازمی‌گرداند که حالت بعدی را هنگام فراخوانی بازگشت می‌دهد. در این صورت تابع ()getNextState را از فایل environment.js فراخوانی می‌کنیم که پیاده‌سازی جاوا اسکریپت خالص است.

...  const next = game(   document.getElementById('game'),   COLUMNS,   LINES,   createGameMatrix(LINES, COLUMNS), // generates the initial state   strategy(     STRATEGY,     COLUMNS,     LINES,     initialConfig   ) // Defines which strategy to use to calculate the next state );  ...  function loop() {   next().then(() => {     requestAnimationFrame(loop);   }); };  loop();

داخل کامپوننت environment.js همچنان مسئله را به تابع‌های تخصصی کوچک‌تر افراز می‌کنیم. بدین ترتیب به روشی آسان‌تر می‌توانیم بهینه‌سازی کامپایلر JIT را تحریک کنیم. این بهینه‌سازی‌ها را در مقاله بعدی بررسی خواهیم کرد. این تابع‌ها به محاسبه حالت کنونی همسایه‌های فوقانی، تحتانی و کناری پرداخته و همه حالت‌های گوشه‌ای را پوشش می‌دهند.

میانگین سرعت این محاسبه حالت از ۹ تا ۴ میلی‌ثانیه برای یک ماتریس ۸۰۰×۴۵۰ متفاوت است. برای مشاهده تصویر در ابعاد اصلی روی این لینک کلیک کنید.

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

اندکی از تاریخچه جاوا اسکریپت

جاوا اسکریپت در سال ۱۹۹۵ از سوی «برندن آیک» (Brendan Eich) طراحی شد و هدف وی ارائه زبانی بود که طراحان به کمک آن بتوانند اینترفیس‌های دینامیک را با آن به سادگی پیاده‌سازی کنند. به بیان دیگر جاوا اسکریپت برای این ساخته نشده که سریع باشد؛ بلکه هدف اولیه این بود که لایه رفتاری را به صفحه‌های HTML به روشی راحت و سرراست اضافه کند.

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

جاوا اسکریپت در ابتدا یک زبان تفسیری بود. بدین ترتیب فاز آغازین سریع‌تر می‌شد، چون مفسر تنها کافی بود که خط نخست کد را بخواند تا بتواند آن را به بایت‌کد ترجمه کرده و به طرز صحیح اجرا کند. برای نیازهای اینترنت در دهه ۱۹۹۰ میلادی، جاوا اسکریپت این کار را به طرز خوبی انجام می‌داد. مشکل زمانی بروز کرد که اپلیکیشن‌ها رفته‌رفته پیچیده‌تر شدند.

در دهه ۲۰۰۰ میلادی فناوری‌هایی مانند Ajax موجب شدند که وب اپلیکیشن‌ها، پویاتر شوند، جیمیل در سال ۲۰۰۴ و گوگل مپ در سال ۲۰۰۵ آغازگر روندی برای استفاده از این فناوری ای‌جکس بودند. این روش جدید برای ساخت وب اپلیکیشن‌ها موجب شد که بیشتر بخش منطقی برنامه در سمت کلاینت نوشت شود. در این زمان جاوا اسکریپت باید عملکرد خود را ارتقا می‌داد و این اتفاق در سال ۲۰۰۸ با ظهور گوگل و موتور V8 آن که همه کدهای جاوا اسکریپت را به طور بی‌درنگ به بایت‌کد کامپایل می‌کرد رخ داد. اما اینک شاید بپرسید طرز کار کامپایلرهای JIT چگونه است؟

آشنایی با طرز کار کامپایلرهای JIT

اگر بخواهیم کامپایلرهای JIT را به طور خلاصه توضیح دهیم، زمانی که کد بارگذاری شد، کد منبع به یک بازنمایی درختی تبدیل می‌شود که «درخت ساختار مجرد» (Abstract Syntax Tree) یا AST نامیده می‌شود. پس از آن بسته به این که از چه موتور/سیستم عامل/پلتفرمی استفاده می‌شود، یا یک نسخه مبنا از کد کامپایل می‌شود و یا بایت‌کد تولید می‌شود که باید تفسیر شود.

در این مرحله profiler به رصد و گردآوری داده‌های اجرای کد می‌پردازد. البته این توضیح بسیار مختصر بوده و تفاوت‌هایی در میان موتورهای مرورگر مختلف در این زمینه وجود دارد.

در گام نخست، همه چیز از تفسیر عبور می‌کند، این فرایند تضمین می‌کند که کد پس از ایجاد AST سریع‌تر اجرا می‌شود. زمانی که قطعه کدی چندین بار اجرا می‌شود، مانند تابع ()getNextState ما، تفسیر عملکرد خود را از دست می‌دهد، زیرا باید قطعه کد یکسانی را به طور مکرر تفسیر کند و زمانی که این اتفاق بیافتد profiler این قطعه کد را به صورت «کد گرم» (Warm Code) علامتگذاری می‌کند و «کامپایلر مبنا» (Baseline Compiler) وارد عمل می‌شود.

کامپایلر مبنا

برای این که طرز کار JIT را بهتر نشان دهیم از این پس از قطعه کد زیر به عنوان مثال استفاده می‌کنیم:

function sum (x, y) {   return x + y; }  [1, 2, 3, 4, 5, '6', 7, 8, 9, 10].reduce(   (prev, curr) => sum(prev, curr),   0 );

زمانی که پروفایلر یک قطعه کد را به صورت «کد گرم» علامتگذاری می‌کند، JIT کد را به کامپایلر مبنا می‌سپارد که یک کد کامپایل شده می‌سازد و در همین حال پروفایلر همچنان به گردآوری داده‌ها در ارتباط با فراوانی و انواع کدهای اجرا شده ادامه می‌دهد. زمانی که این بخش از کد اجرا می‌شود (در مثال فرضی ما بخش ;return x + y است) JIT تنها کافی است این بخش کامپایل شده را مجدداً اجرا کند. زمانی که کد گرم چندین بار به روش مشابه فراخوانی شود، به صورت «کد داغ» (hot code) علامتگذاری می‌شود.

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

زمانی که یک قطعه کد به صورت کد داغ علامت‌گذاری شود، «کامپایلر بهینه‌ساز» (Optimizer Compiler) یک نسخه باز هم سریع‌تر از این کد می‌سازد. این وضعیت تنها بر مبنای این فرضیه عمل می‌کند که کامپایلر بهینه‌ساز، نوع متغیرها یا شکل شیءهای مورد استفاده در کد را بهینه‌سازی می‌کند. در مورد مثال فرضی ما می‌توان تصور کرد که «کد داغ» ;return x + y هر دو مقدار x و y را به صورت number فرض می‌کند.

مشکل این است که در مواردی کد با چیزی مواجه می‌شود که کامپایلر بهینه‌ساز انتظار ندارد، برای نمونه در مورد مثال ما با (‘sum(15, ‘۶ فراخوانی می‌شود، چون y یک string است. زمانی که این اتفاق می‌افتد، پروفایلر فرض می‌کند که فرضیات آن اشتباه بوده است و همه چیز را کنار گذاشته و به نسخه کامپایل شده مبنا (یا تفسیری) باز می‌گردد. این مرحله «غیر بهینه‌سازی» (Deoptimization) نام دارد. برخی اوقات این اتفاق چنان مکرر رخ می‌دهد که حتی نسخه بهینه شده نسبت به نسخه مبنا کندتر می‌شود.

جمع‌بندی

برخی موتورهای جاوا اسکریپت در خصوص کمّیت تلاش‌های بهینه‌سازی محدودیت‌هایی دارند و زمانی که به این حد برسند دیگر برای بهینه‌سازی تلاش نمی‌کنند. برخی دیگر مانند V8 به صورت شهودی زمانی که می‌بینند احتمالاً کد «غیربهینه‌سازی» خواهد شد از بهینه‌سازی آن اجتناب می‌کنند. این فرایند bailing out نام دارد.

بنابراین به طور خلاصه مراحل کامپایلر JIT را می‌توان به صورت زیر توصیف کرد:

  • تجزیه
  • کامپایل
  • بهینه‌سازی/غیر بهینه سزی
  • اجرا
  • Garbage Collector
نمونه‌ای از مراحل کار JIT
نمونه‌ای از مراحل کار JIT

همه این پیشرفت‌ها که به وسیله کامپایلرهای JIT ارائه شده است، موجب گشته که جاوا اسکریپت نسبت به سال ۲۰۰۸ بسیار سریع‌تر شود. اپلیکیشن‌های امروزی به لطف سرعت موتورهای جاوا اسکریپت بسیار پایدارتر هستند.

اگر این مطلب برای شما مفید بوده است، آموزش‌های زیر نیز به شما پیشنهاد می‌شوند:

  • مجموعه آموزش‌های برنامه‌‌نویسی
  • مجموعه آموزش‌های مهندسی نرم افزار
  • آموزش طراحی کامپایلر
  • آموزش طراحی کامپایلر (مرور و حل تست های کنکور کارشناسی ارشد)
  • کامپایلر، طراحی و معماری آن — به زبان ساده
  • تولید کد میانی (Intermediate Code) در طراحی کامپایلر – راهنمای جامع
  • آموزش طراحی کامپایلر — مجموعه مقالات جامع وبلاگ فرادرس

==

بلی خیر

نوشته وب اسمبلی (WebAssembly) — به زبان ساده اولین بار در وبلاگ فرادرس. پدیدار شد.

درباره ی admin

مطلب پیشنهادی

تعمیر یک هارد درایو خراب و بازیابی داده های آن— به زبان ساده

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

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

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