הפוסט הזה הוא מאת Ahmed Elwasefi, שותף ל-Chromium, שמספר איך הוא הפך לשותף במסגרת Google Summer of Code, ואילו בעיות ביצועים שקשורות לנגישות הוא זיהה ותיקן.
לקראת השנה האחרונה שלי בהנדסת מחשבים באוניברסיטה הגרמנית בקהיר, החלטתי לבדוק הזדמנויות לתרום לקוד פתוח. התחלתי לבדוק את רשימת הבעיות של Chromium שמתאימות למתחילים, וגיליתי עניין מיוחד בנושא הנגישות. החיפוש שלי אחר הדרכה הוביל אותי אל Aaron Leventhal, שהמומחיות והנכונות שלו לעזור גרמו לי להצטרף אליו לפרויקט. שיתוף הפעולה הזה הפך לחוויית Google Summer of Code שלי, שבמסגרתה התקבלתי לעבוד עם צוות הנגישות של Chromium.
אחרי שסיימתי את Google Summer of Code, המשכתי לטפל בבעיה שלא נפתרה בגלילה, מתוך רצון לשפר את הביצועים. תודות לשתי מענקים מתוכנית OpenCollective של Google, הצלחתי להמשיך לעבוד על הפרויקט הזה תוך כדי ביצוע משימות נוספות שמתמקדות בשיפור הקוד לשיפור הביצועים.
בפוסט הזה בבלוג אספק לכם סקירה של המסע שלי עם Chromium בשנה וחצי האחרונות, עם פירוט של השיפורים הטכניים שביצענו, במיוחד בתחום הביצועים.
איך קוד הנגישות משפיע על הביצועים ב-Chrome
קוד הנגישות של Chrome עוזר לטכנולוגיות מסייעות כמו קוראי מסך לגשת לאינטרנט. עם זאת, כשהיא מופעלת, היא עשויה להשפיע על זמני הטעינה, הביצועים ועל חיי הסוללה. לכן, אם אין צורך בקוד הזה, הוא יישאר לא פעיל כדי למנוע האטה בביצועים. כ-5-10% מהמשתמשים הפעילו את קוד הנגישות, לרוב בגלל כלים כמו מנהלי סיסמאות ותוכנות אנטי-וירוס שמשתמשים בממשקי API לנגישות בפלטפורמה. הכלים האלה מסתמכים על ממשקי ה-API האלה כדי לקיים אינטראקציה עם תוכן הדף ולשנות אותו, למשל לאתר שדות סיסמה למנהלי סיסמאות ולכלי למילויי טפסים.
עדיין לא ידוע מהי הירידה הכוללת במדדים המרכזיים, אבל ניסוי שנערך לאחרונה בשם 'השבתה אוטומטית של נגישות' (השבתה של נגישות כשהיא לא בשימוש) מראה שהיא גבוהה למדי. הבעיה נובעת מכמויות העצומות של חישובים ותקשורת בשני תחומים עיקריים של קוד הבסיס של הנגישות ב-Chrome: המרת הקוד והדפדפן. המרת הדפים אוספת מידע על תוכן האינטרנט ועל שינויים בתוכן, ומחשבת את מאפייני הנגישות של עץ הצמתים. לאחר מכן, כל הצמתים המלוכלכים עוברים סריאליזציה ונשלחים דרך צינור לשרשור הממשק הראשי של תהליך הדפדפן. השרשור הזה מקבל את המידע הזה וממיר אותו לאותה עץ של צמתים, ולאחר מכן ממיר אותו לפורמט שמתאים לטכנולוגיות מסייעות של צד שלישי, כמו קוראי מסך.
שיפורים בנגישות של Chromium
הפרויקטים הבאים הושלמו במהלך Summer of Code ולאחר מכן, במסגרת המימון של תוכנית Google OpenCollective.
שיפור המטמון
ב-Chrome יש מבנה נתונים מיוחד שנקרא עץ הנגישות, שמשקף את עץ ה-DOM. הוא משמש לעזרה לטכנולוגיות מסייעות לגשת לתוכן באינטרנט. לפעמים, כשמכשיר זקוק למידע מהעץ הזה, יכול להיות שהוא לא יהיה מוכן, ולכן הדפדפן צריך לתזמן את הבקשות האלה למועד מאוחר יותר.
בעבר, התזמון הזה טופל באמצעות שיטה שנקראת closures, שכוללת הוספה של פונקציות קריאה חוזרת (callbacks) לתור. הגישה הזו הוסיפה עבודה נוספת בגלל האופן שבו מתבצע העיבוד של החסימות.
כדי לשפר את המצב, עברנו למערכת שמשתמשת ב-enums. לכל משימה מוקצה ערך enum ספציפי, וכשהעץ של נגישות מוכן, מתבצעת קריאה ל-method הנכון של המשימה הזו. השינוי הזה הקל על ההבנה של הקוד ושפר את הביצועים ביותר מ-20%.

איתור ופתרון בעיות בביצועי הגלילה
לאחר מכן בדקתי איך הביצועים משתפרים כשמשביתים את הסריאליזציה של תיבת הסימון. קופסאות מסגרת הן המיקומים והגדלים של רכיבים בדף אינטרנט, כולל פרטים כמו רוחב, גובה והמיקום שלהם ביחס לרכיב ההורה.
כדי לבדוק את זה, הסרתם באופן זמני את הקוד שמטפל בתיבות גבול והרצתם בדיקות ביצועים כדי לראות את ההשפעה. בבדיקה אחת, focus-links.html, נרשמה עלייה עצומה של כ-1,618%. הגילוי הזה שימש כבסיס לעבודה נוספת.
בדיקת הבדיקה האיטית
התחלתי לבדוק למה הבדיקה הספציפית הזו הייתה איטית עם תיבות מלבניות. כל מה שנעשה בבדיקה היה להתמקד בכמה קישורים בזה אחר זה. לכן, הבעיה העיקרית צריכה להיות התמקדות ברכיבים או גלילה שהתרחשה עם פעולת המיקוד. כדי לבדוק את זה, הוספתי את {preventScroll: true}
לקריאה focus()
בבדיקת הביצועים, וכך עצרתי את הגלילה.
כשהגלילה הושבתה, זמן הבדיקה ירד ל-1.2 אלפיות השנייה כשהחישובים של תיבת הגבול היו פעילים. כך גילינו שהבעיה האמיתית היא גלילה.

יצרתי בדיקה חדשה בשם scroll-in-page.html כדי לשכפל את הבדיקה focus-links, אבל במקום להשתמש ב-focus, היא גוללת בין רכיבים באמצעות scrollIntoView()
. בדקתי גלילה חלקה וגלילה מיידית, עם חישובים של תיבת מלבול וגם בלי.

התוצאות הראו שבשימוש בגלילה מיידית ובתיבות גבול, התהליך נמשך כ-66 אלפיות השנייה. גלילה חלקה הייתה איטית עוד יותר, בזמן של כ-124 אלפיות השנייה. כשהשבתה את תיבות הסימון, הבדיקה הסתיימה תוך זמן קצר כי לא הופעלו אירועים.
ידענו על המקרה, אבל למה זה קרה?
עכשיו כבר ברור לנו שגלילה היא המקור לחלק גדול מהאיטיות בסריאליזציה של נגישות, אבל עדיין היינו צריכים לברר למה. כדי לנתח את הנתונים האלה, השתמשו בשני כלים שנקראים perf ו-pprof כדי לפרק את העבודה שבוצעה בתהליך הדפדפן. הכלים האלה משמשים לעתים קרובות ב-C++ לצורך יצירת פרופיל. בתרשים הבא מוצג קטע מהחלק המעניין.

אחרי בדיקה, מצאנו שהבעיה לא הייתה בקוד ההמרה לאובייקט (deserialization) עצמו, אלא בתדירות הקריאות אליו. כדי להבין את זה, צריך להבין איך עדכוני הנגישות פועלים ב-Chromium. העדכונים לא נשלחים בנפרד, אלא נשמרים במיקום מרכזי בשם AXObjectCache
. כשצומת משתנה, שיטות שונות מדווחות למטמון כדי לסמן את הצומת כלא נקי לצורך שרשור מאוחר יותר. לאחר מכן, כל המאפיינים של הערות לא נקיות, כולל מאפיינים שלא השתנו, עוברים סריאליזציה ונשלחים לדפדפן. אמנם העיצוב הזה מפשט את הקוד ומפחית את המורכבות באמצעות נתיב עדכון יחיד, אבל הוא הופך לאיטי כשיש אירועים מהירים של 'סימון כמצב 'לא עדכני'', כמו אירועים שנובעים מהחלקה. הדבר היחיד שמשתנה הוא הערכים של scrollX
ו-scrollY
, אבל אנחנו מבצעים סריאליזציה של שאר המאפיינים יחד איתם בכל פעם. קצב העדכונים הגיע ליותר מ-20 פעמים בשנייה!
כדי לפתור את הבעיה הזו, אפשר להשתמש בסריאליזציה של תיבת ה-bounding box. הפתרון הזה משתמש בנתיב סריאליזציה מהיר יותר ששולח רק את פרטי תיבת ה-bounding box, ומאפשר לבצע עדכונים מהירים בלי להשפיע על נכסים אחרים. השיטה הזו מטפלת ביעילות בשינויים של תיבת הגבול.
תיקון בעיות גלילה
הפתרון היה ברור: צריך לכלול את ההיסטים הנוכחיים של הגלילה עם סריאליזציה של תיבת המכסה. כך מוודאים שהעדכונים בזמן גלילה עוברים עיבוד דרך הנתיב המהיר, וכך משפרים את הביצועים בלי עיכובים מיותרים. כשאנחנו אורזים את ההיסטים בגלילה עם נתוני תיבת המכסה, אנחנו מבצעים אופטימיזציה של התהליך כדי לקבל עדכונים חלקים ויעילים יותר, וכך יוצרים חוויה חלקה יותר למשתמשים שהפעלתם בהם את התכונה 'נגישות'. השיפור אחרי הטמעת התיקון הוא עד 825% בבדיקות גלילה.
פישוט הקוד
בתקופה הזו התמקמתי באיכות הקוד כחלק מפרויקט שנקרא Onion Soup, שמפשט את הקוד על ידי צמצום או הסרה של קוד שמתפזר ללא צורך בין שכבות.
הפרויקט הראשון נועד לייעל את האופן שבו נתוני הנגישות עוברים סריאליזציה מהמעבד לדפדפן. בעבר, הנתונים היו צריכים לעבור דרך שכבה נוספת לפני שהם הגיעו ליעד, מה שהוסיף מורכבות מיותרת. הפכנו את התהליך לפשוט יותר על ידי שליחת הנתונים ישירות, בלי גורם מתווך.
בנוסף, זיהינו והסרנו כמה אירועים לא עדכניים שגרמו לעבודה מיותרת במערכת, כמו אירוע שהופעל כשהפריסה הושלמה. החלפנו אותם בפתרון יעיל יותר.
בוצעו גם שיפורים קטנים אחרים. לצערנו, לא ראינו שיפורים בביצועים, אבל אנחנו גאים לשתף שהקוד ברור הרבה יותר ועכשיו יש לו תיעוד עצמי. כך תוכלו להכין את הקרקע לשיפורים עתידיים בביצועים. אפשר לבדוק את השינויים בפועל בפרופיל שלי ב-Gerrit.
סיכום
העבודה עם צוות הנגישות של Chromium הייתה חוויה מתגמלת. תוך כדי פתרון אתגרים שונים, החל מאופטימיזציה של ביצועי הגלילה ועד לפשטת קוד הבסיס, צברתי הבנה עמוקה יותר של פיתוח בפרויקט בקנה מידה כה רחב, וגם למדתי כלים חשובים ליצירת פרופילים. בנוסף, הבנתי כמה חשובה הנגישות ליצירת אינטרנט שמכיל את כולם. השיפורים שביצענו לא רק משפרים את חוויית המשתמש של מי שמשתמש בטכנולוגיות מסייעות, אלא גם תורמים לביצועים וליעילות הכוללים של הדפדפן.
תוצאות הביצועים היו מרשימות. לדוגמה, המעבר לשימוש ב-enums לתזמון משימות שיפר את הביצועים ב-יותר מ-20%. בנוסף, התיקון שלנו לגלילה הביא לירידה של עד 825% בבדיקות הגלילה. השינויים בהפשטת הקוד לא רק הפכו את הקוד לברור יותר ולקל יותר לתחזוקה, אלא גם סללו את הדרך לשיפורים עתידיים.
אני רוצה להביע את התודה שלי לסטפן זאגר (Stefan Zager), כריס הרלסון (Chris Harrelson) ומייסיון פריד (Mason Freed) על התמיכה וההדרכה שלהם במהלך השנה, ובמיוחד לארון לאוונטל (Aaron Leventhal), בלי שבלעדיו ההזדמנות הזו לא הייתה מתאפשרת. אני רוצה גם להודות ל-Tab Atkins-Bittner ולצוות GSoC על התמיכה שלהם.
אם אתם רוצים לתרום לפרויקט משמעותי ולפתח את המיומנויות שלכם, מומלץ מאוד להצטרף ל-Chromium. זו דרך מצוינת ללמוד, ותוכניות כמו Google Summer of Code הן נקודת התחלה מצוינת.