שיפור הביצועים של הנגישות ב-Chromium

הפוסט הזה הוא מאת 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. הוא משמש לעזרה לטכנולוגיות מסייעות לגשת לתוכן באינטרנט. לפעמים, כשמכשיר זקוק למידע מהעץ הזה, יכול להיות שהוא לא יהיה מוכן, ולכן הדפדפן צריך לתזמן את הבקשות האלה למועד מאוחר יותר.

בעבר, התזמון הזה טופל באמצעות שיטה שנקראת 'סגירה', שכללה הוספת קריאות חוזרות (callbacks) לתור. הגישה הזו הוסיפה עבודה נוספת בגלל האופן שבו מתבצע העיבוד של חסימות.

כדי לשפר את המצב, עברנו למערכת שמשתמשת ב-enums. לכל משימה מוקצה ערך enum ספציפי, וכשהעץ של נגישות מוכן, המערכת קוראת ל-method הנכון של המשימה הזו. השינוי הזה הקל על ההבנה של הקוד ושפר את הביצועים ביותר מ-20%.

תרשימים של בדיקות ביצועים בזמן ריצה.
תרשים של זמני הריצה של כמה בדיקות ביצועים, שבהן יש ירידה גלויה של כ-20% בכל הבדיקות.

איתור ופתרון בעיות בביצועי הגלילה

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

כדי לבדוק את זה, הסרתם באופן זמני את הקוד שמטפל בתיבות גבול והרצתם בדיקות ביצועים כדי לראות את ההשפעה. בבדיקה אחת, focus-links.html, נרשמה עלייה עצומה של כ-1,618%. הגילוי הזה שימש כבסיס לעבודה נוספת.

בדיקת הבדיקה האיטית

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

כשהגלילה הושבתה, זמן הבדיקה ירד ל-1.2 אלפיות השנייה כשהחישובים של תיבת הגבול היו פעילים. כך גילינו שהבעיה האמיתית היא גלילה.

תוצאות הבדיקה כשהגלילה מושבתת.
זמן הריצה של בדיקת הקישורים למוקדי העניין יורד מ-20 אלפיות שנייה ל-1.1 אלפיות שנייה כשהגלילה מושבתת או כשהסריאליזציה של תיבת הגבול מוסרת.

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

תוצאות הבדיקה של הבדיקה החדשה.
הזמן הנדרש לעיבוד גלילות בגלילה מיידית הוא 65 אלפיות שנייה, ואילו בגלילה חלקה הוא 123 אלפיות שנייה.

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

ידענו על המקרה, אבל למה זה קרה?

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

תרשים של בדיקות גלילה עם פרופיל.
תרשים שנוצר מפרופיל של בדיקות גלילה. מוצג שרוב הזמן מנוצל בקריאות לפונקציה שנקראת Unserialize וגם לפונקציה אחרת שנקראת IsChildOfLeaf.

אחרי בדיקה, מצאנו שהבעיה לא הייתה בקוד ההמרה לאובייקט (deserialization) עצמו, אלא בתדירות הקריאות אליו. כדי להבין את זה, צריך להבין איך עדכוני הנגישות פועלים ב-Chromium. העדכונים לא נשלחים בנפרד, אלא נשמרים במיקום מרכזי בשם AXObjectCache. כשצומת משתנה, שיטות שונות מדווחות למטמון כדי לסמן את הצומת כלא נקי לצורך שרשור מאוחר יותר. לאחר מכן, כל המאפיינים של הערות לא נקיות, כולל מאפיינים שלא השתנו, עוברים סריאליזציה ונשלחים לדפדפן. אמנם העיצוב הזה מפשט את הקוד ומפחית את המורכבות באמצעות נתיב עדכון יחיד, אבל הוא הופך לאיטי כשיש אירועים מהירים של 'סימון כמצב 'לא עדכני'', כמו אירועים שנובעים מהחלקה. הדבר היחיד שמשתנה הוא הערכים של scrollX ו-scrollY, אבל אנחנו מבצעים סריאליזציה של שאר המאפיינים יחד איתם בכל פעם. קצב העדכונים הגיע ליותר מ-20 פעמים בשנייה!

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

תיקון בעיות גלילה

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