ניפוי באגים מהיר יותר ב-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 ב-Wasm. ורוב זה, כפי שניתן לצפות, הוא מידע על תוצאות ניפוי הבאגים. ניתן לבדוק זאת באמצעות הכלי 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.3MB מקובץ ה-3MB שלנו. אם אנחנו גם time את הפקודה emcc, אנחנו רואים שבמחשב שלנו היה זה לוקח בערך 1.5 שניות. המספרים האלה הם בסיס קטן, אבל הם כל כך קטנים שאף אחד לא יוכל לראות אותם. עם זאת, באפליקציות האמיתיות, הקובץ הבינארי של ניפוי הבאגים יכול להגיע בקלות לגודל ב-GBs והבנייה שלו נמשכת דקות ספורות!

דילוג על Binaryen

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

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

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

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

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

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

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

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

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

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

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

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

לקבץ קובצי DW בחבילת 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 וניפוי באגים

אפשר גם להציג טבלאות אחרות בקובץ הזה, למשל בטבלת השורות, שבה מוצג המיפוי של bytecode של Wasm לשורות C++ (כדאי לנסות להשתמש ב-llvm-dwarfdump -debug-line).

נוכל גם לעיין במידע על תוצאות ניפוי הבאגים בקובץ ה-.dwo הנפרד:

llvm-dwarfdump mandelbrot.dwo

מידע על mandelbrot.wasm וניפוי באגים

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

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

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

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

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

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

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

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

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

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