המהירות שלו עקבית
במאמרים הקודמים שלי, תיארתי איך WebAssembly מאפשר להביא את סביבת הספריות של C/C++ לאינטרנט. אפליקציה אחת שמשתמשת באופן נרחב בספריות C/C++ היא squoosh, אפליקציית האינטרנט שלנו שמאפשרת לדחוס תמונות באמצעות מגוון קודקים שעבר הידור מ-C++ ל-WebAssembly.
WebAssembly היא מכונה וירטואלית ברמה נמוכה שמריצה את קוד הבייט ששמור בקובצי .wasm
. קוד הבייט הזה מוגדר באופן חזק ומבנה כך שאפשר לבצע לו הידור ואופטימיזציה למערכת המארחת מהר יותר מאשר ל-JavaScript. WebAssembly מספק סביבה להרצת קוד שתוכנן מראש לשימוש בסביבת חול (sandbox) ולהטמעה.
מניסיוני, רוב בעיות הביצועים באינטרנט נגרמות בגלל פריסה מאולצת וצבע יתר, אבל מדי פעם אפליקציה צריכה לבצע משימה שמחייבת חישובים יקרים וממושכים. כאן יכול לעזור 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. הסטאק נמצא בתוך המכונה הווירטואלית לחלוטין, ואין למפתחי האתרים גישה אליו (חוץ דרך כלי הפיתוח). לכן אפשר לכתוב מודולים של WebAssembly שלא זקוקים בכלל לזיכרון נוסף ומשתמשים רק בסטאק הפנימי של המכונה הווירטואלית.
במקרה שלנו, נצטרך להשתמש בקצת זיכרון נוסף כדי לאפשר גישה שרירותית לפיקסלים של התמונה וליצור גרסה מסובבת של התמונה הזו. לשם כך משמש השדה WebAssembly.Memory
.
ניהול זיכרון
בדרך כלל, אחרי שמשתמשים בנפח זיכרון נוסף, צריך לנהל אותו בצורה כלשהי. אילו חלקים מהזיכרון נמצאים בשימוש? אילו שירותים זמינים בחינם?
לדוגמה, ב-C יש את הפונקציה malloc(n)
שמוצאת מרחב זיכרון של n
בייטים רצופים. פונקציות מהסוג הזה נקראות גם 'מקצים'.
כמובן, ההטמעה של מנהל הקצאת הזיכרון שבשימוש צריכה להיכלל במודול WebAssembly, והיא תגדיל את גודל הקובץ. הגודל והביצועים של פונקציות ניהול הזיכרון האלה עשויים להשתנות באופן משמעותי בהתאם לאלגוריתם שבו נעשה שימוש. לכן, בשפות רבות יש כמה הטמעות לבחירה (dmalloc, emmalloc, wee_alloc וכו').
במקרה שלנו, אנחנו יודעים את המימדים של קובץ הקלט (ולכן את המימדים של קובץ הפלט) לפני שאנחנו מריצים את מודול WebAssembly. זו הייתה הזדמנות מצוינת: באופן מסורתי, היינו מעבירים את מאגר ה-RGBA של תמונה הקלט כפרמטר לפונקציית WebAssembly ומחזירים את התמונה המסובבת כערך המוחזר. כדי ליצור את ערך ההחזרה הזה, נצטרך להשתמש במקצה. אבל מכיוון שאנחנו יודעים את נפח הזיכרון הכולל הנדרש (פי שניים מגודל התמונה להזנה, פעם להזנה ופעם לפלט), אנחנו יכולים להעביר את התמונה להזנה לזיכרון של WebAssembly באמצעות JavaScript, להריץ את מודול WebAssembly כדי ליצור תמונה שנייה, מסובבת, ואז להשתמש ב-JavaScript כדי לקרוא חזרה את התוצאה. אנחנו יכולים להסתדר בלי להשתמש בכלל בניהול זיכרון.
יש מבחר גדול
אם תבחנו את הפונקציה המקורית ב-JavaScript שאנחנו רוצים להמיר ל-WebAssembly, תוכלו לראות שמדובר בקוד חישובי בלבד ללא ממשקי API ספציפיים ל-JavaScript. לכן, קל יחסית להעביר את הקוד הזה לכל שפה. בדקנו 3 שפות שונות שאפשר להדרש ל-WebAssembly: C/C++, Rust ו-AssemblyScript. השאלה היחידה שצריך לענות עליה בכל אחת מהשפות היא: איך ניגשים לזיכרון גולמי בלי להשתמש בפונקציות לניהול זיכרון?
C ו-Emscripten
Emscripten הוא מהדר C ליעד WebAssembly. המטרה של Emscripten היא לשמש כתחליף ללא תהליך העברה למהדרי C ידועים כמו GCC או clang, והוא תואם ברוב הדגלים. זהו חלק מהותי מהמשימה של Emscripten, שרוצים להפוך את הידור הקוד הקיים ב-C וב-C++ ל-WebAssembly לפשוט ככל האפשר.
הגישה לזיכרון גולמי היא חלק מהטבע של C, ומסיבה זו בדיוק קיימים מצביעים:
uint8_t* ptr = (uint8_t*)0x124;
ptr[0] = 0xFF;
כאן אנחנו הופכים את המספר 0x124
למצביע למספרים שלמים (או ביטים) לא חתומים באורך 8 ביט. הפעולה הזו הופכת את המשתנה ptr
למערך שמתחיל בכתובת הזיכרון 0x124
, שאפשר להשתמש בו כמו בכל מערך אחר, ומאפשרת לנו לגשת ביטים ספציפיים לצורך קריאה וכתיבה. במקרה שלנו, אנחנו מדברים על מאגר RGBA של תמונה שרוצים לשנות את הסדר שלה כדי לבצע סיבוב. כדי להעביר פיקסל, צריך להעביר 4 בייטים רצופים בבת אחת (בייט אחד לכל ערוץ: R, G, B ו-A). כדי לעשות זאת בקלות, אפשר ליצור מערך של מספרים שלמים 32-ביט ללא סימן. לפי הסכמה, תמונה הקלט תתחיל בכתובת 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
. הערה: קובץ ה-gzip של מודול ה-wasm הוא רק כ-260 בייטים, בעוד שקוד הדבקה הוא כ-3.5KB אחרי gzip. אחרי כמה ניסיונות, הצלחנו להיפטר מקוד הדבקה וליצור מופע של מודולי WebAssembly באמצעות ממשקי ה-API הרגילים.
בדרך כלל אפשר לעשות זאת באמצעות Emscripten, כל עוד לא משתמשים בספרייה הרגילה של C.
Rust
Rust היא שפת תכנות חדשה ומודרנית עם מערכת סוגים עשירה, ללא סביבת זמן ריצה ומודל בעלות שמבטיח בטיחות בזיכרון ובחוט. ב-Rust יש גם תמיכה ב-WebAssembly כתכונה מרכזית, וצוות Rust תרם הרבה כלים מצוינים לסביבת WebAssembly.
אחד מהכלים האלה הוא wasm-pack
, של קבוצת העבודה rustwasm. wasm-pack
ממיר את הקוד שלכם למודול ידידותי לאינטרנט שפועל מתוך האריזה עם חבילות כמו webpack. wasm-pack
הוא כלי נוח מאוד, אבל כרגע הוא פועל רק ב-Rust. הקבוצה שוקלת להוסיף תמיכה בשפות אחרות שמטרגטות WebAssembly.
ב-Rust, פלחים הם מה שמשתמשים בו במקום מערכי מחרוזות ב-C. וגם כמו ב-C, אנחנו צריכים ליצור פלחים שמשתמשים בכתובות ההתחלה שלנו. הפעולה הזו מנוגדת למודל הבטיחות של הזיכרון ש-Rust אוכפת, ולכן כדי להגיע למטרה שלנו אנחנו צריכים להשתמש במילות המפתח 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 ל-WebAssembly. עם זאת, חשוב לציין שהיא לא תשתמש בכל TypeScript. ב-AssemblyScript נעשה שימוש באותו תחביר כמו ב-TypeScript, אבל הספרייה הרגילה מוחלפת בספרייה משלהם. הספרייה הרגילה שלהם מייצגת את היכולות של WebAssembly. המשמעות היא שאי אפשר פשוט לקמפל כל קובץ TypeScript שנמצא ברשותכם ל-WebAssembly, אבל כן המשמעות היא שאתם לא צריכים ללמוד שפת תכנות חדשה כדי לכתוב ב-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 בייטים וללא קוד דבק. המודול פועל רק עם ממשקי ה-API הרגילים של WebAssembly.
WebAssembly Forensics
קובץ ה-Rust בגודל 7.6KB גדול באופן מפתיע בהשוואה לשתי השפות האחרות. יש כמה כלים בסביבת WebAssembly שיכולים לעזור לכם לנתח את קובצי WebAssembly (ללא קשר לשפה שבה הם נוצרו), לומר לכם מה קורה ולעזור לכם לשפר את המצב.
Twiggy
Twiggy הוא כלי נוסף מצוות WebAssembly של Rust, שמאפשר לחלץ מידע מועיל ממודול WebAssembly. הכלי לא ספציפי ל-Rust ומאפשר לבדוק דברים כמו תרשים הקריאות של המודול, לקבוע אילו קטעים לא בשימוש או מיותרים ולגלות אילו קטעים תורמים לגודל הקובץ הכולל של המודול. אפשר לעשות זאת באמצעות הפקודה top
של Twiggy:
$ twiggy top rotate_bg.wasm
במקרה הזה, אפשר לראות שרוב גודל הקובץ נובע מהמקצה. זה היה מפתיע כי בקוד שלנו לא נעשה שימוש בהקצאות דינמיות. גורם נוסף שתורם לכך הוא הקטע 'שמות פונקציות'.
wasm-strip
wasm-strip
הוא כלי מ-WebAssembly Binary Toolkit, או wabt בקיצור. הוא מכיל כמה כלים שמאפשרים לבדוק ולבצע פעולות על מודולים של WebAssembly.
wasm2wat
הוא מנתח קוד שממיר מודול wasm בינארי לפורמט קריא לבני אדם. Wabt מכיל גם את wat2wasm
, שמאפשר להפוך את הפורמט הקריא לאנשים חזרה למודול בינארי של wasm. אמנם השתמשנו בשני הכלים המשולבים האלה כדי לבדוק את קובצי WebAssembly שלנו, אבל גילינו ש-wasm-strip
הוא הכלי הכי שימושי. הפונקציה wasm-strip
מסירה קטעים ומטא-נתונים מיותרים ממודול WebAssembly:
$ wasm-strip rotate_bg.wasm
הפעולה הזו מקטינה את גודל הקובץ של מודול Rust מ-7.5KB ל-6.6KB (אחרי gzip).
wasm-opt
wasm-opt
הוא כלי מ-Binaryen.
הכלי מקבל מודול WebAssembly ומנסה לבצע אופטימיזציה שלו גם לגבי גודל וגם לגבי ביצועים, על סמך קוד הבייט בלבד. כלים מסוימים, כמו 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 הוא פורמט של קוד בייט ברמה נמוכה, עדיין צריך לשלוח אותו דרך מהדר כדי ליצור קוד מכונה ספציפי למארח. בדומה ל-JavaScript, גם המהדר פועל בכמה שלבים. במילים פשוטות: השלב הראשון מהיר יותר בתהליך הידור הקוד, אבל הוא נוטה ליצור קוד איטי יותר. אחרי שהמודול מתחיל לפעול, הדפדפן בודק אילו חלקים נמצאים בשימוש תדיר ושולח אותם דרך מהדר שמבצע אופטימיזציה טובה יותר אבל איטי יותר.
התרחיש לדוגמה שלנו מעניין מכיוון שהקוד לסיבוב תמונה ישמש פעם אחת, אולי פעמיים. לכן, ברוב המקרים לא נוכל ליהנות מהיתרונות של המהדרר עם האופטימיזציה. חשוב לזכור את זה כשמבצעים השוואה למדד ביצועים. הפעלה של המודולים של WebAssembly 10,000 פעמים בלול תגרום לתוצאות לא ריאליסטיות. כדי לקבל מספרים ריאליסטיים, צריך להריץ את המודול פעם אחת ולקבל החלטות על סמך המספרים מההפעלה היחידה הזו.
השוואת ביצועים
שני הגרפים האלה הם תצוגות שונות של אותם נתונים. בתרשים הראשון אנחנו מבצעים השוואה לפי דפדפן, ובתרשים השני אנחנו מבצעים השוואה לפי שפה שבה נעשה שימוש. חשוב לציין שבחרתי בסולם זמן לוגריתמי. חשוב גם לציין שבכל הבדיקות השתמשו באותה תמונה לבדיקה של 16 מגה-פיקסלים ובאותה מכונת מארח, מלבד דפדפן אחד שלא ניתן היה להריץ באותה מכונה.
בלי לנתח יותר מדי את התרשימים האלה, ברור שהצלחנו לפתור את בעיית הביצועים המקורית: כל המודולים של WebAssembly פועלים תוך כ-500 אלפיות השנייה או פחות. התוצאות האלה מאשרות את מה שציינו בהתחלה: WebAssembly מספק ביצועים חזויים. לא משנה באיזו שפה נבחר, ההבדלים בין הדפדפנים והשפות הם מינימליים. ליתר דיוק: סטיית התקן של JavaScript בכל הדפדפנים היא כ-400 אלפיות השנייה, ואילו סטיית התקן של כל המודולים שלנו ל-WebAssembly בכל הדפדפנים היא כ-80 אלפיות השנייה.
השקעת מאמץ
מדד נוסף הוא כמות המאמץ שנדרשה כדי ליצור את המודול של WebAssembly ולשלב אותו ב-squoosh. קשה להקצות ערך מספרי למאמץ, לכן לא אצור גרפים, אבל יש כמה דברים שחשוב לציין:
העבודה עם AssemblyScript הייתה חלקה. הוא מאפשר להשתמש ב-TypeScript כדי לכתוב WebAssembly, וכך להקל על הקולגות שלי לבדוק את הקוד, אבל הוא גם יוצר מודולים של WebAssembly ללא דבק, שהם קטנים מאוד עם ביצועים סבירים. סביר להניח שהכלים בסביבת TypeScript, כמו prettier ו-tslint, יפעלו כרגיל.
Rust בשילוב עם wasm-pack
הוא גם נוח מאוד, אבל מתאים יותר לפרויקטים גדולים יותר של WebAssembly שבהם נדרשים קישורים וניהול זיכרון. כדי להגיע לגודל קובץ תחרותי, היינו צריכים לסטות קצת מהדרך הרגילה.
באמצעות C ו-Emscripten, נוצר מודול WebAssembly קטן מאוד עם ביצועים גבוהים מאוד, אבל בלי האומץ לקפוץ לקוד הדבקה ולצמצם אותו לצרכים הבסיסיים, הגודל הכולל (מודול WebAssembly + קוד הדבקה) מסתיים בגודל גדול למדי.
סיכום
אז באיזו שפה כדאי להשתמש אם יש לכם נתיב חם ב-JS ואתם רוצים שהוא יהיה מהיר יותר או עקבי יותר עם WebAssembly? כמו תמיד בשאלות שקשורות לביצועים, התשובה היא: זה תלוי. אז מה השקנו?
בהשוואה בין השפות השונות שבהן השתמשנו מבחינת הגודל של המודול לעומת הביצועים, נראה שהבחירה הטובה ביותר היא C או AssemblyScript. החלטנו לשלוח את Rust. יש כמה סיבות להחלטה הזו: כל הקודקים ששולחים ב-Squoosh עד כה עוברים הידור באמצעות Emscripten. רצינו להרחיב את הידע שלנו על הסביבה של WebAssembly ולהשתמש בשפה אחרת בסביבת הייצור. AssemblyScript היא חלופה טובה, אבל הפרויקט צעיר יחסית והמְהַדר לא מפותח כמו המהדר של Rust.
ההבדל בגודל הקובץ בין Rust לשפות האחרות נראה דרסטי מאוד בתרשים הפזור, אבל הוא לא כזה גדול במציאות: טעינת 500B או 1.6KB, גם מעל 2GB, נמשכת פחות מעשירית שנייה. ואנחנו מקווים ש-Rust יסגור את הפער בגודל המודול בקרוב.
מבחינת ביצועים בסביבת זמן ריצה, ל-Rust יש זמן ריצה ממוצע מהיר יותר בדפדפנים מאשר ל-AssemblyScript. במיוחד בפרויקטים גדולים יותר, סביר יותר ש-Rust ייצור קוד מהיר יותר בלי צורך באופטימיזציה ידנית של הקוד. עם זאת, זה לא צריך למנוע מכם להשתמש בכלי שמתאים לכם ביותר.
עם זאת, AssemblyScript היה גילוי נהדר. היא מאפשרת למפתחי אתרים ליצור מודולים של WebAssembly בלי ללמוד שפה חדשה. הצוות של AssemblyScript היה זמין מאוד ועבד באופן פעיל על שיפור כלי הפיתוח שלו. אנחנו בהחלט נמשיך לעקוב אחרי AssemblyScript בעתיד.
עדכון: Rust
אחרי פרסום המאמר הזה, ניק פיצ'ג'רלד (Nick Fitzgerald) מצוות Rust הפנה אותנו לספר המעולה שלהם בנושא Rust Wasm, שכולל קטע בנושא אופטימיזציה של גודל הקובץ. בעקבות ההוראות שם (במיוחד הפעלת אופטימיזציות בזמן קישור וטיפול ידני בסטרס) הצלחנו לכתוב קוד Rust "רגיל" ולחזור להשתמש ב-Cargo
(ה-npm
של Rust) בלי להגדיל את גודל הקובץ. אחרי ה-gzip, מודול Rust מסתכם ב-370B. לפרטים נוספים, אפשר לעיין בבקשת התיקון (PR) שפתחתי ב-Squoosh.
תודה מיוחדת ל-Ashley Williams, ל-Steve Klabnik, ל-Nick Fitzgerald ול-Max Graey על כל העזרה במסע הזה.