1 / 29

אופטימיזציות תלויות מכונה של תוכניות

אופטימיזציות תלויות מכונה של תוכניות. מבוסס על Bryant & O’hallaron / Computer Systems: a programmer’s perspective. עד עתה. void combine4(vec_ptr v, int *dest) { int i; int length = vec_length(v); int *data = get_vec_start(v); int sum = 0; for (i = 0; i < length; i++)

kolya
Télécharger la présentation

אופטימיזציות תלויות מכונה של תוכניות

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. אופטימיזציות תלויות מכונה של תוכניות מבוסס על Bryant & O’hallaron / Computer Systems: a programmer’s perspective

  2. עד עתה... void combine4(vec_ptr v, int *dest) { int i; int length = vec_length(v); int *data = get_vec_start(v); int sum = 0; for (i = 0; i < length; i++) sum += data[i]; *dest = sum; } • המטרה • חשב את סכום איברי הוקטור • Vector represented by C-style abstract data type • Achieved CPE of 2.00 • CPE = Cycles per element

  3. טיפוסים נשתמש בהגדרות שונות בשביל data_t int float double פעולות נשתמש בהגדרות שונות בשביל OP ו IDENT + / 0 * / 1 נהפוך את הקוד ליותר כללי void abstract_combine4(vec_ptr v, data_t *dest) { int i; int length = vec_length(v); data_t *data = get_vec_start(v); data_t t = IDENT; for (i = 0; i < length; i++) t = t OP data[i]; *dest = t; }

  4. Method Integer Floating Point + * + * Abstract - g 42.06 41.86 41.44 160.00 Abstract - O2 31.25 33.25 31.25 143.00 Move vec_length 20.66 21.25 21.15 135.00 data access 6.00 9.00 8.00 117.00 Accum. in temp 2.00 4.00 3.00 5.00 אופטימיזציות לא תלויות מכונה • אופטימיזציות • הפחת גישות לזיכרון וקריאות לפונקציות בתוך הלולאה. • משהו קורה כאן מבחינת ביצועים... • מכפלה של FP איטית במיוחד. אבל הבעיה נפתרת כשמשתמשים במשתנה זמני... מדוע ? • נובע ממוזרות מסוימת ב IA32 • הזיכרון משתמש ב 64 ביט. גלישה מטופלת בתוכנה, ולכן איטית מאוד. • הרגיסטרים משתמשים ב 80 ביט. יוצרים גלישה רק כשמועברים לזיכרון. • הקלט שנבדק יצר גלישה ב 64 ביט, אבל לא ב 80 ביט. התוצאה משתנה!

  5. שימוש במצביעים void combine4p(vec_ptr v, int *dest) { int length = vec_length(v); int *data = get_vec_start(v); int *dend = data+length; int sum = 0; while (data < dend) { sum += *data; data++; } *dest = sum; } • אופטימיזציה: • מצביעים במקום גישות למערך. במקרה הזה מאפשר גם להיפטר מהמונה i. • לא בהכרח עוזר. מאוד רגיש למבנה המדויק של המעבד. • בדר"כ מהדרים טובים יותר באופטימיזציות של מערכים.

  6. ארכיטקטורה של מעבד מודרני Instruction Control Address Control Unit (CU) Fetch Control Instruction Cache Retirement Unit Instrs. Register File Instruction Decode Operations Register Updates Prediction OK? Execution Arithmetic Logic Unit (ALU) Functional Units Integer/ Branch General Integer FP Add FP Mult/Div Load Store Operation Results Addr. Addr. Data Data Data Cache

  7. היכולות של Pentium III • ניתן להריץ במקביל: • 1 load • 1 store • 2 integer (one may be branch) • 1 FP Addition • 1 FP Multiplication or Division • Some Instructions Take > 1 Cycle, but Can be Pipelined • InstructionLatencyCycles/Issue • Load / Store 3 1 • Integer add 1 1 • Integer Multiply 4 1 • Integer Divide 36 36 • Double/Single FP Multiply 5 2 • Double/Single FP Add 3 1 • Double/Single FP Divide 38 38 חלקים שונים של אותה פקודה מבוצעים על ידי רכיבים שונים. לכן ניתן לבצע מספר רכיבים של פקודות שונות בו זמנית.

  8. אופטימיזציה: פרישת לולאות (Loop Unrolling) void combine5(vec_ptr v, int *dest) { int length = vec_length(v); int limit = length-2; int *data = get_vec_start(v); int sum = 0; int i; /* Combine 3 elements at a time */ for (i = 0; i < limit; i+=3) { sum += data[i] + data[i+1] + data[i+2]; } /* Finish any remaining elements */ for (; i < length; i++) { sum += data[i]; } *dest = sum; } • אופטימיזציה: • צרף מספר איטרציות לתוך גוף הלולאה. • חוסך תקורה של הלולאה. • pipelining של פקודת load – עד 3 פעולות. • 'קשור קצוות' בסוף. • Measured CPE = 1.33 • בנוסף: • בלוק בסיסי עם יותר פעולות:יותר חופש פעולה למהדר לבצע אופטימיזציות. • למשל:ביצוע במקביל של פעולות בסיסיות אם הן בלתי תלויות.

  9. הצנרה (pipelining) של load Load קיבלנו 3פעולות Load תוך 5 מחזורי שעון Load ציר הזמן add Load add add

  10. ההשפעה של Unrolling • בדוגמה שלנו עוזר רק לחיבור integers • במקרים האחרים האלמנט הדומיננטי הוא ה latency של הפעולות (שקף הבא). • ההשפעה היא לא ליניארית או מונוטונית. • גורמים רבים משפיעים על השיבוץ בפועל של הפעולות.

  11. Multiply Multiply Multiply הצנרה (pipelining) של load Load כאן הצנרה לא עוזרת... Load ציר הזמן Load

  12. 1 x0 * x1 * x2 * x3 * x4 * x5 * x6 * x7 * x8 * x9 * x10 * x11 * חישוב סדרתי • חישוב ((((((((((((1 * x0) * x1) * x2) * x3) * x4) * x5) * x6) * x7) * x8) * x9) * x10) * x11) • ביצועים • N elements, D cycles/operation • N*D cycles

  13. אופטימיזציה: פרישת לולאות מקביליתParallel Loop Unrolling void combine6(vec_ptr v, int *dest) { int length = vec_length(v); int limit = length-1; int *data = get_vec_start(v); int x0 = 1; int x1 = 1; int i; /* Combine 2 elements at a time */ for (i = 0; i < limit; i+=2) { x0 *= data[i]; x1 *= data[i+1]; } /* Finish any remaining elements */ for (; i < length; i++) { x0 *= data[i]; } *dest = x0 * x1; } • גרסה: הכפלת שלמים • אופטימיזציה: • סכום בשתי מכפלות בלתי תלויות. • ניתן לביצוע במקביל. • הכפל אותם בסוף. • ביצועים: • CPE = 2.0 • המהירות הוכפלה (עבור מכפלתint )

  14. 1 x0 1 x1 * x2 * x3 * x4 * x5 * x6 * x7 * x8 * x9 * x10 * x11 * * חישוב שתי מכפלות במקביל • חישוב: ((((((1 * x0) * x2) * x4) * x6) * x8) * x10) * ((((((1 * x1) * x3) * x5) * x7) * x9) * x11) • ביצועים: • N elements, D cycles/operation • (N/2+1)*D cycles • ~2X performance improvement *

  15. דרישות ליצירת ריצה מקבילית • דרישות מתמטיות: • הפעולות צריכות להיות אסוציאטיביות וקומוטטיביות • מכפלת שלמים – בסדר. • לא תמיד נכון עבור floating-point • בסדר ברוב האפליקציות. • דרישות מהחומרה: • Pipelined functional units • המהדר +המעבד צריכים להיות מסוגלים לזהות את האפשרות למקביליות מתוך הקוד. • נקרא out-of-order execution.

  16. כיצד נוצרת ריצה מקבילית ? • ב IA-32 אין פקודות מפורשות שאומרות למעבד איזה פקודות ניתן לבצע באופן בלתי תלוי. • ב IA-64 דווקא יש. • למעבד יש יכולת מסוימת להסתכל קדימה – על 10~ הפקודות הבאות. • יכול לעתים להבחין שיש פקודה בהמשך הדרך שאינה תלויה בתוצאה של החישוב שמתבצע כרגע.

  17. Optimization Results for Combining

  18. שיטה #2Parallel Unrolling void combine6aa(vec_ptr v, int *dest) { int length = vec_length(v); int limit = length-1; int *data = get_vec_start(v); int x = 1; int i; /* Combine 2 elements at a time */ for (i = 0; i < limit; i+=2) { x *= (data[i] * data[i+1]); } /* Finish any remaining elements */ for (; i < length; i++) { x *= data[i]; } *dest = x; } • גירסת קוד: הכפלת שלמים • אופטימיזציה • הכפל זוגות, ואז עדכן והשלם תוצאה. • “Tree height reduction” • Performance • CPE = 2.5

  19. x8 x4 x2 x6 x0 x3 x5 x1 x7 x9 1 * * * * * * * * * x10 x11 * * * שיטה #2 • חישוב: ((((((1 * (x0 * x1)) * (x2 * x3)) * (x4 * x5)) * (x6 * x7)) * (x8 * x9)) * (x10 * x11)) • ביצועים: • N elements, D cycles/operation • Should be (N/2+1)*D cycles • CPE = 2.0 • Measured CPE worse

  20. מקביליות • CPE = 4.00 • מכפלות מבוצעות באופן סדרתי /* Combine 2 elements at a time */ for (i = 0; i < limit; i+=2) { x = (x * data[i]) * data[i+1]; } /* Combine 2 elements at a time */ for (i = 0; i < limit; i+=2) { x = x * (data[i] * data[i+1]); } data[2]*data[3] במקביל ל X * (data[0]*data[1]) • CPE = 2.50 • חלק מהמכפלות - במקביל

  21. מגבלות של ביצוע מקבילי • דרושים רגיסטרים רבים • כדי להחזיק תוצאות של מכפלות / סכומים. • יש רק 6 רגיסטרים שמישים ל - Integers • דרושים גם בשביל מצביעים, תנאי לולאה, וכו'. • 8 רגיסטרים ל floating-point • כשאין מקום ברגיסטרים, המידע מועבר לזיכרון הראשי (ל – stack). • נקרא register-spilling • מבטל את האפקטיביות של האופטימיזציה.

  22. סִכּוּם:תוצאות על Pentium III • ההאצה הגדולה ביותר בשל האופטימיזציות הבסיסיות. • אבל, גם ההאצות הקטנות חשובות.

  23. סכום:תוצאות על מחשב Alpha • דומה ל Pentium 3 • למרות שהמבנה הפנימי והמהדר שונים לחלוטין.

  24. תוצאות עבור Pentium 4 • מהירות שעון גבוהה יותר 2.0 GHz • לא נוצרת הבעיה בגלישה של FP.

  25. הסתעפויות • בעיה • ה CU חייב להיות לפני ה ALU (או ה execution unit) כדי לייצר משימות מספיק מהר כך ש ה ALU ינוצל באופן מקסימלי. • מה ה CU צריך לעשות כשמגיע להסתעפות בתוכנית ? • תנאי ההסתעפות טרם חושב! • להיכן עליו להתקדם ? • התשובה:חיזוי (branch predication) • חיזוי יכול גם להיות שגוי... • העלות של חיזוי שגוי: • על pentium III: ~14 clock cycles

  26. לעתים ניתן להימנע מהסתעפויות... • שימוש ב'מסכות' • הרעיון הוא לנסות למנוע branch prediction במקומות בהם: • נקרא הרבה פעמים • אין עדיפות לכיוון מסוים של ה if (לכן בדר"כ לא רלבנטי ללולאות שם יש עדיפות). העלות של branch miss גבוהה מדי. int bmax(int x, int y) { int mask = -(x>y); return (mask & x) | (~mask & y); }

  27. לעתים ניתן להימנע מהסתעפויות... • שימוש ב'מסכות' • במקרה הספציפי הזה gcc הופך את זה חזרה ל if... • ניתן לפתור על ידי הכרזת volatile int mask. • מכריח את mask להישמר בזיכרון ולא ברגיסטר. מכריח את המעבד לחשב את הערך לפני שממשיך. • תקורה: 22clock cycles. int bmax(int x, int y) { volatile int t = (x>y); int mask = -t; return (mask & x) | (~mask & y); }

  28. לסיכום:אופטימיזציות תלויות מכונה • Loop Unrolling • חלק מהמהדרים עושים זאת אוטומטית • בדר"כ באופן ידני ניתן לעשות טוב יותר • מקביליות (Instruction-Level Parallelism) • מאוד תלוי מכונה

  29. תפקידו של המפתח... כיצד אכתוב את התוכנית שלי, כשיש לי מהדר טוב ? • שמרו על איזון בין יעילות לקריאוּת ויכולת תחזוקה • כמו-כן: • בחרו באלגוריתם הטוב ביותר. • כתבו קוד קריא וניתן לתחזוקה. • הימנעו ככל האפשר מ'חוסמי אופטימיזציות' • כך תאפשרו למהדר לעשות את העבודה שלו. • הדגש הוא על לולאות פנימיות • בצעו אופטימיזציות חזקות ככל האפשר – כאן הסיכוי הגדול היותר להאצה.

More Related