ניפוי באגים מהיר יותר ב-WebAssembly

Philip Pfaffe
Kim-Anh Tran
Kim-Anh Tran
Eric Leese
Sam Clegg

בכנס המפתחים של Chrome 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();
}

זו עדיין דוגמה קטנה למדי, וסביר להניח שלא תיתקלו בבעיות אמיתיות ביישום גדול במיוחד, אבל עדיין נוכל להציג לכם את התכונות החדשות. ההגדרה הזו מהירה ופשוטה, ואפשר לנסות אותה בעצמכם.

בפוסט האחרון הסברנו איך להדר את הדוגמה הזו ולנפות באגים שנמצאו בה. בואו ננסה שוב, אבל בואו נסתכל על //performance//:

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

הפקודה הזו יוצרת קובץ בינארי של 3MB Wam. החלק העיקרי של המידע הזה, כפי שניתן לצפות, הוא מידע על תוצאות ניפוי הבאגים. אפשר לאמת את זה באמצעות הכלי 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

בפלט הזה מוצגים כל הקטעים בקובץ Wam שנוצר. רובם הם קטעי WebAssembly רגילים, אבל יש גם כמה מקטעים בהתאמה אישית ששמם מתחיל ב-.debug_. זה המקום שבו הקובץ הבינארי מכיל את המידע על תוצאות ניפוי הבאגים! אם נסכם את כל הגדלים, אנחנו רואים שהמידע על תוצאות ניפוי הבאגים מהווה כ-2.3MB מהקובץ שגודלו 3MB. אם time גם את הפקודה emcc, רואים במחשב שלנו שהיא לקחה בערך דקה וחצי. המספרים האלה מהווים בסיס קטן ונחמד, אבל הם כל כך קטנים כנראה שאף אחד לא יראה אותם. עם זאת, באפליקציות אמיתיות, הקובץ הבינארי של ניפוי הבאגים יכול להגיע בקלות לגודל של GBs ובנייתו לוקחת דקות ספורות!

דילוג על בינארין

כשמפתחים אפליקציה ב-Wasm באמצעות Emscripten, אחד משלבי ה-build הסופיים הוא הפעלת כלי האופטימיזציה של Binaryen. Binaryen היא ערכת כלי מהדר שמאפשרת אופטימיזציה והפיכת קבצים בינאריים (כמו בינארי) ל-WebAssembly וחוקיים. ההפעלה של Binaryen כחלק מה-build יקרה למדי, אבל היא נדרשת רק בתנאים מסוימים. בגרסאות build של ניפוי באגים, אנחנו יכולים לזרז את זמן ה-build באופן משמעותי אם לא יהיה צורך באישורים של 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 פועל ומשכתב את הקובץ הבינארי באופן בלתי צפוי. כך נוכל לוודא שאנחנו ממשיכים בנתיב המהיר.

למרות שהדוגמה שלנו די קטנה, עדיין אפשר לראות את ההשפעה של דילוג על Binaryen! לפי time, הפקודה הזו רצה קצת פחות משנייה אחת, כך שהיא מהירה יותר בחצי שנייה מבעבר!

שינויים מתקדמים

דילוג על סריקת קובצי קלט

בדרך כלל, כשמקשרים פרויקט Emscripten, emcc יסרוק את כל הקבצים והספריות של אובייקטי הקלט. הפעולה הזו מאפשרת להטמיע יחסי תלות מדויקים בין פונקציות של ספריית JavaScript לבין סמלים מקוריים בתוכנית שלכם. בפרויקטים גדולים יותר, הסריקה הנוספת הזו של קובצי קלט (באמצעות llvm-nm) עשויה להוסיף משמעותית לזמן הקישור.

במקום זאת, אפשר להריץ עם -sREVERSE_DEPS=all שאומרת ל-emcc לכלול את כל יחסי התלות המקומיים האפשריים של פונקציות JavaScript. השיטה הזו גורמת לעומס קטן על קוד, אבל היא יכולה לזרז את זמני הקישורים, והיא יכולה להיות שימושית לגרסאות build של ניפוי באגים.

בפרויקט קטן כמו הדוגמה שלנו, אין הבדל ממשי, אבל אם יש לכם מאות או אפילו אלפי קובצי אובייקטים בפרויקט, הדבר יכול לשפר משמעותית את זמני הקישורים.

הסרת הקטע 'שם'

בפרויקטים גדולים, במיוחד בכאלה שמשתמשים בהם הרבה בתבניות C++, הקטע 'name' של WebAssembly יכול להיות גדול מאוד. בדוגמה שלנו מדובר רק בחלק זעיר מגודל הקובץ הכולל (ראו את הפלט של llvm-objdump למעלה), אבל במקרים מסוימים יכולה להיות משמעותית מאוד. אם הקטע "שם" של האפליקציה גדול מאוד, והמידע על תוצאות ניפוי הבאגים של הגמד מספיק לצרכים שלכם לניפוי באגים, כדאי להסיר את הקטע 'שם':

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

הפעולה הזו תסיר את הקטע 'name' של WebAssembly תוך שמירה על קטעי ניפוי הבאגים של DWARF.

ביקוע של ניפוי באגים

קבצים בינאריים עם הרבה נתונים של ניפוי באגים לא רק מפעילים לחץ על זמן ה-build, אלא גם על זמן ניפוי הבאגים. הכלי לניפוי באגים צריך לטעון את הנתונים וליצור בשבילו אינדקס, כדי שיוכל להגיב במהירות לשאילתות, כמו "What's the type של המשתנה המקומי 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

בהמשך אנחנו מציגים את הפקודות השונות ואילו קבצים נוצרים על-ידי הידור (compiling) ללא נתוני ניפוי באגים, נתונים של ניפוי באגים ולבסוף עם נתוני ניפוי באגים וגם ביקוע של ניפוי באגים.

את הפקודות השונות ואיזה קבצים נוצרים

כשמפצלים את נתוני DWARF, חלק מנתוני ניפוי הבאגים נמצאים יחד עם הנתונים הבינאריים, ואילו חלקם מועבר לקובץ mandelbrot.dwo (כפי שמתואר למעלה).

ל-mandelbrot יש רק קובץ מקור אחד, אבל בדרך כלל פרויקטים גדולים יותר ממנו וכוללים יותר מקובץ אחד. פיצול ניפוי הבאגים יוצר קובץ .dwo לכל אחד מהם. כדי שגרסת הבטא הנוכחית של הכלי לניפוי באגים (0.1.6.1615) תוכל לטעון את המידע המפוצל על תוצאות ניפוי הבאגים, עלינו לאגד את כל הנתונים לחבילת DWARF שנקראת (.dwp) באופן הבא:

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

לצרף קובצי DW לחבילת DWARF

לבניית חבילת DWARF מאובייקטים הנפרדים יש יתרון בכך שנדרש למלא רק קובץ אחד נוסף! אנחנו פועלים כרגע לטעינת כל האובייקטים הנפרדים בגרסה עתידית.

מה קורה ב-DWARF 5?

אולי שמת לב, הצבנו דגל נוסף לפקודה emcc שלמעלה, -gdwarf-5. הפעלה של גרסה 5 של סמלי DWARF, שכבר לא מוגדרת כברירת המחדל, היא טריק נוסף שיעזור לנו להתחיל לנפות באגים מהר יותר. אצלו, מידע מסוים מאוחסן בקובץ הבינארי הראשי, שלא נכלל בגרסה 4 של ברירת המחדל. באופן ספציפי, אפשר לקבוע את הקבוצה המלאה של קובצי המקור רק מהקובץ הבינארי הראשי. כך הכלי לניפוי באגים יכול לבצע פעולות בסיסיות כמו הצגת עץ המקור המלא והגדרת נקודות עצירה (breakpoint) בלי לטעון ולנתח את נתוני הסמלים המלאים. כך ניפוי הבאגים עם סמלים מפוצלים מהיר הרבה יותר, ולכן אנחנו תמיד משתמשים בדגלי שורת הפקודה -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 ועל ניפוי באגים

אמ;לק: מה היתרון של שימוש בבקע לניפוי באגים?

יש כמה יתרונות לפיצול המידע על תוצאות ניפוי הבאגים אם המידע הזה עובד עם אפליקציות גדולות:

  1. קישור מהיר יותר: ה-linker לא צריך יותר לנתח את כל המידע על תוצאות ניפוי הבאגים. בדרך כלל, מקשרים צריכים לנתח את כל נתוני DWARF שבקובץ הבינארי. על ידי הסרת חלקים גדולים מפרטי ניפוי הבאגים בקבצים נפרדים, ה-Linkers מטפלים בקבצים בינאריים קטנים יותר. התוצאה היא זמני קישור מהירים יותר (במיוחד באפליקציות גדולות).

  2. ניפוי באגים מהיר יותר: הכלי לניפוי באגים יכול לדלג על ניתוח הסמלים הנוספים בקבצים .dwo או .dwp בחלק מחיפושי הסמלים. בבדיקות מסוימות (למשל, בקשות למיפוי השורות בקובצי Wam-C++ ), אנחנו לא צריכים לבדוק את נתוני ניפוי הבאגים הנוספים. כך אנחנו יכולים לחסוך זמן, בלי שתצטרכו לטעון ולנתח את נתוני ניפוי הבאגים הנוספים.

1: אם לא מותקנת במערכת גרסה עדכנית של llvm-objdump ואתם משתמשים ב-emsdk, תוכלו למצוא אותה בספרייה emsdk/upstream/bin.

הורדת הערוצים של התצוגה המקדימה

כדאי להשתמש ב-Chrome Canary, Dev או בטא כדפדפן הפיתוח שמוגדר כברירת מחדל. הערוצים לתצוגה מקדימה אלה מעניקים לך גישה לתכונות החדשות של כלי הפיתוח, בודקים ממשקי API מתקדמים של פלטפורמות אינטרנט ומוצאים בעיות באתר שלך לפני שהמשתמשים עושים זאת.

יצירת קשר עם הצוות של כלי הפיתוח ל-Chrome

אפשר להשתמש באפשרויות הבאות כדי לדון בתכונות ובשינויים החדשים בפוסט, או בכל נושא אחר שקשור לכלי פיתוח.

  • אפשר לשלוח לנו הצעה או משוב דרך crbug.com.
  • כדי לדווח על בעיה בכלי הפיתוח, לוחצים על אפשרויות נוספות   עוד > עזרה > דיווח על בעיות בכלי הפיתוח ב'כלי פיתוח'.
  • שליחת ציוץ אל @ChromeDevTools.
  • נשמח לשמוע מה חדש בסרטונים ב-YouTube של כלי הפיתוח או בסרטונים ב-YouTube שקשורים לכלי פיתוח.