אופטימיזציה מבוססת-פרופיל (נקראת גם PGO או pogo) היא דרך נוספת לבצע אופטימיזציה לבנייה של המשחק, על סמך מידע על אופן הפעולה של המשחק כשמשחקים בו בעולם האמיתי. כך, קוד שמופעל לעיתים רחוקות, כמו שגיאות או מקרים קיצוניים, לא מודגש בנתיבי הביצוע הקריטיים של הקוד, והביצוע שלו מהיר יותר.
איור 1. סקירה כללית של אופן הפעולה של PGO.
כדי להשתמש ב-PGO, קודם צריך להגדיר את ה-build כדי ליצור נתוני פרופיל שהקומפיילר יכול לעבוד איתם. לאחר מכן מריצים את הקוד על ידי הפעלת ה-build ויוצרים קובץ נתונים אחד או יותר של פרופיל. לבסוף, מעתיקים את הקבצים האלה בחזרה מהמכשיר ומשתמשים בהם עם הקומפיילר כדי לבצע אופטימיזציה של קובץ ההפעלה באמצעות פרטי הפרופיל שצילמתם.
איך פועלות גרסאות build שעברו אופטימיזציה ללא PGO
בנייה שעברה אופטימיזציה בלי שימוש בנתוני פרופיל משתמשת במספר היוריסטיקות כדי להחליט איך ליצור קוד שעבר אופטימיזציה.
חלק מההצעות מסומנות באופן מפורש על ידי המפתח – למשל, ב-C++ 20 ואילך, באמצעות רמזים לגבי כיוון ההסתעפות כמו [[likely]]
ו-[[unlikely]]
. דוגמה נוספת היא שימוש במילת המפתח inline
או אפילו __forceinline
(אבל בדרך כלל עדיף להשתמש באפשרות הראשונה כי היא טובה וגמישה יותר).
כברירת מחדל, חלק מהקומפיילרים מניחים שהחלק הראשון של ענף (כלומר, ההצהרה if
, ולא החלק else
) הוא החלק הסביר ביותר. יכול להיות שהכלי לאופטימיזציה יבצע גם הנחות מניתוח סטטי של הקוד לגבי אופן הביצוע שלו – אבל בדרך כלל ההיקף של זה מוגבל.
הבעיה בהיוריסטיקות האלה היא שהן לא יכולות לעזור לקומפיילר בצורה נכונה בכל המצבים – גם לא עם תיוג ידני מקיף. לכן, למרות שהקוד שנוצר בדרך כלל עובר אופטימיזציה טובה, הוא לא טוב כמו שהוא יכול להיות אם לקומפיילר היה יותר מידע על ההתנהגות שלו בזמן הריצה.
יצירת פרופיל
כשיוצרים את קובץ ההפעלה עם PGO מופעל במצב instrumented, קובץ ההפעלה מורחב עם קוד בתחילת כל בלוק קוד – לדוגמה, בתחילת פונקציה או בתחילת כל זרוע של ענף. הקוד הזה משמש למעקב אחר מספר הפעמים שבהן הקוד מופעל בבלוק, והקומפיילר יכול להשתמש בו בהמשך כדי ליצור קוד שעבר אופטימיזציה.
מתבצע גם מעקב אחר נתונים אחרים – לדוגמה, הגודל של פעולות העתקה טיפוסיות בבלוק, כדי שיהיה אפשר ליצור בהמשך גרסאות מהירות של הפעולה שמוטמעות בשורה.
אחרי שהמשחק מבצע עבודה מייצגת כלשהי, קובץ ההפעלה חייב לקרוא לפונקציה – __llvm_profile_write_file()
– כדי לכתוב את נתוני הפרופיל במיקום שניתן להתאמה אישית במכשיר. הפונקציה הזו מקושרת למשחק באופן אוטומטי כשהאפשרות PGO instrumentation מופעלת בהגדרת ה-build.
לאחר מכן צריך להעתיק את קובץ נתוני הפרופיל בחזרה למחשב המארח, ועדיף לשמור אותו במיקום שבו נמצאים פרופילים אחרים מאותו build, כדי שאפשר יהיה להשתמש בהם יחד.
לדוגמה, אפשר לשנות את קוד המשחק כדי לקרוא ל-__llvm_profile_write_file()
כשסצנת המשחק הנוכחית מסתיימת. לאחר מכן, כדי ליצור פרופיל, צריך ליצור את המשחק עם הפעלת מכשור, ואז לפרוס אותו במכשיר Android.
במהלך ההרצה, נתוני הפרופיל נשמרים באופן אוטומטי – מהנדס בקרת האיכות מריץ את המשחק, בודק תרחישים שונים (או פשוט מבצע את הבדיקות הרגילות שלו).
אחרי שמסיימים להתאמן בחלקים שונים של המשחק, אפשר לחזור לתפריט הראשי, מה שיסיים את סצנת המשחק הנוכחית ויכתוב את נתוני הפרופיל.
אחר כך אפשר להשתמש בסקריפט כדי להעתיק את נתוני הפרופיל ממכשיר הבדיקה ולהעלות אותם למאגר מרכזי שבו אפשר לשמור אותם לשימוש מאוחר יותר.
מיזוג נתוני הפרופיל
אחרי שמקבלים פרופיל ממכשיר, צריך להמיר אותו מקובץ נתוני הפרופיל שנוצר על ידי ה-build עם המכשור, לפורמט שהקומפיילר יכול לעבד. הכלי AGDE עושה זאת בשבילכם באופן אוטומטי, לכל קובץ נתונים של פרופיל שאתם מוסיפים לפרויקט.
הטכנולוגיה PGO נועדה לשלב את התוצאות של כמה הפעלות של פרופילים עם מכשור – AGDE עושה זאת גם באופן אוטומטי אם יש לכם כמה קבצים בפרויקט יחיד.
כדי להמחיש את היתרון של מיזוג מערכי נתונים של פרופילים, נניח שיש לכם מעבדת בדיקות מלאה בבודקי QA שמשחקים ברמות שונות במשחק שלכם. כל משחק שלהם מתועד, ואז משמש ליצירת נתוני פרופיל מ-build של המשחק שכולל כלי PGO. מיזוג פרופילים מאפשר לשלב את התוצאות מכל ההרצות השונות של הבדיקות – שיכול להיות שהן יפעילו חלקים שונים לגמרי בקוד – כדי לקבל תוצאות טובות יותר.
יתרון נוסף: כשמבצעים בדיקות לאורך זמן, שבהן שומרים עותקים של נתוני הפרופיל מגרסה פנימית אחת לגרסה פנימית אחרת, בנייה מחדש לא בהכרח מבטלת את התוקף של נתוני הפרופיל הישנים. ברוב המקרים, הקוד יציב יחסית מגרסה לגרסה, ולכן נתוני פרופילים מגרסאות ישנות יותר עדיין יכולים להיות שימושיים ולא מתיישנים מיד.
יצירת גרסאות אופטימליות של אפליקציות בהתבסס על פרופילים
אחרי שמוסיפים את נתוני הפרופיל לפרויקט, אפשר להשתמש בהם כדי ליצור את הקובץ הניתן להפעלה. לשם כך, מפעילים את PGO במצב אופטימיזציה בהגדרות הבנייה.
ההוראה הזו מכוונת את האופטימיזציה של הקומפיילר להשתמש בנתוני הפרופיל שצילמתם קודם לכן כשאתם מקבלים החלטות לגבי אופטימיזציה.
מתי כדאי להשתמש באופטימיזציה מבוססת-פרופיל
PGO לא נועד להיות משהו שמפעילים בתחילת הפיתוח או במהלך איטרציה יומיומית של קוד. במהלך הפיתוח, כדאי להתמקד באופטימיזציות שמבוססות על אלגוריתמים ועל פריסת נתונים, כי הן יניבו לכם יתרונות משמעותיים יותר.
אופטימיזציה מונחית פרופילים (PGO) מתבצעת בשלב מאוחר יותר בתהליך הפיתוח, כשמבצעים ליטוש לקראת ההשקה. אפשר לחשוב על אופטימיזציה מונחית-פרופיל כדובדן שבקצפת, שמאפשרת לכם להפיק את הביצועים המקסימליים מהקוד אחרי שכבר השקעתם זמן באופטימיזציה של הקוד בעצמכם.
שיפור הביצועים הצפוי באמצעות PGO
היכולת להשתמש בה תלויה במספר רב של גורמים, כולל מידת המקיפות והעדכניות של הפרופילים, ומידת הקרבה לאופטימליות של הקוד שהיה מתקבל בבנייה מסורתית שעברה אופטימיזציה.
באופן כללי, הערכה שמרנית מאוד היא שעלויות ה-CPU ירדו בכ-5% ב-Threads מרכזיים. יכול להיות שתראו תוצאות שונות.
תקורה של אינסטרומנטציה
הכלי PGO כולל מכשור מקיף, והוא נוצר באופן אוטומטי, אבל הוא לא בחינם. התקורה של PGO instrumentation עשויה להשתנות בהתאם לבסיס הקוד.
עלות הביצועים של אינסטרומנטציה מונחית-פרופיל
יכול להיות שתהיה ירידה בקצב הפריימים בגרסאות עם מכשור. במקרים מסוימים – תלוי כמה אחוזים מה-CPU בשימוש במהלך פעולה רגילה – הירידה הזו יכולה להיות כל כך גדולה שיהיה קשה לשחק כרגיל.
מומלץ שרוב המפתחים יבנו מצב הפעלה חוזרת חצי-דטרמיניסטי למשחק שלהם. פונקציונליות כזו מאפשרת לצוות בקרת האיכות להתחיל את המשחק במיקום התחלתי ידוע וניתן לחזרה במשחק (כמו משחק שמור או רמת בדיקה ספציפית), ואז לתעד את הקלט שלו. אפשר להזין את הקלט הזה, שנרשם מגרסת ה-build לבדיקה, לגרסת build עם PGO, להפעיל אותו מחדש וליצור נתוני פרופיל מהעולם האמיתי, בלי קשר למשך הזמן שנדרש לעיבוד של פריים בודד – גם אם המשחק פועל לאט מדי ואי אפשר לשחק בו.
לפונקציונליות כזו יש גם יתרונות משמעותיים אחרים, כמו הכפלת המאמץ של הבודק: בודק אחד יכול להקליט את הקלט שלו במכשיר, ואז אפשר להפעיל אותו מחדש במכשירים מסוגים שונים למטרות בדיקה ראשונית.
למערכת הפעלה חוזרת כזו יכולים להיות יתרונות עצומים ב-Android, שבה יש מספר גדול של גרסאות מכשירים במערכת האקולוגית – והיתרונות לא מסתיימים כאן: היא יכולה להוות חלק מרכזי במערכת הבנייה של שילוב מתמשך, ולאפשר לכם לבצע באופן קבוע בדיקות רגרסיה של ביצועים ובדיקות עשן במהלך הלילה.
ההקלטה צריכה לתעד את קלט המשתמש בנקודה המתאימה ביותר במנגנון הקלט של המשחק (סביר להניח שלא אירועים ישירים של מסך מגע, אלא תיעוד של ההשלכות שלהם כפקודות). בנוסף, הקלט צריך לכלול מספר פריים שעולה באופן מונוטוני במהלך המשחק, כדי שבמהלך ההפעלה, מנגנון ההפעלה יוכל לחכות לפריים המתאים שבו צריך להפעיל אירוע.
במצב הפעלה, המשחק לא צריך לבצע כניסה אונליין, לא צריך להציג מודעות וצריך לפעול עם מרווח זמן קבוע (במסגרת קצב הפריימים המקסימלי). כדאי להשבית את VSync.
לא חשוב שכל דבר במשחק (למשל, מערכות חלקיקים) יהיה ניתן לשחזור באופן דטרמיניסטי מושלם, אבל אותן פעולות צריכות להניב את אותן השלכות ותוצאות במשחק – כלומר, המשחק צריך להיות זהה.
עלות הזיכרון של Profile-Guided Instrumentation
התקורה של הזיכרון של PGO instrumentation משתנה מאוד בהתאם לספרייה הספציפית שעוברת קומפילציה. בבדיקות שערכנו ראינו עלייה כוללת בגודל של קובץ ההפעלה של הבדיקה, בערך פי 2.2. הגידול הזה בגודל כלל גם את הקוד הנוסף שנדרש כדי להטמיע את בלוקי הקוד, וגם את המקום שנדרש לאחסון המונים. הבדיקות האלה לא היו מקיפות, ויכול להיות שהחוויה שלכם תהיה שונה.
מתי צריך לעדכן את נתוני הפרופיל או למחוק אותם
כדאי לעדכן את הפרופילים בכל פעם שמבצעים שינוי גדול בקוד (או בתוכן של המשחק).
המשמעות המדויקת של זה תלויה בסביבת ה-build ובשלב הפיתוח.
כמו שציינו קודם, לא מומלץ להעביר נתוני פרופיל בין שינויים משמעותיים בסביבת הבנייה. זה לא ימנע מכם לבנות או ישבש את הבנייה, אבל זה יפחית את היתרונות של השימוש ב-PGO כי מעט מאוד נתוני פרופיל יהיו רלוונטיים לסביבת הבנייה החדשה. עם זאת, זה לא המקרה היחיד שבו נתוני הפרופיל שלכם עלולים להיות לא עדכניים.
נתחיל בהנחה שלא תשתמשו ב-PGO עד שתתקרבו לסיום הפיתוח ותתכוננו להשקה, אולי למעט איסוף נתונים שבועי כדי שמהנדסים שמתמקדים בביצועים יוכלו לוודא שלא יהיו בעיות בלתי צפויות לקראת ההשקה.
המספר הזה משתנה ככל שמתקרבים לחלון ההשקה, כשהצוות לבקרת איכות בודק את המשחק מדי יום ומריץ אותו באופן מקיף. במהלך השלב הזה תוכלו ליצור פרופילים מהנתונים האלה מדי יום, ולהשתמש בהם כדי לקבל החלטות לגבי בנייה עתידית לצורך בדיקת ביצועים והתאמה של תקציבי הביצועים שלכם.
כשמתכוננים להפצה, צריך לנעול את גרסת ה-build שמתכננים להפיץ, ואז צוות בקרת האיכות יריץ אותה ויפיק את נתוני הפרופיל החדשים. לאחר מכן משתמשים בנתונים האלה כדי ליצור גרסה סופית של הקובץ הניתן להפעלה.
צוות ה-QA יכול לבצע בדיקה סופית של הגרסה הזו שעברה אופטימיזציה ומוכנה להפצה, כדי לוודא שהיא מוכנה להשקה.