זיהוי תכונות WebAssembly

איך משתמשים בתכונות החדשות ביותר של WebAssembly תוך תמיכה במשתמשים בכל הדפדפנים.

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

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

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

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

בחירת תכונות וקיבוץ שלהן

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

טבלה שמציגה את התמיכה בדפדפנים בתכונות שנבחרו.
טבלת התכונות הזו זמינה בכתובת webassembly.org/roadmap.

כדי לוודא שכל משתמש מקבל את חוויית השימוש האופטימלית ביותר, אפשר לפצל את הדפדפנים לקבוצות המאפיינים הבאות:

  • דפדפנים מבוססי Chrome: יש תמיכה בשרשור, ב-SIMD ובטיפול בחריגות.
  • Firefox: יש תמיכה ב-Thread וב-SIMD, אבל אין תמיכה בטיפול בחריגות.
  • Safari: יש תמיכה בשרשור, אבל אין תמיכה ב-SIMD ובטיפול בחריגות.
  • דפדפנים אחרים: נניח שיש תמיכה רק ב-WebAssembly ברמה הבסיסית.

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

הידור לקבוצות תכונות שונות

ל-WebAssembly אין דרך מובנית לזהות תכונות נתמכות בסביבת זמן הריצה, לכן כל ההוראות במודול צריכות להיות נתמכות ביעד. לכן, צריך לקמפל את קוד המקור ל-Wasm בנפרד לכל אחת מקבוצות התכונות השונות.

כל כלי ופלטפורמת build שונים, ותצטרכו לעיין במסמכי העזר של המהדר שלכם כדי ללמוד איך לשנות את התכונות האלה. כדי לשמור על פשטות, אשתמש בספריית C++‎ בקובץ יחיד בדוגמה הבאה ואראה איך לקמפל אותה באמצעות Emscripten.

אשתמש ב-SIMD באמצעות הדמיה של SSE2, בשרשור באמצעות תמיכה בספרייה Pthreads ובאפשרות לבחור בין טיפול בחריגות ב-Wasm לבין הטמעת JavaScript חלופית:

# First bundle: threads + SIMD + Wasm exceptions
$ emcc main.cpp -o main.threads-simd-exceptions.mjs -pthread -msimd128 -msse2 -fwasm-exceptions
# Second bundle: threads + SIMD + JS exceptions fallback
$ emcc main.cpp -o main.threads-simd.mjs -pthread -msimd128 -msse2 -fexceptions
# Third bundle: threads + JS exception fallback
$ emcc main.cpp -o main.threads.mjs -pthread -fexceptions
# Fourth bundle: basic Wasm with JS exceptions fallback
$ emcc main.cpp -o main.basic.mjs -fexceptions

בקוד C++ עצמו אפשר להשתמש ב-#ifdef __EMSCRIPTEN_PTHREADS__ וב-#ifdef __SSE2__ כדי לבחור באופן מותנה בין הטמעות מקבילות (של תהליכים ו-SIMD) של אותן פונקציות לבין הטמעות הטורי בזמן הידור. זה ייראה כך:

void process_data(std::vector<int>& some_input) {
#ifdef __EMSCRIPTEN_PTHREADS__
#ifdef __SSE2__
  // …implementation using threads and SIMD for max speed
#else
  // …implementation using threads but not SIMD
#endif
#else
  // …fallback implementation for browsers without those features
#endif
}

טיפול בחריגים לא דורש הנחיות #ifdef, כי אפשר להשתמש בו באותו אופן מ-C++‎, ללא קשר להטמעה הבסיסית שנבחרה באמצעות דגלים של הידור.

טעינה של החבילה הנכונה

אחרי שיוצרים חבילות לכל קבוצות האנשים עם מאפיינים משותפים, צריך לטעון את החבילה הנכונה מאפליקציית JavaScript הראשית. כדי לעשות זאת, קודם צריך לזהות אילו תכונות נתמכות בדפדפן הנוכחי. אפשר לעשות זאת באמצעות הספרייה wasm-feature-detect. כשמשלבים את התכונה הזו עם ייבוא דינמי, אפשר לטעון את החבילה עם האופטימיזציה הטובה ביותר בכל דפדפן:

import { simd, threads, exceptions } from 'https://unpkg.com/wasm-feature-detect?module';

let initModule;
if (await threads()) {
  if (await simd()) {
    if (await exceptions()) {
      initModule = import('./main.threads-simd-exceptions.mjs');
    } else {
      initModule = import('./main.threads-simd.mjs');
    }
  } else {
    initModule = import('./main.threads.mjs');
  }
} else {
  initModule = import('./main.basic.mjs');
}

const Module = await initModule();
// now you can use `Module` Emscripten object like you normally would

מילים אחרונות

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

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

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