תאריך פרסום: 30 בינואר 2025
אפליקציות WebAssembly רבות באינטרנט נהנות מריבוי משימות, באותו אופן שבו אפליקציות נייטיב נהנות מכך. שימוש בכמה שרשורים מאפשר לבצע יותר משימות במקביל, ולהעביר משימות כבדות מהשרשור הראשי כדי למנוע בעיות של זמן אחזור. עד לאחרונה היו כמה נקודות כאב נפוצות שיכולות לקרות באפליקציות כאלה עם מספר רב של שרשורים, שקשורות להקצאות ול-I/O. למזלנו, התכונות האחרונות ב-Emscripten יכולות לעזור מאוד בבעיות האלה. במדריך הזה נסביר איך התכונות האלה יכולות להוביל לשיפורים של פי 10 או יותר במהירות, במקרים מסוימים.
שינוי קנה מידה
בתרשים הבא מוצגת התאמה יעילה לעומס עבודה מתמטי טהור במספר רב של שרשורים (מבדיקת הביצועים שבה נשתמש במאמר הזה):
המדד הזה מודד חישובים טהורים, שכל ליבה של המעבד יכולה לבצע בעצמה, ולכן הביצועים משתפרים ככל שיש יותר ליבות. קו יורד כזה של ביצועים מהירים יותר הוא בדיוק מה שאפשר לצפות לו כשהמערכת מתכווננת לעומס. בנוסף, הבדיקה מראה שפלטפורמת האינטרנט יכולה להריץ קוד מקורי מרובה-ליבות בצורה טובה מאוד, למרות השימוש ב-web workers כבסיס לעבודה במקביל, השימוש ב-Wasm במקום בקוד מקורי אמיתי ופרטים אחרים שעשויים להיראות פחות אופטימלי.
ניהול אשכול: malloc
/free
malloc
ו-free
הן פונקציות ספרייה רגילות חיוניות בכל השפות עם זיכרון לינאריים (לדוגמה, C, C++, Rust ו-Zig), שמשמשות לניהול כל הזיכרון שאינו סטטי לחלוטין או שנמצא בסטאק. ברירת המחדל של Emscripten היא dlmalloc
, שהיא הטמעה קומפקטית אך יעילה (היא תומכת גם ב-emmalloc
, שהיא קומפקטית עוד יותר אבל איטית יותר במקרים מסוימים). עם זאת, הביצועים של dlmalloc
במספר שרשורים מוגבלים כי הוא נועל כל malloc
/free
(כי יש מקצה גלובלי יחיד). לכן, אם יש הרבה הקצאות בכמה חוטים בו-זמנית, יכול להיות שתבחינו במאבק על המשאבים ובאיטיות. זה מה שקורה כשמריצים בדיקת ביצועים שמשתמשת ב-malloc
באופן אינטנסיבי:
לא רק שהביצועים לא משתפרים עם יותר ליבות, אלא שהם הולכים ופוחתים, כי כל שרשור ממתין פרקי זמן ארוכים לנעילה של malloc
. זהו התרחיש הגרוע ביותר האפשרי, אבל הוא יכול לקרות בעומסי עבודה אמיתיים אם יש מספיק הקצאות.
mimalloc
יש גרסאות של dlmalloc
שמותאמות לשימוש במספר שרשורים, כמו ptmalloc3
, שמטמיעה מופע נפרד של מנהל הקצאות לכל שרשור, כדי למנוע מצב של תחרות.
יש עוד כמה מנהלי אחסון עם אופטימיזציות למספר רב של שרשורים, כמו jemalloc
ו-tcmalloc
. ב-Emscripten החליטו להתמקד בפרויקט mimalloc
שהושקה לאחרונה, שהוא מנהל זיכרון מעוצב היטב של Microsoft עם ניידות וביצועים טובים מאוד. משתמשים בו באופן הבא:
emcc -sMALLOC=mimalloc
אלה התוצאות של נקודת השוואה malloc
באמצעות mimalloc
:
יופי! עכשיו הביצועים משתפרים בצורה יעילה, והם הולכים ומשתפרים עם כל ליבה.
אם תבחנו היטב את הנתונים של הביצועים של הליבה היחידה בשני התרשימים האחרונים, תראו של-dlmalloc
נדרשו 2,660 אלפיות שנייה ול-mimalloc
רק 1,466 אלפיות שנייה, כלומר שיפור של כמעט פי 2. הנתונים האלה מראים שגם באפליקציה עם ליבה אחת אפשר ליהנות מהאופטימיזציות המתוחכמות יותר של mimalloc
, אבל חשוב לזכור שהן מגיעות עם עלות של גודל הקוד ושימוש בזיכרון (לכן dlmalloc
נשאר ברירת המחדל).
קבצים וקלט/פלט
לאפליקציות רבות יש צורך להשתמש בקבצים מסיבות שונות. לדוגמה, כדי לטעון רמות במשחק או גופנים בכלי לעריכת תמונות. גם פעולה כמו printf
משתמשת במערכת הקבצים מתחת לפני השטח, כי היא מדפיסה על ידי כתיבת נתונים ב-stdout
.
באפליקציות עם ליבה יחידה, בדרך כלל אין בעיה כזו, ו-Emscripten ימנע באופן אוטומטי קישור לתמיכה מלאה במערכת קבצים אם כל מה שדרוש לכם הוא printf
. עם זאת, אם אתם משתמשים בקבצים, הגישה למערכת קבצים עם מספר רב של שרשורים היא מורכבת, כי צריך לסנכרן את הגישה לקבצים בין השרשורים. ההטמעה המקורית של מערכת הקבצים ב-Emscripten, שנקראת 'JS FS' כי היא הוטמעה ב-JavaScript, השתמשה במודל הפשוט של הטמעת מערכת הקבצים רק בשרשור הראשי. בכל פעם ששרשור אחר רוצה לגשת לקובץ, הוא מעביר את הבקשה לשרשור הראשי. כלומר, ה-thread השני נחסם על בקשה חוצת-שרשורים, שה-thread הראשי מטפל בה בסופו של דבר.
המודל הפשוט הזה הוא אופטימלי אם רק לשרשור הראשי יש גישה לקבצים, וזה דפוס נפוץ. עם זאת, אם תהליכים אחרים מבצעים פעולות קריאה וכתיבה, יכולות להתרחש בעיות. ראשית, ה-thread הראשי מסיים לבצע עבודה עבור threads אחרים, וכתוצאה מכך נוצרת זמן אחזור גלוי למשתמש. לאחר מכן, שרשראות הרקע ממתינות ל-thread הראשי שיהיה פנוי כדי לבצע את העבודה הנדרשת, כך שהדברים נעשים איטיים יותר (או גרוע מכך, יכול להיות שתגיעו למצב 'ללא מוצא' אם ה-thread הראשי ממתין כרגע ל-thread העובד הזה).
WasmFS
כדי לפתור את הבעיה, ל-Emscripten יש הטמעה חדשה של מערכת קבצים, WasmFS. מערכת הקבצים WasmFS נכתבה ב-C++ ועברתה הידור ל-Wasm, בניגוד למערכת הקבצים המקורית שנכתבה ב-JavaScript. WasmFS תומכת בגישה למערכת קבצים מכמה שרשורים עם עלות מינימלית, על ידי שמירת הקבצים בזיכרון ליניארי של Wasm, שמשותף בין כל השרשורים. עכשיו כל השרשור יכולים לבצע פעולות קלט/פלט של קבצים עם ביצועים זהים, ולעיתים קרובות הם יכולים גם להימנע מחסימה הדדית.
בדיקת ביצועים פשוטה של מערכת קבצים מראה את היתרון העצום של WasmFS בהשוואה ל-JS FS הישן.
כאן מוצגת השוואה בין הפעלת קוד של מערכת קבצים ישירות ב-thread הראשי לבין הפעלתו ב-pthread יחיד. ב-JS FS הישן, כל פעולה במערכת הקבצים חייבת לעבור דרך שרת proxy לשרשור הראשי, מה שמאט את הפעולה פי עשרה ב-pthread! הסיבה לכך היא שבמקום רק לקרוא או לכתוב כמה בייטים, מערכת הקבצים של JS מבצעת תקשורת בין חוטים, שכוללת מנעולים, תור ואת תהליך ההמתנה. לעומת זאת, ל-WasmFS יש גישה לקבצים מכל חוט באופן שווה, ולכן בתרשים מוצג שאין כמעט הבדל בין החוט הראשי לבין pthread. כתוצאה מכך, WasmFS מהיר פי 32 מ-JS FS כשמשתמשים ב-pthread.
שימו לב שיש גם הבדל בשרשור הראשי, שבו WasmFS מהיר פי 2. הסיבה לכך היא ש-JS FS מבצע קריאה ל-JavaScript בכל פעולה במערכת הקבצים, ו-WasmFS נמנעת מכך. ב-WasmFS נעשה שימוש ב-JavaScript רק במקרים שבהם הוא נחוץ (לדוגמה, כדי להשתמש ב-Web API), כך שרוב קובצי WasmFS נשארים ב-Wasm. בנוסף, גם כשנדרשת JavaScript, WasmFS יכול להשתמש בשרשור עזר במקום בשרשור הראשי, כדי למנוע זמן אחזור גלוי למשתמשים. לכן, יכול להיות שתבחינו בשיפורים במהירות כשמשתמשים ב-WasmFS גם אם האפליקציה שלכם לא מבוססת על מספר חוטים (או אם היא מבוססת על מספר חוטים אבל משתמשת בקובצים רק בחוט הראשי).
משתמשים ב-WasmFS באופן הבא:
emcc -sWASMFS
מערכת WasmFS משמשת בסביבת הייצור ונחשבת ליציבה, אבל היא עדיין לא תומכת בכל התכונות של מערכת הקבצים הקודמת של JS. לעומת זאת, היא כוללת כמה תכונות חדשות חשובות, כמו תמיכה במערכת הקבצים הפרטית של המקור (OPFS, מומלץ מאוד לאחסון מתמיד). אלא אם אתם צריכים תכונה שעדיין לא הועברה, צוות Emscripten ממליץ להשתמש ב-WasmFS.
סיכום
אם יש לכם אפליקציה עם כמה שרשורים שמבצעת הרבה הקצאות או משתמשת בקבצים, כדאי לכם להשתמש ב-WasmFS ו/או ב-mimalloc
. קל לנסות את שניהם בפרויקט Emscripten, פשוט צריך לבצע הידור מחדש עם הדגלים שמפורטים בפוסט הזה.
כדאי לנסות את התכונות האלה גם אם אתם לא משתמשים בשרשור: כפי שצוין קודם, הטמעות מודרניות יותר כוללות אופטימיזציות שאפשר להבחין בהן גם בליבה אחת במקרים מסוימים.