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

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

תמיכה בדפדפן

  • 119
  • 119
  • 120
  • x

איסוף אשפה

במילים פשוטות, איסוף אשפה פירושו ניסיון להחזיר לפעולה את הזיכרון שהוקצה על ידי התוכנית, אבל כבר אין התייחסות אליו. זיכרון כזה נקרא אשפה. יש אסטרטגיות רבות ליישום איסוף אשפה. אחת מהן היא ספירת הפניות כאשר המטרה היא לספור את מספר ההפניות לאובייקטים בזיכרון. כשאין יותר הפניות לאובייקט, אפשר לסמן אותו כאובייקט שכבר לא בשימוש ולכן הוא מוכן לאיסוף אשפה. אוסף האשפה של 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 עוברים הידור (bytecode) של אותו קוד ומופעל על ידי Zend Engine. Zend Engine הוא סביבת מהדר וסביבת זמן ריצה עבור שפת הסקריפטים של PHP. היא כוללת את Zend Virtual Machine (VM), שמורכב מה-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 לבין אספן האשפה המובנה של השפה שעברה הידור ל-Wasm, ונוטה לגרום לבעיות כמו דליפות זיכרון וניסיונות איסוף לא יעילים. כדי למנוע בעיות כאלה, אפשר לתת למודולים של Wasm לעשות שימוש חוזר ב-GC המובנה הקיים.

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

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

כדי לבדוק את ההשפעה של השיפור הזה בעולם האמיתי, צוות Wasm של Chrome חיבר גרסאות של המדד Fannkuch (שמקצה מבני נתונים בזמן שהוא עובד) מ-C, מ-Rust ומ-Java. הקבצים הבינאריים C ו-Rust יכולים להיות בכל מקום בין 6.1 K ל-9.6 K, בהתאם לדגלי המהדר השונים, וגרסת Java קטנה בהרבה ב-2.3K בלבד! 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 שלמעלה מורכב בעיקרו מ-JavaScript OM APIs שהומרו ל-Kotlin. השילוב עם Compose Multiplatform מאפשר למפתחים להתבסס על ממשק המשתמש שהם כבר יצרו לאפליקציות ל-Android Kotlin. מומלץ לחקור את הנושא הזה בהדגמה של Kotlin/Wasm image View (מציג תמונות ב-Kotlin/Wasm) ולעיין בקוד המקור שלו, כמו גם באדיבות צוות Kotlin.

קליעה למטרה

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

מידע נוסף על WasmGC

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

אישורים

תמונה ראשית (Hero) מאת Gary Chan באתר UnFlood. המאמר הזה נבדק על ידי מתיאס לידקה, אדם קליין, ג'ושוע בל, אלון זאקאי, Jakob Kummerow, Clemens Backes, עמנואל זיגלר ורייצ'ל אנדרו.