240 likes | 425 Views
תורת הקומפילציה תרגול 12: אופטימיזציה. נכתב ע"י אלכס קוגן ( ( sakogan@cs סמסטר חורף, תשס"ח עודכן: סמסטר אביב 2012. Control Flow Graph (CFG). גרף מכוון המייצג את זרימת בקרת התוכנית אחד הייצוגים הנפוצים של תכנית. צמתים: בלוקים בסיסיים סדרת פקודות בתכנית שתמיד מתבצעת ברצף.
E N D
תורת הקומפילציהתרגול 12:אופטימיזציה נכתב ע"י אלכס קוגן ((sakogan@cs סמסטר חורף, תשס"ח עודכן: סמסטר אביב 2012
Control Flow Graph (CFG) • גרף מכוון המייצג את זרימת בקרת התוכנית • אחד הייצוגים הנפוצים של תכנית. • צמתים: בלוקים בסיסיים • סדרת פקודות בתכנית שתמיד מתבצעת ברצף. • יש כניסה יחידה (לתחילת הבלוק) ויציאה יחידה (מסוף הבלוק). • קשתות: יש קשת מבלוק Bi לבלוק Bk אם: • Bi מסתיים בקפיצה (אולי מותנית)ל-Bk, או • Bi מסתיים בקפיצה מותנית או ללא קפיצה ו-Bkמופיע אחרי Bi בקוד המקורי.
דוגמה 100: x := y + 1 200: if (x > 10) goto 500 300: z := 0 400: goto 700 500: z := x - 10 600: goto 700 700: y := z * x 800: a := y * y x := y + 1 if (x > 10) goto 500 z := 0 goto 700 z := x - 10 goto 700 y := z * x a := y * y איזה מבנה בקרה יכול ליצור קוד כזה בשיטת backpatching?
איך בונים CFG ? נגדיר: • leader - כל פקודה המקיימת אחת הדרישות: • מופיעה מייד אחרי קפיצה (מותנית או לא מותנית). • יעד של קפיצה (מותנית או לא מותנית). • פקודה ראשונה בתכנית. • בדוגמה: פקודות בכתובות 100, 300, 500, 700
אלגוריתם לבניית CFG • מצא את כל ה-leaders בתכנית. • עבור כל leader: הבלוק הבסיסי של ה-leader הינו: • סדרת הפקודות החל ממנו (כולל) ועד ל-leader הבא (לא כולל). • או עד לסוף התכנית (אם אין leader נוסף אחריו). • הוסף קשתות בהתאם לקפיצות בסוף כל בלוק בסיסי. • לעתים נתעניין ב-CFG בו כל צומת מכיל פקודה יחידה. • לעתים נניח כי לבלוק הראשון בתכנית אין קשתות נכנסות ולבלוק אחרון אין קשתות יוצאות • תמיד ניתן להוסיף בלוקים ריקים בהתחלה ובסוף.
דוגמה ממבחן • פקודה u תיקרא בלתי ישיגה (unreachable) אם u אינה מתבצעת באף הרצה של תכנית. • הציעו אלגוריתם למחיקת פקודות בלתי ישיגות מה-CFG. • פתרון: נבחין שאם פקודה u אינה נמצאת על אף מסלול מהצומת התחילי ב-CFG, לא ייתכן שהיא מתבצעת בריצה כלשהי u בלתי ישיגה.
דוגמה ממבחן - המשך • אלגוריתם: • בצע סריקה ב-CFG (BFS או DFS) וסמן את כל הצמתים ב-CFG שבהם נתקלת בסריקה. • כל צומת שאינו מסומן - מחק מהגרף. • האם הפתרון אופטימלי? כלומר, האם כל הפקודות בלתי-ישיגות יימחקו?
הערה כללית • האופטימיזציות שנציע יהיו שמרניות • לא נזהה את כל המקרים שניתן לשפר, אבל • אם ביצענו שינוי כלשהו, מובטח שמשמעות התכנית לא תשתנה. • ככלל, עדיף תכנית פחות יעילה על פני תכנית לא נכונה.
דוגמא - Branch Chaining • הציעו אלגוריתם המבצע את האופטימיזציה Branch Chaining: • ביטול שרשראות של קפיצות שכל אחת מהן קופצת לבאה בתור והחלפתן בקפיצה לכתובת היעד של הקפיצה האחרונה בשרשרת. • הרעיון: 100: x:= y + z 100: x:= y + z 200: goto 300 200: goto 400 300: goto 400
Branch Chaining • מתי קפיצה (פקודות goto) מיועדת לסילוק? • חייבת להיות קפיצה לא מותנית • וגם, חייבת להיות פקודה יחידה בבלוק הבסיסי שלה • היא אחרונה כי תמיד אחרי goto מתחילים בלוק חדש. • היא leader כי יש פקודה שקופצת אליה. • בהינתן CFG, נציע אלגוריתם הבא: • מצא פקודות "goto x" שנמצאת לבד בצומת v כלשהו. • לכל צומת u ב-CFG שיש קשת מ-u ל-v וגם u מסתיים בקפיצה לא מותנית, החלף את כתובת הקפיצה בפקודה האחרונה של u לכתובת x. • אם אין יותר קשתות נכנסות ל-v, מחק את v מהגרף. • חזור ל-1 כל עוד יש מועמדים מתאימים.
Branch Chaining • שאלה: אילו אופטימיזציות כדאי להריץ לפני Branch Chaining על מנת להגדיל את יעילותה? • תשובה: אופטימיזציות שיעזרו להחליף קפיצות מותנות בקפיצות לא מותנות: • Copy & Constant Propagation • החלפת הופעות משתנה יעד של פקודת השמה במשתנה מקור / קבוע. • Constant Folding • פישוט של ביטויים המורכבים מקבועים בלבד. • החלפת קפיצות מותנות עם תנאי שיש בו רק קבועים בקפיצות לא מותנות.
דוגמה 100: x:=1+5 200: y:=x 300: if (y > 0) goto 400 400: goto 500 100: x:=1+5 200: y:=x 300: if (x > 0) goto 400 400: goto 500 copy propagation 100: x:=6 200: y:=6 300: if (6 > 0) goto 400 400: goto 500 100: x:=6 200: y:=x 300: if (x > 0) goto 400 400: goto 500 constant folding constant propagation 100: x:=6 200: y:=6 300: goto 400 400: goto 500 100: x:=6 200: y:=6 300: goto 500 החלפת קפיצה branch chaining ואם אין יותר שימוש ב-x וב-y, ניתן למחוק גם את שתי הפקודות הראשונות (Dead Code Elimination)
Dead Code Elimination • הגדרה: נאמר כי משתנה x הוא חי בנקודה מסויימת של תכנית אם ייתכן שיש בו שימוש (קריאת ערך) לפני שכותבים אליו מחדש. • כיצד זה יתורגם במונחים של CFG? • אם משתנה x אינו חי מייד אחרי פקודת השמה x:= …, פקודה זו מיותרת. • אופטימיזציית Dead Code Elimination: • מוחקת פקודות השמה שאחריהן מובטח שאין שימוש למשתנה אליו כתבו.
Dead Code Elimination • אחרי כל פקודת השמה צריך לזהות אילו משתנים חיים. • לא מעשי לבצע חישוב כזה עבור כל פקודת השמה וכל משתנה בנפרד. • הפתרון: Data Flow Analysis - חישוב גלובלי שמטרתו לזהות את כל הפקודות / משתנים / ביטויים אשר מקיימים תכונה מסויימת בנקודה מסוימת בתכנית. • DFA משמש לעוד אופטימיזציות רבות.
in B out בעיות DFA • קלט: CFG, כאשר כל צומת הוא בלוק בסיסי • פלט: קבוצות in(B)ו- out(B) לכל צומת B ב-CFG • in(B) - מתייחסת לנק' שלפני הפקודה הראשונה ב-B . • out(B) - מתייחסת לנק' שאחרי הפקודה האחרונה ב-B . • מה מכילות הקבוצות? • תלוי בבעיה - משתנים / השמות / ביטויים וכו' • הערה: בד"כ לאופטימיזציה עצמה נצטרך רק אחת משתי הקבוצות, אבל בשביל החישוב צריך את שתיהן.
שימוש ב DFA לניתוח חיוּת • מטרת החישוב: בכל נקודה p בתכנית, נרצה למצוא את כל המשתנים החיים בה. • עבור כל צומת B ב- CFG (בלוק בסיסי) נגדיר את פריטי המידע הבאים: • in(B) – כל משתני התוכנית שחיים לפני תחילת הבלוק B. • out(B) – כל משתני התוכנית שחיים מיד לאחר הבלוק B.
בעיות DFA - המשך • ההשפעה של B על in/outמושפעת משני מרכיבים: • gen(B) - דברים שנוצרים ב-B. • kill(B) - דברים ש-B"הורג". • במקרה של ניתוח חיוּת נגדיר: gen(B) = { x | B משתנה שנעשה בו שימוש ב x ואין כתיבה לתוכו לפני השימוש } kill(B) = { x | B הוא משתנה אשר מבוצעת אליו כתיבת ערך בx }
בעיות DFA - המשך • צורת החישוב - אחת מהשתיים: • סריקה קדמית - המידע "זורם" קדימה • למשל, בחישוב של reaching definitions (בהרצאה). • דוגמאות נוספות בתרגול הבא. • סריקה אחורית - המידע "זורם" אחורה • למשל, בניתוח חיוּת – שימוש במשתנה יגרום להגדרתו כחי מיד לפני השימוש.
שימוש ב DFA לניתוח חיוּת - המשך gen(B) = { x | B משתנה שנעשה בו שימוש ב x ואין כתיבה לתוכו לפני השימוש } kill(B) = { x | B הוא משתנה אשר מבוצעת אליו כתיבת ערך בx } • נבצע איטרציות על כל הבלוקים B עד להתייצבות של: • אלו הנוסחאות הכלליות עבור סריקה אחורית. • הסריקה תבוצע מהבלוק הסופי להתחלה. in(B) = gen(B) [ out(B) \ kill(B) ] out(B) = in(D) (B,D) CFG
שימוש ב DFA לניתוח חיוּת - המשך • אתחול הפריטים In(B), Out(B) מבוצע תמיד בצורה שמרנית: • במקרה שלנו, במהלך הסריקה האחורית פריטים מתווספים לקבוצה out . • לכן עבור הבלוק הסופי Bfin, נאתחל : out(Bfin) = Ø . • בסוף התוכנית בוודאי אף משתנה אינו חי. out(B) = in(D) (B,D) CFG
שימוש ב DFA לניתוח חיוּת - דוגמא in(B) = gen(B) [ out(B) \ kill(B) ] out(B) = in(D) (B,D) CFG in={ x, z } print(z); z:=12; y:=4; z:=16; if y>0 goto 100; in={ z } out={ x, y, z } in={ x , y } x:=z; print(x); goto 300; print(y); goto 300; out={ x } out={ x } in={ x } print(x); הערה: בדוגמאות מסובכות יותר תיתכן תלות מעגלית. יידרשו הפעלות חוזרות עד להתייצבות. אתחול out={ }
שימוש ב DFA לניתוח חיוּת in(B) = gen(B) [ out(B) \ kill(B) ] out(B) = in(D) (B,D) CFG • התוצאה: לכל בלוק B, out(B) יכיל את קבוצת המשתנים החיים ביציאה מהבלוק. • כעת ניתן עבור כל בלוק בנפרד, לבצע הרצה אחת כדי למצוא את המשתנים החיים בסיום כל פקודת השמה. • היתרון שהושג: • קיבלנו את המשתנים החיים עבור כל הבלוקים ב CFG בבת אחת. • זהו היתרון ב DFA.
סיכום – מה ראינו: • דוגמאות לאופטימיזציות כלליות על ה CFG: • מחיקת פקודות לא ישיגות. • Branch Chaining. • דוגמאות לאופטימיזציות בשיטת DFA: • Dead Code Elimination. • בתרגול הבא: עוד DFA.