9.4 אופטימיזציות של הקומפיילר תרגול
תרגול - אופטימיזציות של הקומפיילר¶
תרגיל 1 - זיהוי אופטימיזציות¶
לכל קטע קוד, קמפלו עם -O0 -S ועם -O2 -S והשוו את האסמבלי. זהו אילו אופטימיזציות הקומפיילר ביצע.
קטע א:
קטע ב:
קטע ג:
static int double_it(int x) { return x * 2; }
int inline_demo(int n) {
return double_it(n) + double_it(n + 1);
}
קטע ד:
void loop_demo(int *arr, int n, int factor) {
for (int i = 0; i < n; i++) {
arr[i] = factor * 3 + i;
}
}
לכל קטע:
1. קמפלו עם gcc -O0 -S -o code_O0.s code.c
2. קמפלו עם gcc -O2 -S -o code_O2.s code.c
3. השוו את האסמבלי וזהו את האופטימיזציות
תרגיל 2 - volatile בפעולה¶
צרו את הקובץ הבא:
// volatile_test.c
int regular_loop() {
int flag = 1;
int count = 0;
while (flag) {
count++;
if (count >= 1000000) {
flag = 0;
}
}
return count;
}
int volatile_loop() {
volatile int flag = 1;
int count = 0;
while (flag) {
count++;
if (count >= 1000000) {
// flag יכול להשתנות מבחוץ (למשל על ידי signal handler)
// ב"אמת" היינו בודקים תנאי חיצוני
flag = 0;
}
}
return count;
}
- קמפלו עם
-O2 -Sובדקו את האסמבלי שלregular_loop. האם הקומפיילר שמר אתflagבמשתנה? - בדקו את האסמבלי של
volatile_loop. מה ההבדל? - הסבירו: למה בלי
volatileהקומפיילר יכול לבצע אופטימיזציות שישנו את ההתנהגות? - נסו להוסיף שורה
printf("%d\n", flag);בתוך הלולאה שלregular_loop. מה משתנה באסמבלי? למה?
תרגיל 3 - השוואת ביצועים בין רמות אופטימיזציה¶
כתבו תוכנית שמבצעת חישוב כבד וימדדו את הזמן:
// perf.c
#include <stdio.h>
#include <time.h>
#define N 100000000
long long sum_squares(int n) {
long long sum = 0;
for (int i = 0; i < n; i++) {
sum += (long long)i * i;
}
return sum;
}
int main() {
clock_t start = clock();
long long result = sum_squares(N);
clock_t end = clock();
double elapsed = (double)(end - start) / CLOCKS_PER_SEC;
printf("Result: %lld\n", result);
printf("Time: %.3f seconds\n", elapsed);
return 0;
}
- קמפלו והריצו ב-4 רמות:
-O0,-O1,-O2,-O3. רשמו את הזמן של כל רמה. - השוו את האסמבלי של
sum_squaresב--O0מול-O3. מה ההבדלים? - נסו גם עם
-Os(אופטימיזציה לגודל). מה הזמן? מה גודל הקובץ לעומת-O3? - נסו עם
-O3 -march=native(שימוש בכל הוראות המעבד המקומי). האם יש שיפור נוסף?
תרגיל 4 - tail call optimization¶
// tail.c
#include <stdio.h>
// גרסה רגילה (לא tail call)
int factorial_regular(int n) {
if (n <= 1) return 1;
return n * factorial_regular(n - 1); // כפל אחרי הקריאה - לא tail call
}
// גרסה tail-call
int factorial_tail(int n, int acc) {
if (n <= 1) return acc;
return factorial_tail(n - 1, n * acc); // הקריאה היא הדבר האחרון
}
- קמפלו עם
-O2 -Sובדקו את האסמבלי של שתי הפונקציות. - בגרסה של
factorial_tail- האם הקומפיילר הפך את הרקורסיה ללולאה? חפשוcallמולjmp. - בגרסה של
factorial_regular- האם גם כאן הקומפיילר הצליח לבצע אופטימיזציה? - כתבו תוכנית שקוראת ל-
factorial_regular(1000000). מה קורה? (רמז: stack overflow). ואז נסו עםfactorial_tail(1000000, 1)ב--O2- מה קורה?
תרגיל 5 - LTO - אופטימיזציית זמן לינקוג'¶
צרו שני קבצים:
// math_ops.c
int square(int x) {
return x * x;
}
int cube(int x) {
return x * x * x;
}
int unused_function(int x) {
return x + 42;
}
// main.c
#include <stdio.h>
int square(int x);
int cube(int x);
int main() {
int a = square(5);
int b = cube(3);
printf("square(5)=%d, cube(3)=%d\n", a, b);
return 0;
}
-
קמפלו בלי LTO:
בדקו עםnm no_lto- האםunused_functionנמצא בקובץ? האםsquareו-cubeהוטמעו? -
קמפלו עם LTO:
gcc -O2 -flto -c math_ops.c -o math_ops_lto.o gcc -O2 -flto -c main.c -o main_lto.o gcc -O2 -flto math_ops_lto.o main_lto.o -o with_lto
בדקו עםnm with_lto- האםunused_functionנמצא? האםsquareו-cubeהוטמעו? -
השוו את גודל שני הקבצים:
ls -la no_lto with_lto - בדקו את האסמבלי של
mainבשני המקרים עםobjdump -d. מה ההבדל?