החלפת נתיב חם ב-JavaScript של האפליקציה שלך באמצעות WebAssembly

זה מהיר באופן עקבי, אתה

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

WebAssembly היא מכונה וירטואלית ברמה נמוכה שמריצה את קוד הבייט שמאוחסן בקובצי .wasm. קוד הבייט הזה מוקלד ומובנה באופן משמעותי, כך שאפשר להדר אותו ולבצע אופטימיזציה שלו למערכת המארחת הרבה יותר מהר מכפי ש-JavaScript יכול. WebAssembly מספקת סביבה להפעלת קוד, שחשבה כבר מההתחלה על הרצה בארגז חול (sandboxing) והטמעה.

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

המסלול החם

ב-Squoosh כתבנו פונקציית JavaScript שמסובבת מאגר תמונות בכפולות של 90 מעלות. אמנם OffscreenCanvas אידיאלי לשם כך, אך לא ניתן להשתמש בו בכל הדפדפנים שטירגטנו אליו, ויש בו קצת באגים ב-Chrome.

הפונקציה חוזרת על עצמה בכל פיקסל של תמונת קלט ומעתיקה אותו למיקום אחר בתמונת הפלט כדי לבצע סיבוב. לתמונה בגודל 4,094 על 4,096 פיקסלים (16 מגה-פיקסל), יש צורך ביותר מ-16 מיליון חזרות של בלוק הקוד הפנימי. פעולה זו נקראת 'נתיב חם'. למרות המספר הרב של חזרות, שניים מתוך שלושה דפדפנים שבדקנו משלימים את המשימה תוך 2 שניות או פחות. משך זמן מקובל לסוג אינטראקציה זה.

for (let d2 = d2Start; d2 >= 0 && d2 < d2Limit; d2 += d2Advance) {
    for (let d1 = d1Start; d1 >= 0 && d1 < d1Limit; d1 += d1Advance) {
    const in_idx = ((d1 * d1Multiplier) + (d2 * d2Multiplier));
    outBuffer[i] = inBuffer[in_idx];
    i += 1;
    }
}

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

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

WebAssembly לזיהוי ביצועים צפויים

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

כתיבה עבור WebAssembly

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

הארכיטקטורה של WebAssembly

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

אם רוצים לצטט את WebAssembly.org:

כשאתם מהדרים קטע של קוד C או Rust ל-WebAssembly, מתקבל קובץ .wasm שמכיל הצהרת מודול. ההצהרה כוללת רשימה של "ייבוא" שהמודול מצפה לה מהסביבה, רשימת פעולות ייצוא שהמודול מאפשר למארח (פונקציות, קבועים, מקטעי זיכרון) וכמובן גם הוראות בינאריות בפועל לפונקציות שכלולות בו.

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

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

ניהול זיכרון

בדרך כלל, אחרי שאתם משתמשים בזיכרון נוסף, אתם צריכים לנהל איכשהו את הזיכרון הזה. באילו חלקים מהזיכרון נעשה שימוש? אילו מהם מוצעים בחינם? לדוגמה, ב-C יש את הפונקציה malloc(n) שמוצאת שטח זיכרון של n בייטים ברצף. פונקציות מהסוג הזה נקראות גם "סלים". כמובן שצריך לכלול את ההטמעה של המקצה שבשימוש במודול WebAssembly וכך להגדיל את הקובץ. הגודל והביצועים של הפונקציות האלה של ניהול הזיכרון עשויים להשתנות באופן משמעותי בהתאם לאלגוריתם שבו נעשה שימוש, ולכן שפות רבות מציעות הטמעות מרובות שניתן לבחור (dmalloc, emmalloc, wee_alloc וכו').

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

מפונק לבחירה

אם בדקתם את פונקציית ה-JavaScript המקורית שאותה אנחנו רוצים לבצע ל-WebAssembly-fy, תוכלו לראות שמדובר בקוד חישובי בלבד ללא ממשקי API ספציפיים ל-JavaScript. לכן, צריך להעביר את הקוד לכל שפה. בדקנו 3 שפות שונות שמבצעות את ההדרה ל-WebAssembly: C/C++, Rust ו-AssemblyScript. השאלה היחידה שאנחנו צריכים לענות עליה בכל אחת מהשפות היא: איך אפשר לגשת לזיכרון גולמי בלי להשתמש בפונקציות של ניהול זיכרון?

C ו-Emscripten

Emscripten הוא מהדר C שמיועד ליעד WebAssembly. המטרה של Emscripten היא לתפקד כתחליף למהדרי C ידועים כמו GCC או קלאנג, והוא בעיקר תואם לדגלים. זהו חלק מרכזי במשימה של Emscripten, כי המטרה שלו היא להקל ככל האפשר על ההידור של קודי C ו-C++ קיימים ל-WebAssembly.

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

uint8_t* ptr = (uint8_t*)0x124;
ptr[0] = 0xFF;

כאן אנחנו הופכים את המספר 0x124 למצביע לבייטים (או בייטים) לא חתומים של 8 ביט. הפעולה הזאת הופכת את המשתנה ptr למערך שמתחיל בכתובת הזיכרון 0x124, שבו אפשר להשתמש כמו כל מערך אחר, ומאפשר לנו לגשת לבייטים בודדים לצורך קריאה וכתיבה. במקרה שלנו, אנחנו משתמשים במאגר נתונים זמני RGBA של תמונה שאנחנו רוצים לסדר מחדש כדי לסובב אותה. כדי להעביר פיקסל, אנחנו צריכים להעביר 4 בייטים ברצף (בייט אחד לכל ערוץ: R, G, B ו-A). כדי להקל על התהליך, נוכל ליצור מערך של מספרים שלמים 32-bit ללא חתימה. לפי המוסכמה שלנו, תמונת הקלט שלנו תתחיל בכתובת 4, ותמונת הפלט שלנו תתחיל מיד לאחר שתמונת הקלט תסתיים:

int bpp = 4;
int imageSize = inputWidth * inputHeight * bpp;
uint32_t* inBuffer = (uint32_t*) 4;
uint32_t* outBuffer = (uint32_t*) (inBuffer + imageSize);

for (int d2 = d2Start; d2 >= 0 && d2 < d2Limit; d2 += d2Advance) {
    for (int d1 = d1Start; d1 >= 0 && d1 < d1Limit; d1 += d1Advance) {
    int in_idx = ((d1 * d1Multiplier) + (d2 * d2Multiplier));
    outBuffer[i] = inBuffer[in_idx];
    i += 1;
    }
}

אחרי שתעבירו את כל פונקציית ה-JavaScript ל-C, נוכל להדר את קובץ C בעזרת emcc:

$ emcc -O3 -s ALLOW_MEMORY_GROWTH=1 -o c.js rotate.c

כמו תמיד, emscripten יוצר קובץ קוד דבק שנקרא c.js ומודול Wasm שנקרא c.wasm. שימו לב שמודול Wasm מפתחות gzip לגודל של כ-260 בייטים בלבד, בעוד שקוד השיוך הוא בערך 3.5KB אחרי ה-gzip. אחרי כמה כינור, הצלחנו למחוק את קוד ההדבקה וליצור את המודולים של WebAssembly באמצעות ממשקי ה-Vanilla API. בדרך כלל אפשר לעשות זאת ב-Emscripten כל עוד לא משתמשים בכלל מהספרייה הרגילה של C.

Rust

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

אחד מהכלים האלה הוא wasm-pack, של קבוצת העבודה החלומות. wasm-pack לוקח את הקוד והופך אותו למודול ידידותי לאינטרנט, שעובד באופן מיידי עם חבילות APK כמו Webpack. wasm-pack מאוד נוח, אבל כרגע עובד רק עם Rust. הקבוצה תשקול להוסיף תמיכה בשפות אחרות שמטרגטות WebAssembly.

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

let imageSize = (inputWidth * inputHeight) as usize;
let inBuffer: &mut [u32];
let outBuffer: &mut [u32];
unsafe {
    inBuffer = slice::from_raw_parts_mut::<u32>(4 as *mut u32, imageSize);
    outBuffer = slice::from_raw_parts_mut::<u32>((imageSize * 4 + 4) as *mut u32, imageSize);
}

for d2 in 0..d2Limit {
    for d1 in 0..d1Limit {
    let in_idx = (d1Start + d1 * d1Advance) * d1Multiplier + (d2Start + d2 * d2Advance) * d2Multiplier;
    outBuffer[i as usize] = inBuffer[in_idx as usize];
    i += 1;
    }
}

הידור קובצי Rust באמצעות

$ wasm-pack build

מניב מודול Wasm בגודל 7.6KB עם כ-100 בייטים של קוד הדבקה (שניהם אחרי gzip).

AssemblyScript

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

    for (let d2 = d2Start; d2 >= 0 && d2 < d2Limit; d2 += d2Advance) {
      for (let d1 = d1Start; d1 >= 0 && d1 < d1Limit; d1 += d1Advance) {
        let in_idx = ((d1 * d1Multiplier) + (d2 * d2Multiplier));
        store<u32>(offset + i * 4 + 4, load<u32>(in_idx * 4 + 4));
        i += 1;
      }
    }

מכיוון שהפונקציה rotate() שלנו כוללת סוג קטן, היה אפשר להעביר את הקוד הזה ל-AssemblyScript בקלות. הפונקציות load<T>(ptr: usize) ו-store<T>(ptr: usize, value: T) מסופקות על ידי AssemblyScript כדי לגשת לזיכרון גולמי. כדי להדר את קובץ AssemblyScript שלנו, אנחנו צריכים רק להתקין את חבילת npm AssemblyScript/assemblyscript ולהפעיל

$ asc rotate.ts -b assemblyscript.wasm --validate -O3

AssemblyScript יספק לנו מודול Wasm של כ-300 בייטים וקוד דבק no. המודול עובד רק עם ממשקי WebAssembly של וניל.

זיהוי פלילי של WebAssembly

באופן מפתיע, 7.6KB של Rust גדולים בהשוואה לשתי השפות האחרות. יש כמה כלים בסביבה העסקית של WebAssembly שיכולים לעזור לכם לנתח את קובצי WebAssembly (לא משנה באיזו שפה נוצרה הקובץ) ולספר לכם מה קורה, וגם לעזור לכם לשפר את המצב.

טוויגי

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

$ twiggy top rotate_bg.wasm
צילום מסך של ההתקנה של Tuwiggy

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

Wasm-Strip

wasm-strip הוא כלי מתוך WebAssembly Binary Toolkit, או בקיצור wabt. הוא כולל כמה כלים שמאפשרים לבדוק ולטפל במודולים של WebAssembly. wasm2wat הוא מפרק שהופך מודול Wasm בינארי לפורמט קריא לבני אדם. Wabt מכיל גם wat2wasm, שמאפשר להמיר בחזרה את הפורמט שקריא לבני אדם למודול Wabt בינארי. אמנם השתמשנו בשני הכלים המשלימים האלה כדי לבדוק את קובצי WebAssembly, אבל הגענו למסקנה ש-wasm-strip הוא הכי שימושי. הקוד wasm-strip מסיר ממודול WebAssembly קטעים ומטא-נתונים מיותרים:

$ wasm-strip rotate_bg.wasm

הפעולה הזו מקטינה את גודל הקובץ של מודול החלודה מ-7.5KB ל-6.6KB (אחרי gzip).

wasm-opt

wasm-opt הוא כלי של Binaryen. הוא צריך מודול WebAssembly ומנסה לבצע אופטימיזציה שלו גם לשיפור הגודל וגם לביצועים על סמך ה-bytecode בלבד. כלים מסוימים כמו Emscripten כבר מריצים את הכלי הזה, וחלק לא. בדרך כלל כדאי לנסות ולשמור בייטים נוספים באמצעות הכלים האלה.

wasm-opt -O3 -o rotate_bg_opt.wasm rotate_bg.wasm

בעזרת wasm-opt אנחנו יכולים לחתוך עוד כמה בייטים וכך להשאיר סה"כ 6.2KB אחרי gzip.

#![no_std]

אחרי התייעצות ומחקר, כתבנו מחדש את קוד Rust בלי להשתמש בספרייה הרגילה של Rust, באמצעות הפיצ'ר #![no_std]. הפעולה הזו גם משביתה הקצאות זיכרון דינמיות ומסירות את קוד ההקצאה מהמודול. מתבצע הידור של קובץ Rust הזה באמצעות

$ rustc --target=wasm32-unknown-unknown -C opt-level=3 -o rust.wasm rotate.rs

הניבה מודול Wasm בנפח 1.6KB אחרי wasm-opt, wasm-strip ו-gzip. הוא אמנם עדיין גדול יותר מהמודולים שנוצרו על ידי C ו-AssemblyScript, אבל הוא קטן עד כדי כך שנחשב לפשוט.

ביצועים

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

איך מבצעים השוואה לשוק

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

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

השוואת ביצועים

השוואת מהירות לפי שפה
השוואת מהירות לפי דפדפן

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

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

השקעת מאמץ

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

AssemblyScript היה ללא חיכוך. הוא לא רק מאפשר להשתמש ב-TypeScript כדי לכתוב WebAssembly, מה שמקל על בדיקת הקוד עבור העמיתים שלי, אלא גם יוצר מודולים של WebAssembly ללא דבק שהם קטנים מאוד עם ביצועים טובים. סביר להניח שהכלים בסביבה העסקית של TypeScript, כמו prettier ו-tslint, יתאימו רק לכם.

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

C ו-Empscripten יצרו מודול WebAssembly קטן מאוד עם ביצועים גבוהים ובלי את האומץ להיכנס לקוד דבק ולצמצם אותו לצורך הצורך, כך שהגודל הכולל (המודול WebAssembly + קוד דבק) יהיה די גדול.

סיכום

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

תרשים השוואה

בהשוואה לגודל המודול או לביצועים של השפות השונות שבהן השתמשנו, נראה שהבחירה הטובה ביותר היא C או AssemblyScript. החלטנו לשלוח את Rust. יש כמה סיבות להחלטה הזו: כל רכיבי ה-Codec שנשלחו ב-Squoosh עד עכשיו נוצרו באמצעות Emscripten. רצינו להרחיב את הידע שלנו על הסביבה העסקית של WebAssembly ולהשתמש בשפה אחרת בסביבת הייצור. AssemblyScript הוא חלופה חזקה, אבל הפרויקט צעיר יחסית והמהדר לא בוגר כמו הדר של Rust.

ההבדל בגודל הקבצים בין חלודה לבין גודל השפות האחרות נראה קיצוני בתרשים הפיזור, אבל במציאות זה לא כזה משמעותי: טעינה של 500B או 1.6KB גם ב-2G תיקח פחות מ-1/10 שנייה. ו-Rust בתקווה תפתור את הפער מבחינת גודל המודול בקרוב.

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

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

עדכון: חלודה

לאחר שפרסם את המאמר הזה, ניק פיצג'רלד מצוות Rust הפנה אותנו לספר Rust Wasm המעולה שלו, שמכיל קטע על אופטימיזציה של גודל קובץ. בעקבות ההוראות (בעיקר הפעלה של אופטימיזציות של זמן קישור וטיפול ידני בפניות) אפשרו לנו לכתוב קוד Rust "רגיל" ולחזור להשתמש ב-Cargo (npm של Rust) בלי להגדיל את גודל הקובץ. מודול Rust מסתיים ב-370B אחרי gzip. לפרטים נוספים, אפשר לעיין ביחסי הציבור שפתחתי ב-Squoosh.

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