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

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

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

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

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

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

תכונות של איסוף וקיבוץ

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

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

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

  • דפדפני Chrome: פרוטוקול Thread, 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 חדשות בכל הדפדפנים.