יש שני סוגים של שפות תכנות: שפות תכנות שנאספות לאשפה ושפות תכנות שמחייבות ניהול זיכרון ידני. דוגמאות לשיטות אחרות הן Kotlin , PHP או Java. דוגמאות לפורמט הזה: C , C++ או חלודה. באופן כללי, בשפות תכנות ברמה גבוהה יותר יש סיכוי גבוה יותר שאיסוף אשפה הוא תכונה סטנדרטית. בפוסט הזה בבלוג, אנחנו מתמקדים בשפות תכנות שאוספות אשפה ואיך אפשר להדר אותן אל WebAssembly (Wasm). אבל איך מתחילים? מהו איסוף אשפה (שנקרא בדרך כלל GC)?
תמיכה בדפדפן
איסוף אשפה
במילים פשוטות, הרעיון של איסוף אשפה הוא הניסיון לשחזר את הזיכרון שהוקצה על ידי התוכנה, אבל כבר אין התייחסות אליו. זיכרון כזה נקרא אשפה. יש הרבה אסטרטגיות להטמעת פינוי אשפה. אחת מהן היא ספירת קובצי עזר, כאשר המטרה היא לספור את מספר ההפניות לאובייקטים בזיכרון. כשאין יותר הפניות לאובייקט, אפשר לסמן אותו כעצם שלא נמצא בשימוש ולכן הוא מוכן לאיסוף אשפה. אוסף האשפה של PHP משתמש בספירת הפניות, ושימוש בפונקציה xdebug_debug_zval()
של תוסף Xdebug מאפשר להציץ מתחת למכסה הקדמי. כדאי להשתמש בתוכנית PHP הבאה.
<?php
$a= (string) rand();
$c = $b = $a;
$b = 42;
unset($c);
$a = null;
?>
התוכנה מקצה מספר אקראי שמועבר למחרוזת למשתנה חדש בשם a
. לאחר מכן המערכת יוצרת שני משתנים חדשים, b
ו-c
, ומקצה להם את הערך a
. לאחר מכן, היא תקצה מחדש את b
למספר 42
, ואז תבטל את ההגדרה של c
. לבסוף, הוא מגדיר את הערך של a
כ-null
. כשמוסיפים הערות לכל שלב בתוכנית עם xdebug_debug_zval()
, אפשר לראות את דלפק האשפה בעבודה.
<?php
$a= (string) rand();
$c = $b = $a;
xdebug_debug_zval('a');
$b = 42;
xdebug_debug_zval('a');
unset($c);
xdebug_debug_zval('a');
$a = null;
xdebug_debug_zval('a');
?>
הדוגמה שלמעלה תפיק את הפלט של היומנים הבאים, שבהם תוכלו לראות איך מספר ההפניות לערך של המשתנה a
יורד אחרי כל שלב, וזה הגיוני בהינתן רצף הקוד. (המספר האקראי יהיה שונה כמובן).
a:
(refcount=3, is_ref=0)string '419796578' (length=9)
a:
(refcount=2, is_ref=0)string '419796578' (length=9)
a:
(refcount=1, is_ref=0)string '419796578' (length=9)
a:
(refcount=0, is_ref=0)null
יש עוד אתגרים שקשורים לאיסוף אשפה, כמו זיהוי מחזורים, אבל במאמר הזה מספיק הבנה בסיסית של ספירת ההפניות.
שפות התכנות מוטמעות בשפות תכנות אחרות
זה אמנם נראה כמו התחלה, אבל שפות תכנות מוטמעות בשפות תכנות אחרות. לדוגמה, זמן הריצה של PHP מוטמע בעיקר ב-C. אפשר לבדוק את קוד המקור של PHP ב-GitHub. קוד איסוף האשפה של PHP נמצא בעיקר בקובץ zend_gc.c
. רוב המפתחים יתקינו את PHP דרך מנהל החבילות של מערכת ההפעלה שלהם. עם זאת, מפתחים יכולים גם לבנות PHP מקוד המקור. לדוגמה, בסביבת Linux, השלבים של ./buildconf && ./configure && make
ייצרו PHP לסביבת זמן הריצה של Linux. אבל זה גם אומר שאפשר להדר את סביבת זמן הריצה של PHP לסביבות של זמני ריצה אחרים, כמו Wasm, כפי ניחש.
שיטות מסורתיות לניוד שפות אל סביבת זמן הריצה של Wasm
בנפרד מהפלטפורמה שבה מריצים PHP, הסקריפטים של PHP עוברים לאותו קוד בייט אחד ופועלים על ידי Zend Engine. Zend Engine הוא מהדר וסביבת זמן הריצה של שפת כתיבת הסקריפטים PHP. הוא מורכב מהמכונה הווירטואלית של Zend, שמורכבת מה-Zend Compiler ומ-Zend Executor. שפות כמו PHP שמוטמעות בשפות אחרות ברמה גבוהה כמו C בדרך כלל כוללות אופטימיזציות שמטרגטות ארכיטקטורות ספציפיות, כמו Intel או ARM, ומחייבות קצה עורפי שונה בכל ארכיטקטורה. בהקשר הזה, Wasm מייצגת ארכיטקטורה חדשה. אם למכונה הווירטואלית יש קוד ספציפי לארכיטקטורה, כמו 'בדיוק בזמן' (JIT) או הידור מראש (AOT), המפתח מטמיע גם קצה עורפי עבור JIT/AOT לארכיטקטורה החדשה. הגישה הזו הגיונית, כי לעיתים קרובות אפשר פשוט ליצור מחדש את החלק העיקרי של ה-codebase לכל ארכיטקטורה חדשה.
בגלל שהרמה הנמוכה של Wasm, טבעי לנסות שם את אותה גישה: יש להדר מחדש את קוד ה-VM הראשי באמצעות המנתח, התמיכה בספרייה, איסוף האשפה וכלי האופטימיזציה ל-Wasm, ולהטמיע קצה עורפי JIT או AOT עבור Wasm אם יש צורך. זה היה אפשרי מאז ה-MVP של Wasm, והוא פועל היטב במקרים רבים. למעשה, PHP שהורכב ל-Wasm הוא הגורם שמפעיל את WordPress Playground. מידע נוסף על הפרויקט זמין במאמר יצירת חוויות משתמש ב-WordPress בדפדפן באמצעות WordPress Playground ו-WebAssembly.
עם זאת, PHP Wasm פועלת בדפדפן בהקשר של JavaScript של שפת המארח. ב-Chrome, JavaScript ו-Wasm פועלים ב-V8, מנוע ה-JavaScript בקוד פתוח של Google שמטמיע ECMAScript כפי שצוין ב-ECMA-262. בנוסף, ב-V8 כבר יש אוסף אשפה. המשמעות היא שמפתחים שמשתמשים, למשל, ב-PHP שעבר הידור ל-Wasm, שולחים בסופו של דבר הטמעת אוסף אשפה של השפה שהועברה (PHP) לדפדפן שכבר יש בו קולט אשפה. הפעולה הזאת נראית מבוזבזת כמו שזה נשמע. כאן נכנסים לתמונה WasmGC.
הבעיה הנוספת של הגישה הקודמת, של מתן אפשרות למודולים של Wasm לבנות GC משלהם בנוסף לזיכרון הלינארי של Wasm, היא שאז אין אינטראקציה בין אוסף האשפה של Wasm לבין אוסף האשפה המובנה באפליקציה הזו שעבר הידור (compile-to-Wasm), שבדרך כלל גורם לבעיות כמו דליפות זיכרון וניסיונות לא יעילים של איסוף אשפה. כדי למנוע בעיות כאלה, אפשר לתת למודולים של Wasm לעשות שימוש חוזר ב-GC המובנה הקיים.
העברה של שפות תכנות לזמני ריצה חדשים באמצעות WasmGC
WasmGC היא הצעה של קבוצת הקהילה WebAssembly. ההטמעה הנוכחית של Wasm MVP יכולה לטפל רק במספרים, כלומר במספרים שלמים ובמספרים צפים, בזיכרון ליניארי, וכשנשלחת הצעה לסוגי ההפניות, Wasm יכולה גם לשמור קובצי עזר חיצוניים. מעכשיו, ב-WasmGC נוספו סוגים של ערימה (heap) ומבניים (struct), כלומר תמיכה בהקצאת זיכרון לא ליניארית. לכל אובייקט WasmGC יש סוג ומבנה קבועים, שמאפשרים למכונות וירטואליות ליצור בקלות קוד יעיל כדי לגשת לשדות שלהן, בלי הסיכון לביטול האופטימיזציה של שפות דינמיות כמו JavaScript. ההצעה הזו מוסיפה ל-WebAssembly תמיכה יעילה בשפות מנוהלות ברמה גבוהה, באמצעות סוגי ערימה של מבני ומערכים, שמאפשרים מהדרים של שפות שמטרגטים ל-Wasm להשתלב עם אוסף אשפה במכונה הווירטואלית המארחת. במילים פשוטות, המשמעות היא שעבור WasmGC, ניוד של שפת תכנות אל Wasm כבר לא צריך להיות חלק מהנמל, אך במקום זאת ניתן להשתמש בטלקן האשפה הקיים.
כדי לאמת את ההשפעה בעולם האמיתי של שיפור זה, צוות Wasm של Chrome יצר גרסאות של בנצ'מרק של Fannkuch (שמקצה מבני נתונים) מ-C, Rust ו-Java. הקבצים הבינאריים C ו-Rust יכולים להיות בין 6.1 K ל- 9.6 K בהתאם בדגלים השונים של המהדר (compiler), ואילו גרסת Java קטנה בהרבה ב- 2.3 K בלבד! C ו-Rust לא כוללים אוסף אשפה, אבל הם עדיין כוללים את malloc/free
לניהול הזיכרון, והסיבה לכך ש-Java קטנה יותר כאן היא שאין צורך לצרף בכלל קוד לניהול זיכרון. זו רק דוגמה אחת ספציפית, אבל היא מראה שלקבצים בינאריים של WasmGC יש פוטנציאל להיות קטן מאוד, וזה עוד לפני שנעשתה עבודה משמעותית באופטימיזציה של גודל.
לראות בפעולה את שפת התכנות שמבוססת על WasmGC
קוטלין וואסם
אחת משפות התכנות הראשונות שהועברו אל Wasm הודות ל-WasmGC היא Kotlin בפורמט Kotlin/Wasm. ההדגמה, עם קוד המקור באדיבות צוות Kotlin, מוצגת בדף האפליקציה הבא.
import kotlinx.browser.document
import kotlinx.dom.appendText
import org.w3c.dom.HTMLDivElement
fun main() {
(document.getElementById("warning") as HTMLDivElement).style.display = "none"
document.body?.appendText("Hello, ${greet()}!")
}
fun greet() = "world"
יכול להיות שתהיתם מה המטרה, כי קוד Kotlin שלמעלה מכיל בעיקרו ממשקי API של JavaScript OM שהומרו ל-Kotlin. המודל הזה מתחיל לפעול בצורה הגיונית יותר בשילוב עם התכונה Compose Multiplatform, שמאפשרת למפתחים להתבסס על ממשק המשתמש שכבר יצרו לאפליקציות שלהם ל-Android Kotlin. אתם מוזמנים להתנסות בחקירה מוקדמת של התהליך באמצעות ההדגמה של Kotlin/Wasm image Viewer ועיון בקוד המקור שלו, גם באדיבות צוות Kotlin.
קליעה למטרה ורופפים
צוותי Dart ו-Flutter ב-Google מכינים גם תמיכה ל-WasmGC. עבודת האיסוף של Drt-to-Wasm כמעט הושלמה, והצוות עובד על תמיכה בכלים כדי לספק אפליקציות אינטרנט של Flutter שנאספו ל-WebAssembly. אתם יכולים לקרוא על מצב העבודה הנוכחי במסמכי התיעוד בנושא Flutter. ההדגמה הבאה היא התצוגה המקדימה שלFlutter WasmGC.
מידע נוסף על WasmGC
הפוסט בבלוג הזה כמעט לא ניצל את כל מה שנוגע ל-WasmGC, ובעיקרון סיפק סקירה כללית ברמה גבוהה על WasmGC. מידע נוסף על התכונה זמין בקישורים הבאים:
- דרך חדשה להעביר באופן יעיל את שפות תכנות שנאספו באמצעות אשפה ל-WebAssembly
- סקירה כללית של WasmGC
- MVP של WasmGC
- WasmGC אחרי ה-MVP
אישורים
התמונה הראשית (Hero) של גרי צ'אן בסרטון Unbounce. המאמר הזה נבדק על ידי מתיאס לידטקה, אדם קליין, ג'ושוע בל, אלון זקאי, Jakob Kummerow, קלמנס גבס, עמנואל זיגלר וגם רייצ'ל אנדרו