זיהוי תכונות 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 חדשות בכל הדפדפנים.