אופטימיזציה מבוססת-פרופיל (PGO) היא שיטת אופטימיזציה ידועה של המהדר. ב-PGO, המהדר משתמש בפרופילי זמן ריצה מהפעלות של תוכנה כדי לקבל החלטות אופטימליות לגבי הטבעת הקוד ופריסת הקוד. כתוצאה מכך, הביצועים משתפרים והקוד קטן יותר.
אפשר לפרוס את PGO באפליקציה או בספרייה באמצעות השלבים הבאים: 1. מזהים עומס עבודה מייצג. 2. איסוף פרופילים. 3. משתמשים בפרופילים בגרסה ייעודית להפצה.
שלב 1: זיהוי עומס עבודה מייצג
קודם כול, מזהים מדד ביצועים או עומס עבודה מייצגים לאפליקציה. זהו שלב קריטי, כי הפרופילים שנאספים מעומס העבודה מאפשרים לזהות את האזורים החמים והקרים בקוד. במהלך השימוש בפרופילים, המהדר יבצע פעולות אופטימיזציה אגרסיביות והטבעה באזורים החמים. המהדר יכול גם לבחור לצמצם את גודל הקוד של אזורים לא פעילים, תוך פגיעה בביצועים.
זיהוי עומס עבודה טוב מועיל גם למעקב אחרי הביצועים באופן כללי.
שלב 2: איסוף פרופילים
איסוף הפרופילים כולל שלושה שלבים: - בניית קוד נייטיב עם אינסטרומנטציה, - הפעלת האפליקציה עם האינסטרומנטציה במכשיר ויצירת פרופילים ו- מיזוג או עיבוד לאחר מכן של הפרופילים במארח.
יצירה מוזיקלית
איסוף הפרופילים מתבצע על ידי הרצת עומס העבודה משלב 1 ב-build של האפליקציה. כדי ליצור build עם אינסטרומנטציה, מוסיפים את -fprofile-generate
לדגלי המהדר והקישור. צריך לשלוט בדגל הזה באמצעות משתנה build נפרד, כי הדגל לא נדרש במהלך build שמוגדר כברירת מחדל.
יצירת פרופילים
בשלב הבא, צריך להריץ את האפליקציה האינסטרומנטלית במכשיר וליצור פרופילים.
הפרופילים נאספים בזיכרון כשמריצים את קובץ הבינארי המתווסף, ונכתבים לקובץ בסיום. עם זאת, פונקציות שרשומות ב-atexit
לא נקראות באפליקציה ל-Android – האפליקציה פשוט נמחקת.
האפליקציה או עומס העבודה צריכים לבצע עבודה נוספת כדי להגדיר נתיב לקובץ הפרופיל, ואז להפעיל באופן מפורש כתיבת של הפרופיל.
- כדי להגדיר את הנתיב לקובץ הפרופיל, קוראים לפונקציה
__llvm_profile_set_filename(PROFILE_DIR "/default-%m.profraw
.%m
שימושי כשיש מספר ספריות משותפות.%m
מתרחב לחתימה ייחודית של המודול בספרייה הזו, וכתוצאה מכך נוצר פרופיל נפרד לכל ספרייה. כאן מפורטים עוד מפרטים שימושיים של דפוסים.PROFILE_DIR
היא ספרייה שאפשר לכתוב מתוך האפליקציה. תוכלו להיעזר בהדגמה כדי לזהות את הספרייה הזו בסביבת זמן הריצה. - כדי להפעיל באופן מפורש כתיבה של פרופיל, צריך להפעיל את הפונקציה
__llvm_profile_write_file
.
extern "C" {
extern int __llvm_profile_set_filename(const char*);
extern int __llvm_profile_write_file(void);
}
#define PROFILE_DIR "<location-writable-from-app>"
void workload() {
// ...
// run workload
// ...
// set path and write profiles after workload execution
__llvm_profile_set_filename(PROFILE_DIR "/default-%m.profraw");
__llvm_profile_write_file();
return;
}
NB: קל יותר ליצור את קובץ הפרופיל אם עומס העבודה הוא בינארי עצמאי – פשוט צריך להגדיר את משתנה הסביבה LLVM_PROFILE_FILE
ל-%t/default-%m.profraw
לפני שמריצים את הקובץ הבינארי.
פרופילים של עיבוד נתונים לאחר פרסום
קובצי הפרופיל הם בפורמט .profraw. קודם צריך לאחזר אותם מהמכשיר באמצעות adb pull
. אחרי האחזור, משתמשים בכלי השירות llvm-profdata
ב-NDK כדי להמיר מ-.profraw
ל-.profdata
, ואז ניתן להעביר אותו למהדר.
$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-profdata \
merge --output=pgo_profile.profdata \
<list-of-profraw-files>
כדי למנוע אי-התאמה בין הגרסאות של פורמטים של קובצי הפרופיל, צריך להשתמש ב-llvm-profdata
וב-clang
מאותה גרסה של NDK.
שלב 3: שימוש בפרופילים ליצירת אפליקציה
כדי להשתמש בפרופיל מהשלב הקודם במהלך ה-build של הגרסה היציבה של האפליקציה, מעבירים את הערך -fprofile-use=<>.profdata
למהדר ולמקשר. אפשר להשתמש בפרופילים גם כשהקוד מתפתח – המהדר Clang יכול לסבול אי-התאמה קלה בין המקור לבין הפרופילים.
הערה: באופן כללי, הפרופילים ברוב הספריות נפוצים בכל הארכיטקטורות. לדוגמה, אפשר להשתמש בפרופילים שנוצרו מ-build של Arm64 של הספרייה בכל הארכיטקטורות. עם זאת, אם יש בספרייה נתיבי קודים ספציפיים לארכיטקטורה (arm לעומת x86 או 32-bit לעומת 64-bit), יש להשתמש בפרופילים נפרדים לכל הגדרה כזו.
סיכום של כל המידע
בדף https://github.com/DanAlbert/ndk-samples/tree/pgo/pgo תוכלו לראות הדגמה מקצה לקצה של שימוש ב-PGO מאפליקציה. הדף הזה מכיל פרטים נוספים שלא התייחסנו אליהם במאמר הזה.
- כללי ה-build CMake מראים איך להגדיר משתנה CMake שיוצר קוד מקורי עם אינסטרומנטציה. כשמשתנה ה-build לא מוגדר, מתבצעת אופטימיזציה של קוד נייטיב באמצעות פרופילי PGO שנוצרו בעבר.
- ב-build שמוגדר באינסטרומנטציה, התגים ב-pgodemo.cpp כותבים שהפרופילים הם ביצוע של עומס עבודה.
- מיקום שניתן לכתיבה של הפרופילים נמצא בזמן הריצה ב-MainActivity.kt באמצעות
applicationContext.cacheDir.toString()
. - כדי לשלוף פרופילים מהמכשיר בלי לדרוש
adb root
, אפשר להשתמש במתכון שלadb
כאן.