איסוף האשפה ב-WebAssembly (WasmGC) מופעל עכשיו כברירת מחדל ב-Chrome

יש שני סוגים של שפות תכנות: שפות תכנות עם איסוף אשפה ושפות תכנות שדורשות ניהול ידני של זיכרון. דוגמאות לקודים מהסוג הראשון, בין היתר, הן Kotlin,‏ PHP או Java. דוגמאות לקודים כאלה הן C,‏ C++ או Rust. ככלל, בשפות תכנות ברמה גבוהה יותר יש סיכוי גבוה יותר לכך שאיסוף אשפה יהיה תכונה סטנדרטית. בפוסט הזה נסביר על שפות תכנות כאלה עם איסוף אשפה, ונראה איך אפשר לקמפל אותן ל-WebAssembly‏ (Wasm). אבל מהי בכלל איסוף אשפה (שנקרא בדרך כלל GC)?

תמיכה בדפדפנים

  • Chrome: ‏ 119.
  • Edge: ‏ 119.
  • Firefox: ‏ 120.
  • Safari: לא נתמך.

איסוף אשפה

במילים פשוטות, הרעיון של איסוף אשפה הוא ניסיון לנצל מחדש זיכרון שהוקצה על ידי התוכנית, אבל כבר אין אליו הפניה. זיכרון כזה נקרא ''. יש הרבה שיטות להטמעת איסוף אשפה. אחת מהן היא ספירת הפניות, שבה המטרה היא לספור את מספר הפניות לאובייקטים בזיכרון. כשאין יותר הפניות לאובייקט, אפשר לסמן אותו כאובייקט שלא בשימוש, וכך הוא מוכן לאיסוף אשפה. מנקה האשפה של 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 דרך מנהל החבילות של מערכת ההפעלה שלהם. אבל מפתחים יכולים גם ליצור גרסת build של PHP מקוד המקור. לדוגמה, בסביבת Linux, השלבים ./buildconf && ./configure && make יוצרים את PHP לסביבת זמן הריצה של Linux. אבל המשמעות היא גם שאפשר לקמפל את סביבת זמן הריצה של PHP לסביבות זמן ריצה אחרות, כמו Wasm.

שיטות מסורתיות להעברת שפות לסביבת זמן הריצה של Wasm

ללא קשר לפלטפורמה שבה פועלת PHP, סקריפטים של PHP מקובצים לאותו קוד בייט ומופעל על ידי Zend Engine. Zend Engine הוא מהדר וסביבת זמן ריצה של שפת הסקריפטים PHP. הוא מורכב מ-Zend Virtual Machine‏ (VM), שמורכב מ-Zend Compiler ומ-Zend Executor. שפות כמו PHP שמוטמעות בשפות אחרות ברמה גבוהה כמו C, כוללות בדרך כלל אופטימיזציות שמטרגטות ארכיטקטורות ספציפיות, כמו Intel או ARM, ודורשות קצה עורפי שונה לכל ארכיטקטורה. בהקשר הזה, Wasm מייצג ארכיטקטורה חדשה. אם ב-VM יש קוד ספציפי לארכיטקטורה, כמו הידור בזמן אמת (JIT) או הידור מראש (AOT), המפתחים צריכים להטמיע גם קצה עורפי ל-JIT/AOT לארכיטקטורה החדשה. הגישה הזו הגיונית מאוד, כי לרוב אפשר פשוט לבצע הידור מחדש של החלק העיקרי של קוד הבסיס לכל ארכיטקטורה חדשה.

מכיוון ש-Wasm הוא ברמה נמוכה, טבעי לנסות אותה גישה גם בו: קומפילציה מחדש של קוד ה-VM הראשי עם המנתח, תמיכת הספרייה, איסוף האשפה והאופטימיזטור שלו ל-Wasm, והטמעת קצה עורפי של JIT או AOT ל-Wasm לפי הצורך. אפשר לעשות זאת מאז השקת Wasm MVP, ובמקרים רבים זה עובד טוב בפועל. למעשה, 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 ליצור מנקה גרוטאות משלהם מעל הזיכרון הליניארי של Wasm, היא שאין אינטראקציה בין מנקה האשפה של Wasm לבין מנקה האשפה שנוצר מעל השפה שעבר תהליך הידור ל-Wasm. כתוצאה מכך, יש בעיות כמו דליפות זיכרון ונסיונות איסוף לא יעילים. כדי למנוע את הבעיות האלה, אפשר לאפשר למודולים של Wasm לעשות שימוש חוזר ב-GC המובנה הקיים.

העברת שפות תכנות לסביבות זמן ריצה חדשות באמצעות WasmGC

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

כדי לוודא את ההשפעה של השיפור הזה בעולם האמיתי, צוות Wasm של Chrome יצר גרסאות של מדד הביצועים של Fannkuch (שמקצה מבני נתונים תוך כדי עבודה) מ-C, מ-Rust ומ-Java. קובצי ה-binary של C ו-Rust יכולים לנוע בין 6.1K ל-9.6K, בהתאם לדגלים השונים של המהדר, בעוד שגרסת Java קטנה בהרבה – רק 2.3K! C ו-Rust אינם כוללים אספן אשפה, אבל הם עדיין מאגדים malloc/free לניהול זיכרון, והסיבה ש-Java קטנה יותר כאן היא כי אין צורך לאגד קוד ניהול זיכרון כלל. זו רק דוגמה ספציפית אחת, אבל היא מראה שיש פוטנציאל לקובצי בינאריים של WasmGC להיות קטנים מאוד, וזאת עוד לפני שנעשה עבודה משמעותית לאופטימיזציה של הגודל.

הצגת שפת תכנות שהועברה ל-WasmGC בפעולה

Kotlin Wasm

אחת משפות התכנות הראשונות שהועברו ל-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 של OM ב-JavaScript שהומרו ל-Kotlin. השילוב עם Compose Multiplatform מתחיל להיראות הגיוני יותר, כי הוא מאפשר למפתחים להמשיך לפתח את ממשק המשתמש שכבר יצרו לאפליקציות שלהם ל-Android ב-Kotlin. תוכלו לבדוק את הנושא הזה באמצעות הדמו של הצגת תמונות ב-Kotlin/Wasm ולעיין בקוד המקור שלו, גם כן באדיבות צוות Kotlin.

Dart ו-Flutter

גם הצוותים של Dart ו-Flutter ב-Google מתכוננים להוסיף תמיכה ב-WasmGC. העבודה על הידור Dart ל-Wasm כמעט הושלמה, והצוות עובד על תמיכה בכלים להעברת אפליקציות אינטרנט של Flutter שעובדות ב-WebAssembly. במסמכי התיעוד של Flutter אפשר לקרוא על הסטטוס הנוכחי של העבודה. הדגמה הבאה היא התצוגה המקדימה של Flutter WasmGC.

מידע נוסף על WasmGC

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

תודות

הבדיקה של המאמר בוצעה על ידי Matthias Liedtke,‏ Adam Klein,‏ Joshua Bell,‏ Alon Zakai,‏ Jakob Kummerow,‏ Clemens Backes,‏ Emanuel Ziegler ו-Rachel Andrew.