1 / 41

אופטימיזציות קומפיילרים

אופטימיזציות קומפיילרים. Delayed branch slots. אם ניזכר במכונת ה- MIPS pipeline (in-order) , הרי שהכרעת פקודות הסיעוף התבצעה בשלב ה- MEM , ולכל פקודת סיעוף כזו ישנם 3 מ"ש אחריה אשר בהם איננו יודעים בוודאות אילו פקודות עלינו להכניס.

Download Presentation

אופטימיזציות קומפיילרים

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. אופטימיזציות קומפיילרים

  2. Delayed branch slots • אם ניזכר במכונת ה-MIPS pipeline (in-order), הרי שהכרעת פקודות הסיעוף התבצעה בשלב ה-MEM, ולכל פקודת סיעוף כזו ישנם 3 מ"ש אחריה אשר בהם איננו יודעים בוודאות אילו פקודות עלינו להכניס. • הפתרון של המכונה עבור בעיה זו הוא לנסות להמר מה תהיה תוצאת ה-branch ולפעול בהתאם.

  3. הקומפיילר יכול להציע לנו פתרון אחר: • אחרי כל פקודת סיעוף לשים 3 תאים (slots). • לעבור על הקוד ולחפש פקודות שיבוצעו בכל מקרה ועצם מיקומם באחד התאים לא יפגע בנכונות התוכנית. • תאים שיוותרו ריקים יוחלפו בפקודת nop • במקרה כזה אין צורך בשום מנגנון חיזוי או טיפול מיוחד בפקודות branch. • 3 תאים יהיה קשה לקומפיילר למלא אבל 2 או 1 יש לו סיכוי טוב יותר. • נקבל איפה שאין לעולם טעויות חיזוי, ואין תשלום penalty על פקודת ה-branch (למעט ה-slots שלא הצלחנו למלא).

  4. דוגמא נתון מעבד MIPS (כפי שנלמד בכיתה) ללא כל מנגנון טיפול בהוראות סיעוף (כלומר המעבד ממשיך להזרים קוד בכל מקרה). לשם שמירה על נכונות, נעזר המעבד בקומפיילר ליצירת בועות (הוראות nop) עבור הוראות BRANCH. לפניך קטע קוד ללא הוראות ה-nop , הוסף לקוד הוראות nop וסדר אותו כך שמספר הבועות יהיה קטן ככל האפשר. על הקוד החדש לשמור על נכונות התוכנית. (ניתן, ואף רצוי, להשתמש בשיטת ה- delayed branch slots), הנח שאין קפיצות משום מקום אחר אל תוך קטע הקוד הבא. L2 ADDIR1, R22, 4 ADDI R20, R20, 5 SW R4, 50(R15) LW R2, 100(R20) ADD R3, R1, R2 BEQ R1, R2, L1 ADDI R10, R10, 1 L1 ADDI R7, R9, 2 SUBI R8, R10, 1 BEQ R7, R8, L2 ADDI R1, R22, 4 ADD R22, R22, R3 ADDI R23, R0, 100

  5. פתרון Original code L2 ADDIR1, R22, 4 ADDI R20, R20, 5 SW R4, 50(R15) LW R2, 100(R20) ADD R3, R1, R2 BEQ R1, R2, L1 ADDI R10, R10, 1 L1 ADDI R7, R9, 2 SUBI R8, R10, 1 BEQ R7, R8, L2 ADDI R1, R22, 4 ADD R22, R22, R3 ADDI R23, R0, 100 Optimized Code ADDI R1, R22, 4 L2 ADDI R20, R20, 5 SW R4, 50(R15) LW R2, 100(R20) BEQ R1, R2, L1 ADD R3, R1, R2 ADDI R7, R9, 2 NOP ADDI R10, R10, 1 L1 SUBI R8, R10, 1 BEQ R7, R8, L2 ADDI R1, R22, 4 ADDI R23, R0, 100 NOP ADD R22, R22, R3 ADD R3, R1, R2 ADDI R7, R9, 2 NOP ADDI R1, R22, 4 ADDI R23, R0, 100 NOP

  6. דרכים למלא תאי הסתעפות Original code L2 ADDIR1, R22, 4 ADDI R20, R20, 5 SW R4, 50(R15) LW R2, 100(R20) ADD R3, R1, R2 BEQ R1, R2, L1 ADDI R10, R10, 1 L1 ADDI R7, R9, 2 SUBI R8, R10, 1 BEQ R7, R8, L2 ADDI R1, R22, 4 ADD R22, R22, R3 ADDI R23, R0, 100 Optimized Code ADDI R1, R22, 4 L2 ADDI R20, R20, 5 SW R4, 50(R15) LW R2, 100(R20) BEQ R1, R2, L1 ADD R3, R1, R2 ADDI R7, R9, 2 NOP ADDI R10, R10, 1 L1 SUBI R8, R10, 1 BEQ R7, R8, L2 ADDI R1, R22, 4 ADDI R23, R0, 100 NOP ADD R22, R22, R3 מלפני הקפיצה ADD R3, R1, R2 ADDI R7, R9, 2 NOP מההמשך המשותף פקודה זהה בשני הסעיפים ADDI R1, R22, 4 ADDI R23, R0, 100 NOP פקודה שלא מפרה נכונות

  7. Basic pipeline scheduling • לצורך הדוגמאות הבאות, נניח שאנו עובדים עם מעבד אחר עם pipeline in-order בן 5 שלבים ללא כל מנגנון חיזוי, וכן נניח latency (מספר מחזורי שעון שממתינים עד הפקודה הבאה) כדלקמן:

  8. דוגמא • נתבונן בקטע הקוד הבא: for (i=1000; i>0; i--) x[i] = x[i] + s; • נתרגם לאסמבלי של MIPS תוך הנחה ש: • 1R מאותחל לכתובת האלמנט האחרון במערך (x[1000]) • 2F מכיל את הערך של s • 2R מאותחל לכתובת של x[0]

  9. for (i=1000; i>0; i--) x[i] = x[i] + s; תרגום: R1  &x[1000] F2  s R2  &x[0] Loop: LD F0,0(R1) ADD F4,F0,F2 SD F4,0(R1) ADDI R1,R1,#-8 (DW=8 bytes) BNE R1,R2,Loop

  10. נראה כיצד תיראה באמת התוכנית (כולל ה-stalls): מ"ש R1  &x[1000] F2  s R2  &x[0] Loop: LD F0,0(R1) 1 stall2 ADD F4,F0,F2 3 stall4 stall5 SD F4,0(R1) 6 ADDI R1,R1,# -8 7 stall8 BNE R1,R2,Loop 9 stall10

  11. R1  &x[1000] F2  s R2  &x[0] Loop: LD F0,0(R1) stall ADD F4,F0,F2 stall stall SD F4,0(R1) ADDI R1,R1,# -8 stall BNE R1,R2,Loop stall אבל אם נתזמן אחרת... Loop: LD F0,0(R1) ADDI R1,R1,#-8 ADD F4,F0,F2 stall BNER1,R2,Loop SD F4,8(R1) //פק' הסיעוףמבוצע תמיד לאחר • נקבל כל איטרציה ב-6 מ"ש במקום 10

  12. Loop unrolling • נשים לב שרק 3 מ"ש מתוך ה-6 באמת מהווים את זמן העבודה של האיטרציה (LD, SD וה-ADD). • שאר 3 המ"ש הם עדכון המונה, פקודת הסיעוף וה-stall שנכפה עלינו. • אם נפרוש את הלולאה כך שהיא תתבצע פחות פעמים ובכל פעם תבצע כמה איטרציות אולי נוכל לצמצם מספר זה.

  13. למשל אם נפרוש 4 איטרציות R1  &x[1000] F2  s R2  &x[0] Loop: LD F0,0(R1) ADD F4,F0,F2 SD F4,0(R1) LD F6,-8(R1) ADD F8,F6,F2 SD F8,-8(R1) LD F10,-16(R1) ADD F12,F10,F2 SD F12,-16(R1) LD F14,-24(R1) ADD F16,F14,F2 SD F16,-24(R1) ADDI R1,R1,# -32 BNE R1,R2,Loop ( ללא התייחסות ל- stalls)

  14. הסבר • הסרנו את הוראות ה-branch ואת הקטנות המונה הפנימיות. • במידה ומספר האיטרציות לא מתחלק בצורה יפה, נחזיק גם לולאה של איטרציות בודדות ואת השארית נריץ שם. • עדיין נקבל stalls:1 עבור כל LD 2 עבור כל ADD 1 עבור ה- ADDI 1 עבור ה-branch • חוץ מה- stalls יש 14 פקודות לכן נקבל 7 מ"ש לאיטרציה (ישנה) • (see next slide)

  15. נבצע אופטימיזציות Loop:LD F0,0(R1) Stall ADD F4,F0,F2 Stall*2 SD F4,0(R1) LD F6,-8(R1) Stall ADD F8,F6,F2 Stall*2 SD F8,-8(R1) LD F10,-16(R1) Stall ADD F12,F10,F2 Stall*2 SD F12,-16(R1) LD F14,-24(R1) Stall ADD F16,F14,F2 Stall*2 SD F16,-24(R1) ADDI R1,R1,# -32 Stall BNE R1,R2,Loop Stall Loop: LD F0,0(R1) LD F6,-8(R1) LD F10,-16(R1) LD F14,-24(R1) ADD F4,F0,F2 ADD F8,F6,F2 ADD F12,F10,F2 ADD F16,F14,F2 SD F4,0(R1) SD F8,-8(R1) ADDI R1,R1,# -32 stall stall SD F12,16(R1)(16-32=-16) BNE R1,R2,Loop SD F16,8(R1) (8-32=-24)

  16. מה קיבלנו? • קיבלנו אפוא קוד עם פחות stalls כך שכל איטרציה מתבצעת בו ב-16 מ"ש. • זה אומר שכל איטרציה ישנה מתבצעת ב-4!!! • החסרון: צריכת האוגרים

  17. Software pipelining • שיטה אחרת להיפטר מ-stalls של תלויות • ראשית נפרוש את גוף הלולאה 3 פעמים: Iteration i: LD F0,16(R1) ADD F4,F0,F2 SD F4,16(R1) Iteration i+1: LD F0,8(R1) ADD F4,F0,F2 SD F4,8(R1) Iteration i+2: LD F0,0(R1) ADD F4,F0,F2 SD F4,0(R1)

  18. RAW WAW WAR • כעת נסמן תלויות: Iteration i: LD F0,16(R1) ADD F4,F0,F2 SD F4,16(R1) Iteration i+1: LD F0,8(R1) ADD F4,F0,F2 SD F4,8(R1) Iteration i+2: LD F0,0(R1) ADD F4,F0,F2 SD F4,0(R1)

  19. RAW WAW WAR • נבחר פקודות שאינן תלויות, אחת מכל איטרציה ישנה: Iteration i: LD F0,16(R1) ADD F4,F0,F2 SD F4,16(R1) Iteration i+1: LD F0,8(R1) ADD F4,F0,F2 SD F4,8(R1) Iteration i+2: LD F0,0(R1) ADD F4,F0,F2 SD F4,0(R1)

  20. ונקבע איטרציה להיות: Loop: SD F4,16(R1) ; stores into x[i] ADD F4,F0,F2 ; adds to x[i-1] LD F0,0(R1) ; loads x[i-2] ADDI R1,R1,# -8 BNE R1,R2, Loop

  21. ונקבע איטרציה להיות: Loop: SD F4,16(R1) ; stores into x[i] ADD F4,F0,F2 ; adds to x[i-1] LD F0,0(R1) ; loads x[i-2] ADDI R1,R1,# -8 BNE R1,R2, Loop

  22. ואחרי scheduling (ע"מ לחסוך stalls): Loop: SD F4,16(R1) ; stores into x[i] ADDI R1,R1,# -8 ADD F4,F0,F2 ; adds to x[i-1] BNE R1,R2, Loop LD F0,8(R1) ; loads x[i-2] מבוצע תמיד לאחר הסיעוף

  23. prolog epilog R1  &x[998] F2  s R2  &x[0] LD F0,16(R1) ADD F4,F0,F2 LD F0,8(R1) • יש לתכנן prolog ו-epilog כדי לשמר נכונות ונקבל: R1  &x[998] F2  s R2  &x[0] LD F0,16(R1) ADD F4,F0,F2 LD F0,8(R1) Loop: SD F4,16(R1) ADDI R1,R1,# -8 ADD F4,F0,F2 BNE R1,R2, Loop LD F0,8(R1) SD F4,16(R1) ADD F4,F0,F2 SD F4,8(R1) מבוצע תמיד לאחר הסיעוף SD F4,16(R1) ADD F4,F0,F2 SD F4,8(R1)

  24. השוואת השיטות • האיטרציה לוקחת 5 מ"ש אם נתעלם מהזנבות • היתרון הגדול של software pipelineעל פני loop unrollingהיא העובדה שאנו לא מנפחים את הקוד • לעומת זאת, loop unrollingמאפשר למכונות רחבות (יותר שלבים ב- pipeline) לנצל טוב יותר את רוחבן (ללא פקודות סיעוף תכופות שקוטעות את הרצף) • במקרים רבים, שילוב של שתי השיטות נותן את הביצועים הטובים ביותר, שכן הן תוקפות את הבעיה מכיוונים שונים.

  25. שיפור ה-hit rate של המטמון

  26. שיפור ה-hit rate של המטמון ע"י קומפיילר • דוגמא הכי פשוטה לאפשרות של קומפיילר לשפר hit rate ניתן לראות בלולאות מקוננות אשר סדר הגישות בהן לזיכרון הוא לא סדרתי • אם המטמון אינו גדול דיו לאחסן את כל הנתונים עלול להיות דפדוף רב של שורות • לעיתים החלפת סדר הלולאות עשוי לפתור את הבעיה

  27. דוגמא • Before for (j=0; j<100; j++) for (i=0; i<5000; i++) x[i][j]=2*x[i][j]; • After for (i=0; i<5000; i++) for (j=0; j<100; j++) x[i][j]=2*x[i][j]; • בבדיקה: הפרש פי 10 על Core2 Xeon

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

  29. לדוגמא – כפל מטריצות N*NX = Y*Z for (i=0; i<N; i++) for (j=0; j<N; j++) { r=0; for (k=0; k<N; k++) r = r + y[i][k] * z[k][j]; x[i][j]=r; };

  30. ברור שמספר ההחטאות במטמון תלוי ב-N ובגודל המטמון • במקרה הגרוע ביותר, עלולים להגיע ל- 2N3+N2 החטאות מתוך N3פעולות • נחלק את המטריצות לבלוקים של B על B, כך שהבלוקים המטופלים בכל עת יהיו קטנים דיו • באופן כללי סדר גודל של גודל מטמון עבור בלוק + 2 שורות של הבלוקים ממטריצות A ו-C אמור להיות אופטימאלי • B מכונה כ-blocking factor • נניח שהמטריצה X מאתחלת לאפסים.

  31. הרעיון: X = Y * Z

  32. הרעיון: Z X = Y *

  33. הרעיון: Z X = Y * בדוגמה הנחנו: 1. N הוא כפולה שלמה של B 2. יש מקום במטמון לארבעה "בלוקי טבלה" של B*B

  34. After for (jj=0; jj<N; jj+B) for (kk=0; kk<N; kk+B) for (i=0; i<N; i++) for (j=jj; j<min(jj+B,N); j++) { r=0; for (k=kk; k<min(kk+B,N); k++) r = r + y[i][k] * z[k][j]; x[i][j]+=r; };

  35. Compiler prefetch • הכוונה היא להכין מידע מהזיכרון מבעוד מועד • ניתן להפריד ל-prefetchingלרגיסטרים או למטמון, וכן בין כאלה שיוצרים פסיקות (faulting) לכאלה שבמקרה של פסיקה (גישה לכתובות אסורות למשל) פשוט הופכות ל-nop(nonfaulting) • אפשר לחשוב על פקודת load רגילה כעל prefetching לרגיסטר מסוג faulting... • רוב המעבדים כיום תומכים ב-nonfaulting prefetchingלמטמון.

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

  37. דוגמא A: 3 100 2 • נניח מערכת עם מטמון נתונים בגודל 8KB, בשיטת 2-way וגודל שורה של 16 בתים • a ו- b הם מערכים של איברים בני 8 בתים. • a הוא בן 3 שורות ו-100 עמודות, • bבן 101 שורות ושתיעמודות. • נניח גם שהמטמון ריק • עבור קטע הקוד הבא, אילו גישות סביר שיגרמו ל-cache misses? for (i=0; i<3; i++) for (j=0; j<100; j++) a[i][j]=b[j][0]+b[j+1][0]; B: 101 …

  38. תשובה for (i=0; i<3; i++) for (j=0; j<100; j++) a[i][j]=b[j][0]+b[j+1][0]; • הגישות ל-a הן לפי הסדר בו יושב המערך בזיכרון ולכן נקבל החטאה רק עם כל מעבר בלוק (כלומר ב-j-ים האי זוגיים) סה"כ 3*(100/2)=150 • הגישות ל-b אינן נהנות מלוקליות. לעומת זאת הגישה חוזרת על עצמה בכל איטרציה של i ונותנת לנו סה"כ 101 החטאות (לערכי j השונים). כל זאת בהנחה שאין התנגשויות עם a. … A: 3 100 2 B: 101 …

  39. כעת נוסיף פקודות prefetch for (j=0; j<100; j++) { // i==0 prefetch(b[j+7][0]); /* b(j,0) for 7 iterations later */ prefetch(a[0][j+7]); /* a(0,j) for 7 iterations later */ a[0][j]=b[j][0]+b[j+1][0]; } for (i=1; i<3; i++) for (j=0; j<100; j++) { prefetch(a[i][j+7]); /* a(i,j) for +7 iterations */ a[i][j]=b[j][0]+b[j+1][0]; }

  40. הערות • נשים לב שבסוף הלולאה אנו חורגים מגבולות המערך (בפקודות ה-prefetch). זה בסדר בתנאי שהפקודות הן nonfaulting • ההנחה היא שה- miss penalty כה גדול כך שיש להתחיל את ה-prefetching 7 איטרציות קודם

  41. ביצועים • הקוד מבצע לנו prefetching עבור a[i][7] עד a[i][99] וכן מ- b[7][0] עד b[100][0]. • החטאות נקבל עבור: • 7 החטאות של b[0][0] - b[6][0] בלולאה הראשונה. • 4 החטאות (7/2) עבור a[0][0] – a[0][6] בלולאה הראשונה. • 4 החטאות (7/2) עבור a[1][0] – a[1][6] בלולאה השנייה. • סה"כ 15 החטאות ללא prefetch. כך שהמחיר עבור התחמקות מ-236cache misses הוא 400 הוראות prefetch, מה שעל פניו נראה כהחלפה טובה

More Related