C7_programming
Download
Report
Transcript C7_programming
אופטימיזציה של תוכניות
מבוסס על Bryant & O’hallaron / Computer Systems: a programmer’s perspective
הנושאים במצגת זאת:
אופטימיזציות לא תלויות-מכונה
הזזת קוד
החלפת אופרטורים 'יקרים'
צירוף תתי ביטויים משותפים
כוונון בעזרת :profiling
זיהוי צווארי בקבוק בזמני ריצה
סיבוכיות :תיאוריה אל מול מציאות
בדר"כ אנחנו מודדים סיבוכיות אסימפטוטית של אלגוריתמים.
בפועל :אנחנו עובדים על תוכנה שלמה ,לא רק אלגוריתם מבודד.
ניתן לשפר בקבוע – גם זה חשוב!
תכנות יעיל יכול להביא לשיפור ביצועים של פי 10ויותר.
אופטימיזציה במספר רמות :אלגוריתם ,מבנה נתונים ,פרוצדורות
ולולאות.
לא ניתן לעשות זאת ללא הבנה של איך המחשב עובד
כיצד תוכנה מהודרת ומקושרת )(linked
איך למדוד ביצועי תוכנה ולזהות צווארי בקבוק
איך לשפר קוד בלי להרוס את המודולריות שלו והכלליות שלו.
––2
סוגי אופטימיזציות
שתי משפחות של אופטימיזציות:
אופטימיזציות לא תלויות מכונה (הרצאה זאת)
אופטימיזציות תלויות מכונה (שעור הבא)
האפקטיביות מאוד תלויה במבנה הספציפי של המעבד
––3
מהדרים הם מתרגמים מרובי אופטימיזציות
ממפים ביעילות תוכניות לשפת מכונה
הקצאת רגיסטרים
אופטימיזציות 'זולות' לחישוב ,מקומיות ושמרניות.
בדר"כ לא יכולים להשפיע על סיבוכיות אסימפטוטית
עדיין באחריות המתכנת לבחור את האלגוריתם הטוב ביותר
סיבוכיות )…( Oעדיין יותר חשובה משיפורים בפקטור קבוע.
אבל גם אלה חשובים...
מרכיבים רבים בקוד לא מאפשרים להם לעשות אופטימיזציות
מרחיקות ראות.
חשש מ memory aliasing
חשש מ "תופעות לוואי" ( )side-effectsשל פרוצדורות.
––4
מגבלות של מהדרים
נדרשים לעמוד במגבלות חמורות:
אסור להם לשנות את הסמנטיקה של התוכנית תחת כל תנאי.
דבר זה מונע מהם לבצע שינויים שהיו מתגלים כלא נכונים רק במקרים
שלעולם לא קורים בפועל.
התנהגות של התוכנית שהיא מובנת מאליה למתכנת ,יכולה להיות
מוסתרת מפני המהדר בגלל סגנון תכנות
למשל :טווח ערכים במערך בפועל קטן יותר מאשר הטווח המוכרז.
––5
מגבלות של מהדרים
רוב הניתוח המבוצע על ידי המהדר נעשה בתוך הפרוצדורות ולא
ביניהן.
ניתוח של כלל התוכנית הוא לא מעשי בגלל אילוצי זמן.
מבוסס על מידע סטטי
לא יכול לזהות קלט שיתקבל בזמן ריצה ,וכו'.
חייב להיות שמרני!
––6
אופטימיזציות שאינן תלויות מכונה
הזזת קוד )(code motion
הקטן את מספר הפעמים שקוד מורץ
בתנאי שזה לא ישנה את התוצאה
במיוחד כשמדובר בהוצאת קוד מלולאות
{ )0; i < n; i++
;= n*i
)= 0; j < n; j++
;]+ j] = b[j
= for (i
int ni
for (j
a[ni
}
)for (i = 0; i < n; i++
)for (j = 0; j < n; j++
;]a[n*i + j] = b[j
––7
הזזת קוד על ידי המהדר
.בדר"כ מהדרים עושים עבודה טובה בטיפול במערכים ולולאות פשוטות
Code Generated by gcc
for (i =
int ni
int *p
for (j
*p++
}
for (i = 0; i < n; i++)
for (j = 0; j < n; j++)
a[n*i + j] = b[j];
multiply R1,R2
move 8(R3),R4
leal (R4,R2,4),R5
.L40:
move 12(R3),R4
move (R4,R6,4),R2
move R2,(R5)
add $4,R5
increment R6
compare R2 R6
jl .L40
–8–
0; i < n; i++) {
= n*i;
= a+ni;
= 0; j < n; j++)
= b[j];
#
#
#
#
i*n
a
p = a+i*n (scaled by 4)
Inner Loop
#
#
#
#
#
b
b+j (scaled by 4)
*p = b[j]
p++ (scaled by 4)
j++
# loop if j<n
Code Motion דוגמה נוספת ל
Procedure to Convert String to Lower Case
void lower(char *s)
{
int i;
for (i = 0; i < strlen(s); i++)
if (s[i] >= 'A' && s[i] <= 'Z')
s[i] -= ('A' - 'a');
}
. המהדר לא יצליח לפתור, כפי שנראה,את המקרה הזה
...האחריות – על המתכנת
–9–
Lower Case Conversion ביצועים של
.זמן ריבועי באורך המחרוזת
lower1
1000
CPU Seconds
100
10
1
0.1
0.01
0.001
0.0001
256
512
1024
2048
4096
8192
16384
String Length
– 10 –
32768
65536
131072
262144
...goto נתרגם לתוכנית
void lower(char *s)
{
int i = 0;
if (i >= strlen(s))
goto done;
loop:
if (s[i] >= 'A' && s[i] <= 'Z')
s[i] -= ('A' - 'a');
i++;
if (i < strlen(s))
goto loop;
done:
}
– 11 –
מבוצע בכל איטרציהstrlen
'\0' מחפש את: ליניארי באורך המחרוזתstrlen
זמן ריבועי
שיפור הביצועים
void lower(char *s)
{
int i;
int len = strlen(s);
for (i = 0; i < len; i++)
if (s[i] >= 'A' && s[i] <= 'Z')
s[i] -= ('A' - 'a');
}
– 12 –
מחוץ ללולאהstrlen הוצא קריאה ל
.שהרי התוצאה אינה משתנה בין האיטרציות
code motion סוג של
זמני ריצה
זמן ריצה ריבועי לעומת ליניארי
CPU Seconds
1000
100
10
1
0.1
0.01
0.001
0.0001
0.00001
0.000001
256
512
1024
2048
4096
8192
16384
32768
String Length
lower1
– 13 –
lower2
65536
131072 262144
החלפת אופרטורים 'יקרים'
Shift, add instead of multiply or divide
>--
x << 4
ההשפעה תלויות מכונה :בכמה עולה כפל וחילוק
בפנטיום IIכפל של מספר שלם צורך רק .CPU cycles 4חילוק – הרבה
יותר.
16*x
זהה סדרה של הכפלות:
;int ni = 0
{ )for (i = 0; i < n; i++
)for (j = 0; j < n; j++
;]a[ni + j] = b[j
;ni += n
}
)for (i = 0; i < n; i++
)for (j = 0; j < n; j++
;]a[n*i + j] = b[j
קודם השתמשנו בכפל
)(ni = n *i
– – 14
שימוש ברגיסטרים
קריאה וכתיבה לרגיסטרים מהירה בהרבה מקריאה/כתיבה לזיכרון.
המהדר לא יכול לקבוע איזה משתנים כדאי לשמור ברגיסטרים.
נותן קדימות למשתנים זמניים
נותן קדימות למשתנים המוגדרים על ידי המשתמש בעזרת המילה register
– – 15
צרוף ביטויים משותפים
.בדר"כ מהדרים לא מנצלים ידע אריתמטי
/* Sum neighbors of i,j */
up =
val[(i-1)*n + j];
down = val[(i+1)*n + j];
left = val[i*n
+ j-1];
right = val[i*n
+ j+1];
sum = up + down + left + right;
3 multiplications: i*n, (i–1)*n, (i+1)*n
leal -1(R1),R2
multiply R4,R2
leal 1(R1),R3
multiply R4,R3
multiply R4,R1
– 16 –
#
#
#
#
#
i-1
(i-1)*n
i+1
(i+1)*n
i*n
int inj = i*n +
up =
val[inj
down = val[inj
left = val[inj
right = val[inj
sum = up + down
j;
- n];
+ n];
- 1];
+ 1];
+ left + right;
1 multiplication: i*n
:כלים חשובים
:מדידה
חשב זמן ריצה הנצרך על ידי קטעי קוד מסויימים
לרב המעבדים יש מוני שעון
... לא קל להוציא מהם מידע שימושי
profiler: השימוש ב
' וכו, מספר הקריאות לכל פונקציה
gcc –O2 –pg prog. –o prog
./prog
Executes in normal fashion,
but also generates file
gmon.out
gprof prog
Generates profile information
based on gmon.out
– 17 –
visual studio 6.0 :
Project/settings/link: enable
profiling
הדרBuild profile -
דוגמה ל profiling
המטרה
חשב תדירות של מלים במסמך טקסט
הצג רשימה ממוינת מהמילה התדירה ביותר לנדירה ביותר
צעדים:
הפוך ל lowercase
הפעל פונקציית גִּבּוב (.)hash function
נתחיל עם סכום ערכי ASCIIשל המחרוזת.
קרא מלים והכנס לטבלת גבוב.
פונקציה רקורסיבית שמכניסה מלים חדשות בסוף.
עדכן מונה עבור כל מילה
מיין תוצאות
נפעיל אלגוריתם בשם ( .insertion-sortאלגוריתם זה יוצר רשימה חדשה,
כל פעם מוסיף אליה איברים מהרשימה המקורית במקום הנכון).
– – 18
profiling דוגמה ל
:הקלט
Shakespeare’s
most frequent words
29,801
the
27,529
and
21,029
I
20,957
to
18,514
of
15,370
a
14,010
you
12,936
my
11,722
in
11,519
that
– 19 –
Collected works of Shakespeare
946,596 total words, 26,596 unique
שניות9.2 :מימוש ראשוני
ועכשיוProfiling :
הוסף ל executableפונקציות למדידת זמן
מחשב בקירוב זמן בתוך כל פונקציה.
שיטת החישוב
כל זמן מה (לערך )10 msשולח פסיקה
קובע איזו פונקציה כרגע מבוצעת
מעדכן את מונה הזמן ב 10ms
גם מעדכן מונה של מספר הקריאות לפונקציה
– – 20
תוצאות הProfiling -
name
sort_words
lower1
find_ele_rec
h_add
total
ms/call
8210.00
0.00
0.00
0.00
self
ms/call
8210.00
0.00
0.00
0.00
calls
1
946596
946596
946596
self
seconds
8.21
0.55
0.45
0.12
%
cumulative
time
seconds
86.60
8.21
5.80
8.76
4.75
9.21
1.27
9.33
סטטיסטיקת קריאות
מספר קריאות וסה"כ זמן בכל פונקציה
הבעיה בביצועים:
המיון לוקח זמן רב מדי :אלג' לא יעיל.
קריאה בודדת לוקחת 87%מזמן הריצה.
– – 21
אופטימיזציות
10
9
CPU Secs.
8
7
Rest
6
Hash
5
Lower
4
List
3
Sort
2
1
0
Initial
Quicksort
Iter First
Iter Last
Big Table
Better Hash
Linear Lower
החלף פרוצדורת מיון:צעד ראשון
qsort נשתמש בפונקציית הספרייה
– 22 –
אופטימיזציות נוספות
2
1.8
1.6
Hash
1.2
1
Lower
List
0.8
Sort
0.6
CPU Secs.
Rest
1.4
0.4
0.2
0
Linear Lower
Better Hash
Big Table
Iter Last
Iter First
Quicksort
Initial
:Iter firstבמקום פונקציה רקורסיבית – לולאה .מוסיף איבר חדש לראש
הרשימה.
מאט את הקוד
:Iter lastכנ"ל ,אבל הוסף איבר חדש לסוף הרשימה.
מלים נפוצות יותר יהיו קרוב לתחילת הרשימה.
:Big tableטבלת גיבוב גדולה יותר
:Better hashפונקצית גיבוב טובה יותר
:Linear lowerהוצאת strlenמהלולאה
– – 23
מספר מלים על Profiling
יתרונות
עוזר לזהות צווארי בקבוק בביצועים
שימושי בעיקר כשמדובר במערכת מורכבת עם רכיבים רבים.
מגבלות
תלוי קלט
למשל ,הטכניקה של linear lowerלא הראה את כוחו בגלל שרב המלים
הן קצרות.
ייתכן שלא נזהה חוסר יעילות כגון זמן ריצה ריבועי במקום ליניארי.
מנגנון חישוב הזמן לא מאוד מדויק
לא עובד במקרים של תוכניות הרצות פחות מ 3שניות.
– – 24
מדדי זמן
זמן אבסולוטי
10–9 sec :שניות-בדר"כ ננו
סדר גודל מציאותי
Clock Cycles
טווח טיפוסי
100 MHz
108 cycles per second »
Clock period = 10ns »
2 GHz
2 X 109 cycles per second »
Clock period = 0.5ns »
– 25 –
Cycles Per Element (CPE)
רשימת נתונים/ דרך נוחה למדידת ביצועים במקרה של וקטור
Length = n
שתי תוכניות עם הפרשvsum1, vsum2
T = CPE*n + Overhead
.ליניארי בביצועים
1000
900
800
vsum1
Slope = 4.0
700
Cycles
600
500
vsum2
Slope = 3.5
400
300
200
100
0
0
50
100
Elements
– 26 –
150
200
חיבור איברי וקטור:דוגמה
length
data
0 1 2
length–1
Procedures
vec_ptr new_vec(int len)
Create vector of specified length
int get_vec_element(vec_ptr v, int index, int *dest)
Retrieve vector element, store at *dest
Return 0 if out of bounds, 1 if successful
int *get_vec_start(vec_ptr v)
Return pointer to start of vector data
Similar to array implementations in Pascal, ML, Java
E.g., always do bounds checking
– 27 –
המשך דוגמה
void combine1(vec_ptr v, int *dest)
{
int i;
*dest = 0;
for (i = 0; i < vec_length(v); i++) {
int val;
get_vec_element(v, i, &val);
*dest += val;
}
}
מטרה
חשב סכום איברי הוקטור
.dest שמור תוצאה ב
Pentium III Performance: Clock Cycles / Element
– 28 –
42.06 (Compiled -g) 31.25 (Compiled -O2)
)2 אופטימיזציות ברמה:-O2 ,debug information :-g(
...לולאות
void combine1-goto(vec_ptr v, int *dest)
{
int i = 0;
int val;
*dest = 0;
if (i >= vec_length(v))
goto done;
1 iteration
loop:
get_vec_element(v, i, &val);
*dest += val;
i++;
if (i < vec_length(v))
goto loop
done:
}
:חוסר יעילות
נקראת בכל איטרציהvec_length השגרה
.למרות שהתוצאה זהה
– 29 –
אל מחוץ ללולאהvec_length הוצא את
void combine2(vec_ptr v, int *dest)
{
int i;
int length = vec_length(v);
*dest = 0;
for (i = 0; i < length; i++) {
int val;
get_vec_element(v, i, &val);
*dest += val;
}
}
אופטימיזציה
. מהלולאה הפנימיתvec_length הוצא את
. הערך לא משתנה
Code motion
– 30 –
CPE: from 31.25 down to 20.66 (Compiled -O2)
. אבל עם תקורה משמעותית, דורש זמן קבועvec_length
חוסמי אופטימיזציות :פונקציות
מדוע ,בעצם ,לא יכול המהדר להוציא את vec_lenאו את strlen
מחוץ ללולאה הפנימית ?
לפונקציות יכול להיות side effects
ייתכן שהפונקציה משנה את המצב הגלובלי
ייתכן שהיא מחזירה ערכים שונים עבור קריאה עם אותם ארגומנטים.
ייתכן של strlen -יש אינטראקציה עם .lower
מדוע בעצם המהדר לא מסתכל על הקוד של ? vec_len, strlen
אם ההידור הוא לא סטטי ,המקשר ) (linkerעלול לקשר עם גירסה אחרת של
אותה פונקציה.
אופטימיזציות המערבות מספר פרוצדורות אינה מעשית בדר"כ בגלל זמן ריצה.
המהדר מתייחס לפונקציות כ' -קופסה שחורה'
– – 31
עוד אופטימיזציות...
)void combine3(vec_ptr v, int *dest
{
;int i
;)int length = vec_length(v
;)int *data = get_vec_start(v
;*dest = 0
{ )for (i = 0; i < length; i++
;]*dest += data[i
}
אופטימיזציות
הימנע/י מקריאה לפונקציה כדי להביא את האלמנט הבא
מצא מצביע לתחילת הרשימה לפני הלולאה
בתוך הלולאה התייחס למצביע.
( פחות מודולרי ומתאים ל )abstract data types
CPE: 20.66 6.00
קריאות לפונקציות הן יקרות
הורדנו גם את זמן בדיקת האינדקס (בדיקה שהוא בגבולות המערך).
– – 32
הקטן גישות לזיכרון
)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
}
עוד אופטימיזציה...
אין צורך לשמור את התוצאה ב destעד הסיום
המשתנה הלוקאלי sumנשמר ברגיסטר
חוסך קריאה אחת וכתיבה אחת לזיכרון בכל איטרציה.
)CPE: 6.00 2.00 (we started from 31.25
גישה לזיכרון צורכת זמן רב.
– – 33
חוסמי אופטימיזציותMemory Aliasing :
מדוע המהדר לא הפך את combine3ל combine4בעצמו?
התשובה :חשש מ ִ ( Aliasingכנּוי)
שתי התייחסויות שונות לזיכרון מתייחסות לאותו מיקום.
]v: [2, 3, 6
) combine3(v, get_vec_start(v)+2
>--
האיבר האחרון בווקטור עכשיו יאחסן את התוצאה.
][2,3,0] [2,3,2] [2,3,5] [2,3,10
>--
)combine4(v, get_vec_start(v)+2
התוצאה שונה.
][2,3,6] [2,3,6] [2,3,6] [2,3,11
מסקנה combine3 :ו combine4הן לא בהכרח זהות בנוכחות כינויים.
– – 34
חוסמי אופטימיזציותMemory Aliasing :
נשים לב:
ב aliasing , Cנפוץ.
בגלל שניתן לבצע ( address arithmeticכלומר ,חישוב מפורש של כתובת)
יש גישה ישירה למבנה המאחסן את הנתונים.
כדאי להשתמש במשתנים לוקאליים
בעיקר בתוך לולאות
כך המהדר יודע שהוא לא צריך לבדוק .aliasing
– – 35
סיכום :אופטימיזציות שאינן תלויות מכונה.
הזזת קוד ()Code Motion
מהדרים יודעים לעשות זאת במקרים מסוימים :מערכים פשוטים ולולאות
פשוטות.
לא יודעים לעשות זאת בנוכחות של 'כינויים' וקריאות לפונקציות.
החלפת אופרטורים יקרים
Shift, Add, instead of Multiply or Divide
בדר"כ מהדרים מצליחים לעשות זאת לבד ,אבל לא תמיד.
בכל זאת ,תלוי מכונה
כשאפשר ,שמור מידע ברגיסטרים במקום בזיכרון
למהדרים קשה לזהות הזדמנויות ,בגלל חשש לשימוש בכינויים.
איחוד ביטויים משותפים.
למהדרים יש יכולת אלגבראית מוגבלת.
– – 36