לדלג לתוכן

סמלים ואופטימיזציה

נניח שקיבלנו כתובת קריסה מתוך גרסה שיצאה לפני חודש. קוד המקור כבר השתנה, ה־DLL נבנה שוב וה־PDB החדש נראה בעל אותו שם. טעינת הסמלים החדשים עלולה לייצר סיפור שגוי על קוד שלא רץ במחשב של המשתמש.

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

יוצרים עצירה שאינה תלויה בהשחתת זיכרון

התוכנית הבאה מעלה חריגת תוכנה מכוונת כדי ליצור נקודת חקירה. מפעילים אותה תחת דיבאגר; מחוץ לדיבאגר החריגה הלא מטופלת צפויה לסיים את התהליך.

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <cstdio>

unsigned transform(unsigned value) {
    return (value * 17u) ^ (value >> 3);
}

__declspec(noinline)
void analyze(unsigned count) {
    unsigned total = 0;
    for (unsigned i = 0; i < count; ++i)
        total += transform(i);
    std::printf("total=%u\n", total);
    RaiseException(0xE0000001u, 0, 0, nullptr);
}

int main(int argc, char**) {
    analyze(static_cast<unsigned>(argc) * 1000u);
}

האריתמטיקה היא unsigned, ולכן wraparound בחיבור ובכפל מוגדר. איננו צריכים להכניס undefined behavior כדי לחקור אופטימיזציה. שומרים את הקובץ כ־symbols.cpp.

cl /nologo /std:c++20 /EHsc /W4 /Od /Zi symbols.cpp /Fesymbols_debug.exe /link /DEBUG /PDB:symbols_debug.pdb
cl /nologo /std:c++20 /EHsc /W4 /O2 /Zi symbols.cpp /Fesymbols_opt.exe /link /DEBUG /PDB:symbols_opt.pdb

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

מחברים את הדיבאגר לתוצר הנכון

ב־WinDbg פותחים את ה־EXE המתאים, מגדירים תיקיית symbols מקומית ומאפשרים symbols ציבוריים של Microsoft לרכיבי המערכת. הפקודות הבאות הן דוגמה שיש להתאים לנתיב הניסוי.

.symfix
.sympath+ C:\AmitTechLab\symbols
.reload /f
lmvm symbols_opt
sxe 0xe0000001
g
k

הפקודה lmvm מציגה מידע על ה־module ועל מצב טעינת הסמלים. אם ה־PDB אינו מתאים, מתקנים את המקור לסמלים במקום לכפות שימוש במידע שגוי. בפרט, שרת הסמלים הציבורי אינו מכיל את הסמלים הפרטיים של התוכנית שכתבנו.

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

המשתנה קיים בשפה אך לא בהכרח בזיכרון

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

בעת העצירה אחרי ההדפסה, הערך total כבר אינו נדרש להמשך הפעולה. המהדר יכול להשתמש במיקומו למשהו אחר. הודעת optimized away היא לעיתים תיאור נכון של המציאות, ולא בעיה ב־PDB.

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

גבול קובץ אינו תמיד גבול אופטימיזציה

ללא אופטימיזציה כללית, יחידת תרגום רואה בעיקר את המידע שהגיע אליה. עם /GL ו־/LTCG הכלים יכולים להשתמש במידע מעבר לגבולות האלה. פונקציה שנמצאת בקובץ אחר יכולה לעבור inlining, וקריאה שלא ניתן היה לפשט קודם יכולה להשתנות.

אפשר לפצל את transform לקובץ נפרד ולבנות פעם בלי האפשרויות ופעם איתן. משווים את תוצר הקוד ואת גודלו, ולא רק את זמן הבנייה. בקבצים שנוצרו ב־/GL יש מגבלות תאימות לכלים ולגרסאות, ולכן שומרים את toolset המדויק.

פרופיל אינו אמת נצחית

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

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

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

חבילת תחקיר שמחזיקה זמן

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

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

לקריאה נוספת: הגדרת נתיב סמלים ב־WinDbg ואופטימיזציה מונחית פרופיל.