ארכיטקטורה מחוץ לשרשור הראשי יכולה לשפר באופן משמעותי את האמינות של האפליקציה ואת חוויית המשתמש.
ב-20 השנים האחרונות, האינטרנט התפתח באופן דרמטי ממסמכים סטטיים עם כמה סגנונות ותמונות לאפליקציות מורכבות ודינמיות. עם זאת, דבר אחד נשאר כמעט ללא שינוי: יש לנו רק שרשור אחד בכל כרטיסייה בדפדפן (עם כמה יוצאים מן הכלל) לביצוע העבודה של עיבוד האתרים שלנו והפעלת ה-JavaScript.
כתוצאה מכך, ה-thread הראשי עמוס מאוד. ככל שמידת המורכבות של אפליקציות אינטרנט גדלה, ה-thread הראשי הופך לצוואר בקבוק משמעותי בביצועים. כדי להחמיר את העניין, משך הזמן של הרצת קוד ב-thread הראשי של משתמש נתון כמעט בלתי צפוי כי ליכולות המכשיר יש השפעה משמעותית על הביצועים. חוסר החיזוי הזה יגדל רק ככל שמשתמשים ייגשו לאינטרנט מקבוצה מגוונת יותר של מכשירים, החל מטלפונים ניידים פשוטים מאוד ועד למכונות הדגל עם קצב רענון גבוה.
אם אנחנו רוצים שאפליקציות אינטרנט מתוחכמות יעמדו בצורה אמינה בהנחיות הביצועים כמו מדדי הליבה לבדיקת חוויית המשתמש באתר, שמבוססים על נתונים אמפיריים לגבי התפיסה והפסיכולוגיה של בני האדם, אנחנו צריכים דרכים להוציא את הקוד שלנו מהשרשור הראשי (OMT).
למה כדאי לעבוד עם עובדי אינטרנט?
כברירת מחדל, JavaScript היא שפה עם שרשור יחיד שמריצה משימות על ה-thread הראשי. עם זאת, עובדי אינטרנט מספקים סוג של פתח בריחה מה-thread הראשי בכך שהם מאפשרים למפתחים ליצור שרשורים נפרדים כדי לטפל בעבודה מחוץ ל-thread הראשי. היקף עובדי האינטרנט מוגבל ולא מציע גישה ישירה ל-DOM, אבל הם יכולים להועיל במידה רבה אם יש עבודה רבה שצריך לבצע, שאחרת הייתה תציף את ה-thread הראשי.
כשמדובר במדדי הליבה לבדיקת חוויית המשתמש באתר, עבודה מחוץ לשרשור הראשי יכולה להועיל. באופן ספציפי, הסרת התוכן מעבודה מה-thread הראשי אל עובדי אינטרנט יכולה לצמצם את התחרות על ה-thread הראשי, וכך לשפר את מדד הרספונסיביות מהירות התגובה לאינטראקציה באתר הבא (INP) של הדף. אם יש פחות עבודה בעיבוד ה-thread הראשי, הוא יכול להגיב מהר יותר לאינטראקציות של המשתמשים.
גם צמצום העבודה על השרשורים, במיוחד במהלך ההפעלה, תורם לערך של המהירות שבה נטען רכיב התוכן הכי גדול (LCP), כי הוא גורם לצמצום משימות ארוכות. עיבוד של רכיב LCP דורש זמן שרשור ראשי – לעיבוד טקסט או לתמונות, שהם רכיבי LCP תכופים לעיתים קרובות – וצמצום הכמות הכוללת של ה-thread הראשי, שמפחיתה את הסבירות שרכיב ה-LCP של הדף ייחסם על-ידי עבודה יקרה שכרוך בעובדי אינטרנט לטפל בה.
שרשור עם עובדי אינטרנט
פלטפורמות אחרות בדרך כלל תומכות בעבודה מקבילה בכך שהן מאפשרות להגדיר לשרשור פונקציה, שפועלת במקביל לשאר התוכנית. תוכלו לגשת לאותם משתנים משני השרשורים, ואתם יכולים לסנכרן את הגישה למשאבים המשותפים האלה עם השתקה וסמפור כדי למנוע מרוץ תהליכים.
ב-JavaScript, אנחנו יכולים לקבל פונקציונליות דומה בערך מעובדי אינטרנט, שפועלים מאז 2007 ונתמכים בכל הדפדפנים העיקריים מאז 2012. עובדי אינטרנט פועלים במקביל ל-thread הראשי, אבל בניגוד לשרשורים במערכת ההפעלה, הם לא יכולים לשתף משתנים.
כדי ליצור קובץ Web worker, מעבירים קובץ ל-constructor של Worker, שמתחיל להריץ את הקובץ בשרשור נפרד:
const worker = new Worker("./worker.js");
אפשר לתקשר עם עובד האינטרנט באמצעות שליחת הודעות באמצעות API של postMessage
. מעבירים את ערך ההודעה כפרמטר בקריאה ל-postMessage
ואז מוסיפים ל-worker מעבד אירוע Message:
main.js
const worker = new Worker('./worker.js');
worker.postMessage([40, 2]);
worker.js
addEventListener('message', event => {
const [a, b] = event.data;
// Do stuff with the message
// ...
});
כדי לשלוח הודעה חזרה לשרשור הראשי, צריך להשתמש באותו API של postMessage
ב-Web Worker ולהגדיר האזנה לאירועים ב-thread הראשי:
main.js
const worker = new Worker('./worker.js');
worker.postMessage([40, 2]);
worker.addEventListener('message', event => {
console.log(event.data);
});
worker.js
addEventListener('message', event => {
const [a, b] = event.data;
// Do stuff with the message
postMessage(a b);
});
אומנם הגישה הזו מוגבלת במידה מסוימת. בעבר, עובדי אינטרנט שימשו בעיקר להעברת פיסת עבודה כבדה אחת מה-thread הראשי. הניסיון לטפל בפעולות מרובות באמצעות עובד אינטרנט יחיד הופך למסורבל במהירות: צריך לקודד לא רק את הפרמטרים אלא גם את הפעולה בהודעה, וצריך לנהל את הספרים כדי להתאים את התשובות לבקשות. סביר להניח שהמורכבות הזו נובעת מכך שעובדי אינטרנט אינם מאמצים את השימוש בהם באופן נרחב יותר.
אבל אם נוכל להסיר חלק מהקשיים בתקשורת בין ה-thread הראשי לבין עובדי אינטרנט, המודל הזה יכול להתאים מאוד לתרחישי שימוש רבים. למזלנו, יש ספרייה שעושה את כל זה!
Comlink: צמצום העבודה של עובדי אינטרנט
Comlink היא ספרייה שהמטרה שלה היא לאפשר לכם להשתמש בעובדי אינטרנט בלי שתצטרכו לחשוב על הפרטים של postMessage
. בעזרת Comlink אפשר לשתף משתנים בין עובדי אינטרנט לבין ה-thread הראשי כמעט כמו בשפות תכנות אחרות שתומכות בשרשור.
כדי להגדיר את Comlink, עליכם לייבא אותו ל-Web Worker ולהגדיר קבוצת פונקציות לחשוף ל-thread הראשי. לאחר מכן, מייבאים את Comlink ב-thread הראשי, כוללים את ה-worker ומקבלים גישה לפונקציות שחשופות:
worker.js
import {expose} from 'comlink';
const api = {
someMethod() {
// ...
}
}
expose(api);
main.js
import {wrap} from 'comlink';
const worker = new Worker('./worker.js');
const api = wrap(worker);
המשתנה api
ב-thread הראשי מתנהג בדיוק כמו זה שב-Web worker, אבל כל פונקציה מחזירה הבטחה לערך ולא לערך עצמו.
איזה קוד צריך להעביר ל-Web Worker?
לעובדי אינטרנט אין גישה ל-DOM ולממשקי API רבים כמו WebUSB, WebRTC או Web Audio, לכן אי אפשר להעביר ל-Worker ולראות חלקים מהאפליקציה שמסתמכים על גישה כזו. עם זאת, כל קטע קוד קטן שמועבר לעובד קונה יותר מקום ב-thread הראשי בשביל דברים שחייבים להיות שם – כמו עדכון ממשק המשתמש.
אחת הבעיות של מפתחי אתרים היא שרוב אפליקציות האינטרנט מסתמכות על framework של ממשק משתמש כמו Vue או React, כדי לתזמר את כל מה שנמצא באפליקציה. הכול הוא רכיב של ה-framework, ולכן הוא קשור מטבעו ל-DOM. נראה שהדבר מקשה על המעבר לארכיטקטורת OMT.
עם זאת, אם עוברים למודל שבו בעיות בממשק המשתמש נפרדות מנושאים אחרים, כמו ניהול מדינות, עובדי אינטרנט יכולים להיות שימושיים גם באפליקציות שמבוססות על framework. זו בדיוק הגישה שננקטה עם PROXX.
PROXX: מקרה לדוגמה של OMT
צוות Google Chrome פיתח את PROXX כשכפול של שבש המכרות שעומד בדרישות של Progressive Web App, כולל עבודה אופליין וחוויית משתמש שיוצרת עניין. לצערנו, גרסאות מוקדמות של המשחק הניבו ביצועים נמוכים במכשירים מוגבלים כמו טלפונים ניידים, וכתוצאה מכך הצוות הגיע למסקנה שהשרשור הראשי היה צוואר בקבוק.
הצוות החליט להשתמש בעובדי אינטרנט כדי להפריד בין המצב החזותי של המשחק לבין הלוגיקה שלו:
- ה-thread הראשי מטפל בעיבוד של אנימציות ומעברים.
- עובד אינטרנט מטפל בלוגיקת משחק, שהיא חישובית בלבד.
ל-OMT היו השפעות מעניינות על ביצועי הטלפונים המתקדמים של PROXX. בגרסה שאינה OMT, ממשק המשתמש קפוא למשך 6 שניות אחרי שהמשתמש מקיים איתו אינטראקציה. אין משוב, והמשתמש צריך להמתין למשך 6 השניות המלאות לפני שיוכל לבצע פעולה אחרת.
עם זאת, בגרסת OMT, השלמת עדכון של ממשק המשתמש במשחק נמשכת 12 שניות. זה אומנם ירידה בביצועים, אבל למעשה זה מוביל להגדלת מספר המשובים של המשתמשים. ההאטה מתרחשת כי האפליקציה שולחת יותר פריימים בהשוואה לגרסה שאינה של OMT, שאינה שולחת פריימים. כתוצאה מכך, המשתמשים יודעים שמשהו קורה ויכול להמשיך לשחק בזמן שממשק המשתמש מתעדכן, פעולה שמשפרת את הרגשת המשחק באופן משמעותי.
פעולה מכוונת: אנחנו נותנים למשתמשים במכשירים מוגבלים חוויה נגישה יותר בלי להטיל עונשים על המשתמשים במכשירים מתקדמים.
ההשלכות של ארכיטקטורת OMT
כמו שאפשר לראות בדוגמה של PROXX, התכונה הזו מאפשרת לאפליקציה לפעול באופן מהימן במגוון רחב יותר של מכשירים, אבל היא לא משפרת את המהירות של האפליקציה:
- מתבצעת רק העברה של העבודה מה-thread הראשי, ולא צמצום העבודה.
- תקורת התקשורת הנוספת בין עובד האינטרנט וה-thread הראשי יכול לפעמים להאט בצורה שולית יותר.
חשבו על החסרונות
מאחר שה-thread הראשי חופשי לעבד אינטראקציות של משתמשים, כמו גלילה בזמן ש-JavaScript פועל, יש פחות פריימים שיושמטו, למרות שזמן ההמתנה הכולל עשוי להיות ארוך יותר במידה שולית. עדיף להמתין קצת עם המשתמש על ידי הסרת פריים כי מרווח הטעות קטן יותר לגבי פריימים שהושמטו: שחרור הפריים מתבצע באלפיות השנייה, ואילו מאות אלפיות השנייה לפני שהמשתמש מזהה את זמן ההמתנה.
בגלל חוסר ודאות הביצועים במכשירים שונים, המטרה של ארכיטקטורת OMT היא למעשה צמצום סיכונים – הפיכת האפליקציה לחזקה יותר לאור תנאי זמן ריצה משתנים – ולא יתרונות הביצועים של במקביל. העלייה ברמת החוסן והשיפורים בחוויית המשתמש שווים יותר מכל התפשרות על המהירות.
הערה לגבי כלים
Web worker עדיין לא משמש כמשתמשים רגילים, ולכן רוב הכלים למודולים, כמו webpack ורשימת נכסים, לא תומכים בהם באופן רגיל. (אבל חבילה!) למרבה המזל, קיימים יישומי פלאגין שבעזרתם אפשר לעבוד באינטרנט, ובכן, לעבוד עם webpack ו-rollup:
- worker-plugin ל-webpack
- rollup-plugin-off-main-thread בשביל אוסף ערוצים
סיכום
כדי להבטיח שהאפליקציות שלנו יהיו אמינות ונגישות ככל האפשר, במיוחד בשוק גלובלי שהולך וגדל, עלינו לתמוך במכשירים מוגבלים – אלה הם הדרכים שבהן רוב המשתמשים ניגשים לאינטרנט בכל העולם. OMT מציע דרך מבטיחה לשפר את הביצועים במכשירים כאלה, בלי להשפיע לרעה על משתמשים במכשירים מתקדמים.
בנוסף, ל-OMT יש יתרונות משניים:
- היא מעבירה את עלויות הביצוע של JavaScript לשרשור נפרד.
- המערכת מעבירה ניתוח של העלויות, כך שממשק המשתמש עשוי לפעול מהר יותר. הפעולה הזו עשויה להפחית את המהירות שבה נטען רכיב התוכן הראשון (First-Party) או אפילו Time to Interactive (זמן לביצוע אינטראקטיבי), מה שעשוי להגדיל הציון של Lighthouse.
עובדי האינטרנט לא צריכים להיות מפחידים. כלים כמו Comlink מוציאים את העבודה הקשה של עובדים, והופכים אותם לבחירה משתלמת עבור מגוון רחב של אפליקציות אינטרנט.
תמונה ראשית (Hero) מ-Unbounce, מאת James Peacock.