اشکال زدایی WebAssembly سریعتر

Philip Pfaffe
کیم آنه تران
Kim-Anh Tran
اریک لیز
Eric Leese
Sam Clegg

در Chrome Dev Summit 2020 ، ما برای اولین بار پشتیبانی از اشکال زدایی Chrome را برای برنامه های WebAssembly در وب به نمایش گذاشتیم. از آن زمان، تیم انرژی زیادی را برای ایجاد مقیاس تجربه توسعه‌دهنده برای برنامه‌های کاربردی بزرگ و حتی بزرگ سرمایه‌گذاری کرده است. در این پست دستگیره هایی را که در ابزارهای مختلف اضافه کرده ایم (یا کار کرده ایم) و نحوه استفاده از آنها را به شما نشان خواهیم داد!

اشکال زدایی مقیاس پذیر

بیایید از جایی که در پست 2020 خود را ترک کردیم ادامه دهیم. در اینجا مثالی است که در آن زمان به آن نگاه می کردیم:

#include <SDL2/SDL.h>
#include <complex>

int main() {
  // Init SDL.
  int width = 600, height = 600;
  SDL_Init(SDL_INIT_VIDEO);
  SDL_Window* window;
  SDL_Renderer* renderer;
  SDL_CreateWindowAndRenderer(width, height, SDL_WINDOW_OPENGL, &window,
                              &renderer);

  // Generate a palette with random colors.
  enum { MAX_ITER_COUNT = 256 };
  SDL_Color palette[MAX_ITER_COUNT];
  srand(time(0));
  for (int i = 0; i < MAX_ITER_COUNT; ++i) {
    palette[i] = {
        .r = (uint8_t)rand(),
        .g = (uint8_t)rand(),
        .b = (uint8_t)rand(),
        .a = 255,
    };
  }

  // Calculate and draw the Mandelbrot set.
  std::complex<double> center(0.5, 0.5);
  double scale = 4.0;
  for (int y = 0; y < height; y++) {
    for (int x = 0; x < width; x++) {
      std::complex<double> point((double)x / width, (double)y / height);
      std::complex<double> c = (point - center) * scale;
      std::complex<double> z(0, 0);
      int i = 0;
      for (; i < MAX_ITER_COUNT - 1; i++) {
        z = z * z + c;
        if (abs(z) > 2.0)
          break;
      }
      SDL_Color color = palette[i];
      SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
      SDL_RenderDrawPoint(renderer, x, y);
    }
  }

  // Render everything we've drawn to the canvas.
  SDL_RenderPresent(renderer);

  // SDL_Quit();
}

این هنوز یک نمونه نسبتا کوچک است و احتمالاً هیچ یک از مشکلات واقعی را که در یک برنامه واقعاً بزرگ می بینید، نخواهید دید، اما ما همچنان می توانیم ویژگی های جدید را به شما نشان دهیم. راه اندازی سریع و آسان و تلاش برای خودتان!

در آخرین پست، نحوه کامپایل و اشکال زدایی این مثال را مورد بحث قرار دادیم. Let's do that again, but let's also take a peek at the //performance// :

$ emcc -sUSE_SDL=2 -g -O0 -o mandelbrot.html mandelbrot.cc -sALLOW_MEMORY_GROWTH

این دستور یک باینری wasm 3 مگابایتی تولید می کند. و بخش عمده ای از آن، همانطور که ممکن است انتظار داشته باشید، اطلاعات اشکال زدایی است. برای مثال می‌توانید این را با ابزار llvm-objdump [1] تأیید کنید:

$ llvm-objdump -h mandelbrot.wasm

mandelbrot.wasm:        file format wasm

Sections:
Idx Name          Size     VMA      Type
  0 TYPE          0000026f 00000000
  1 IMPORT        00001f03 00000000
  2 FUNCTION      0000043e 00000000
  3 TABLE         00000007 00000000
  4 MEMORY        00000007 00000000
  5 GLOBAL        00000021 00000000
  6 EXPORT        0000014a 00000000
  7 ELEM          00000457 00000000
  8 CODE          0009308a 00000000 TEXT
  9 DATA          0000e4cc 00000000 DATA
 10 name          00007e58 00000000
 11 .debug_info   000bb1c9 00000000
 12 .debug_loc    0009b407 00000000
 13 .debug_ranges 0000ad90 00000000
 14 .debug_abbrev 000136e8 00000000
 15 .debug_line   000bb3ab 00000000
 16 .debug_str    000209bd 00000000

این خروجی تمام بخش هایی را که در فایل wasm تولید شده هستند به ما نشان می دهد، اکثر آنها بخش های WebAssembly استاندارد هستند، اما چندین بخش سفارشی نیز وجود دارد که نام آنها با .debug_ شروع می شود. اینجاست که باینری حاوی اطلاعات اشکال زدایی ماست! اگر همه اندازه ها را جمع کنیم، می بینیم که اطلاعات اشکال زدایی تقریباً 2.3 مگابایت از فایل 3 مگابایتی ما را تشکیل می دهد. اگر دستور emcc را نیز time کنیم، می بینیم که در دستگاه ما تقریباً 1.5 ثانیه اجرا شده است. این اعداد یک پایه کوچک خوب ایجاد می کنند، اما آنقدر کوچک هستند که احتمالاً هیچ کس به آنها توجهی نمی کند. با این حال، در برنامه های واقعی، باینری اشکال زدایی به راحتی می تواند به اندازه گیگابایت برسد و ساخت آن چند دقیقه طول می کشد!

پرش باینرین

هنگام ساخت یک برنامه wam با Emscripten ، یکی از مراحل ساخت نهایی آن اجرای بهینه ساز Binaryen است. Binaryen یک جعبه ابزار کامپایلر است که باینری های WebAssembly(مانند) را هم بهینه می کند و هم قانونی می کند. اجرای Binaryen به عنوان بخشی از ساخت، نسبتاً گران است، اما فقط تحت شرایط خاصی مورد نیاز است. برای ساخت‌های اشکال‌زدایی، اگر از نیاز به پاس‌های Binaryen اجتناب کنیم، می‌توانیم زمان ساخت را به میزان قابل توجهی افزایش دهیم. متداول‌ترین پاس مورد نیاز Binaryen برای قانونی کردن امضاهای تابع شامل مقادیر صحیح 64 بیتی است. با انتخاب ادغام WebAssembly BigInt با استفاده از -sWASM_BIGINT می توانیم از این امر جلوگیری کنیم.

$ emcc -sUSE_SDL=2 -g -O0 -o mandelbrot.html mandelbrot.cc -sALLOW_MEMORY_GROWTH -sWASM_BIGINT -sERROR_ON_WASM_CHANGES_AFTER_LINK

برای اندازه گیری خوب، پرچم -sERROR_ON_WASM_CHANGES_AFTER_LINK را به کار انداخته ایم. این کمک می کند تا زمانی که Binaryen در حال اجرا است و به طور غیرمنتظره ای باینری را بازنویسی می کند. به این ترتیب، می‌توانیم مطمئن شویم که در مسیر سریع باقی می‌مانیم.

اگرچه مثال ما نسبتاً کوچک است، اما هنوز هم می‌توانیم اثر پرش باینرین را ببینیم! طبق time ، این دستور کمتر از 1 ثانیه اجرا می شود، بنابراین نیم ثانیه سریعتر از قبل!

ترفندهای پیشرفته

رد شدن از اسکن فایل ورودی

معمولاً هنگام پیوند دادن یک پروژه Emscripten، emcc تمام فایل‌ها و کتابخانه‌های شی ورودی را اسکن می‌کند. این کار را به منظور پیاده سازی وابستگی های دقیق بین توابع کتابخانه جاوا اسکریپت و نمادهای بومی در برنامه شما انجام می دهد. برای پروژه های بزرگتر، این اسکن اضافی فایل های ورودی (با استفاده از llvm-nm ) می تواند به میزان قابل توجهی به زمان پیوند اضافه کند.

در عوض می‌توان با -sREVERSE_DEPS=all اجرا کرد که به emcc می‌گوید تمام وابستگی‌های بومی توابع جاوا اسکریپت را شامل شود. این حجم کد کوچکی دارد اما می تواند زمان پیوند را سرعت بخشد و برای ساخت اشکال زدایی مفید باشد.

برای پروژه‌ای به کوچکی مثال ما، این تفاوت واقعی ندارد، اما اگر صدها یا حتی هزاران فایل شی در پروژه خود داشته باشید، می‌تواند به طور معناداری زمان پیوند را بهبود بخشد.

حذف بخش "نام".

در پروژه‌های بزرگ، به‌ویژه پروژه‌هایی که از الگوی C++ زیاد استفاده می‌کنند، بخش WebAssembly «نام» می‌تواند بسیار بزرگ باشد. در مثال ما این فقط کسری کوچک از اندازه کلی فایل است (به خروجی llvm-objdump در بالا مراجعه کنید) اما در برخی موارد می تواند بسیار مهم باشد. اگر بخش "نام" برنامه شما بسیار بزرگ است و اطلاعات اشکال زدایی کوتوله برای نیازهای اشکال زدایی شما کافی است، حذف بخش "نام" می تواند مفید باشد:

$ emstrip --no-strip-all --remove-section=name mandelbrot.wasm

این کار بخش WebAssembly "name" را از بین می برد و در عین حال بخش های اشکال زدایی DWARF را حفظ می کند.

شکافت اشکال زدایی

باینری ها با داده های اشکال زدایی زیاد نه تنها بر زمان ساخت بلکه بر زمان اشکال زدایی نیز فشار وارد می کنند. دیباگر باید داده ها را بارگیری کند و باید یک شاخص برای آن بسازد تا بتواند به سرعت به پرس و جوهایی مانند "نوع متغیر محلی x چیست؟" پاسخ دهد.

شکافت اشکال‌زدایی به ما امکان می‌دهد اطلاعات اشکال‌زدایی یک باینری را به دو بخش تقسیم کنیم: یکی، که در باینری باقی می‌ماند، و یکی، که در یک فایل جداگانه به نام شی DWARF ( .dwo ) موجود است. می توان آن را با ارسال پرچم -gsplit-dwarf به Emscripten فعال کرد:

$ emcc -sUSE_SDL=2 -g -gsplit-dwarf -gdwarf-5 -O0 -o mandelbrot.html mandelbrot.cc  -sALLOW_MEMORY_GROWTH -sWASM_BIGINT -sERROR_ON_WASM_CHANGES_AFTER_LINK

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

دستورات مختلف و فایل هایی که تولید می شوند

هنگام تقسیم داده های DWARF، بخشی از داده های اشکال زدایی همراه با باینری قرار می گیرد، در حالی که بخش بزرگی در فایل mandelbrot.dwo قرار می گیرد (همانطور که در بالا نشان داده شده است).

برای mandelbrot ما فقط یک فایل منبع داریم، اما به طور کلی پروژه ها بزرگتر از این هستند و شامل بیش از یک فایل هستند. شکافت اشکال زدایی یک فایل .dwo برای هر یک از آنها ایجاد می کند. برای اینکه نسخه بتای فعلی اشکال‌زدا (0.1.6.1615) بتواند این اطلاعات اشکال‌زدایی تقسیم‌شده را بارگیری کند، باید همه آن‌ها را در بسته‌ای به اصطلاح DWARF ( .dwp ) جمع کنیم:

$ emdwp -e mandelbrot.wasm -o mandelbrot.dwp

فایل های dwo را در یک بسته DWARF قرار دهید

ساختن پکیج DWARF از بین اشیاء مجزا این مزیت را دارد که شما فقط باید یک فایل اضافی را ارائه دهید! ما در حال حاضر در حال کار بر روی بارگیری تمام اشیاء مجزا در نسخه بعدی هستیم.

DWARF 5 چه خبر؟

ممکن است متوجه شده باشید، ما یک پرچم دیگر را به دستور emcc بالا، -gdwarf-5 مخفی کردیم. فعال کردن نسخه 5 نمادهای DWARF که در حال حاضر پیش فرض نیست، ترفند دیگری است که به ما کمک می کند تا اشکال زدایی را سریعتر شروع کنیم. با آن، اطلاعات خاصی در باینری اصلی ذخیره می شود که نسخه پیش فرض 4 آن را حذف کرده است. به طور خاص، ما می توانیم مجموعه کامل فایل های منبع را فقط از باینری اصلی تعیین کنیم. این به دیباگر اجازه می دهد تا اقدامات اساسی مانند نمایش درخت منبع کامل و تعیین نقاط شکست بدون بارگیری و تجزیه داده های نماد کامل انجام دهد. این امر اشکال زدایی با نمادهای تقسیم را بسیار سریعتر می کند، بنابراین ما همیشه از پرچم های خط فرمان -gsplit-dwarf و -gdwarf-5 با هم استفاده می کنیم!

با فرمت اشکال زدایی DWARF5 به یکی دیگر از ویژگی های مفید نیز دسترسی پیدا می کنیم. این یک فهرست نام را در داده های اشکال زدایی معرفی می کند که هنگام عبور پرچم -gpubnames ایجاد می شود:

$ emcc -sUSE_SDL=2 -g -gdwarf-5 -gsplit-dwarf -gpubnames -O0 -o mandelbrot.html mandelbrot.cc -sALLOW_MEMORY_GROWTH -sWASM_BIGINT -sERROR_ON_WASM_CHANGES_AFTER_LINK

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

برای افراد کنجکاو: نگاه کردن به داده های اشکال زدایی

شما می توانید llvm-dwarfdump برای نگاه کردن به داده های DWARF استفاده کنید. بیایید این را امتحان کنیم:

llvm-dwarfdump mandelbrot.wasm

این به ما یک نمای کلی در مورد "واحدهای کامپایل" (به طور کلی، فایل‌های منبع) که اطلاعات اشکال زدایی آنها را داریم، می‌دهد. در این مثال، ما فقط اطلاعات اشکال زدایی mandelbrot.cc را داریم. اطلاعات عمومی به ما اطلاع می‌دهد که ما یک واحد اسکلت داریم، که فقط به این معنی است که ما داده‌های ناقص روی این فایل داریم، و یک فایل .dwo جداگانه وجود دارد که حاوی اطلاعات اشکال‌زدایی باقی مانده است:

mandelbrot.wasm و اطلاعات اشکال زدایی

همچنین می توانید به جداول دیگر این فایل نگاهی بیندازید، به عنوان مثال در جدول خط که نگاشت بایت کد wasm به خطوط C++ را نشان می دهد (سعی کنید از llvm-dwarfdump -debug-line استفاده کنید).

همچنین می‌توانیم به اطلاعات اشکال‌زدایی که در فایل .dwo جداگانه موجود است نگاهی بیندازیم:

llvm-dwarfdump mandelbrot.dwo

mandelbrot.wasm و اطلاعات اشکال زدایی

TL;DR: مزیت استفاده از شکافت اشکال زدایی چیست؟

اگر کسی با برنامه های کاربردی بزرگ کار می کند، چندین مزیت برای تقسیم اطلاعات اشکال زدایی وجود دارد:

  1. پیوند سریعتر: پیوند دهنده دیگر نیازی به تجزیه کل اطلاعات اشکال زدایی ندارد . پیوند دهنده ها معمولاً نیاز دارند کل داده های DWARF را که در باینری هستند تجزیه کنند. با حذف بخش‌های بزرگی از اطلاعات اشکال‌زدایی در فایل‌های جداگانه، پیونددهنده‌ها با باینری‌های کوچک‌تر سروکار دارند، که منجر به زمان‌های پیوند سریع‌تر می‌شود (مخصوصاً برای برنامه‌های بزرگ صادق است).

  2. اشکال‌زدایی سریع‌تر: اشکال‌زدا می‌تواند از تجزیه نمادهای اضافی در فایل‌های .dwo / .dwp برای برخی جستجوهای نماد صرفنظر کند . برای برخی جستجوها (مانند درخواست‌های مربوط به نگاشت خط فایل‌های wam-to-C++)، نیازی به بررسی داده‌های دیباگ اضافی نداریم. این باعث صرفه جویی در وقت ما می شود و نیازی به بارگیری و تجزیه داده های دیباگ اضافی نیست.

1 : اگر نسخه اخیر llvm-objdump را روی سیستم خود ندارید و از emsdk استفاده می کنید، می توانید آن را در پوشه emsdk/upstream/bin پیدا کنید.

کانال های پیش نمایش را دانلود کنید

استفاده از Chrome Canary ، Dev یا Beta را به عنوان مرورگر توسعه پیش‌فرض خود در نظر بگیرید. این کانال‌های پیش‌نمایش به شما امکان دسترسی به جدیدترین ویژگی‌های DevTools را می‌دهند، به شما اجازه می‌دهند APIهای پلتفرم وب پیشرفته را آزمایش کنید و به شما کمک می‌کنند تا قبل از کاربران، مشکلات سایت خود را پیدا کنید!

با تیم Chrome DevTools در تماس باشید

از گزینه‌های زیر برای بحث در مورد ویژگی‌های جدید، به‌روزرسانی‌ها یا هر چیز دیگری مربوط به DevTools استفاده کنید.