8.1 צינור ההוראות הרצאה
הקדמה¶
בפרק 0.2 למדנו את הבסיס - המעבד קורא הוראות מהזכרון ומבצע אותן אחת אחרי השנייה. הוראת MOV, אחריה הוראת ADD, אחריה הוראת CMP, וכך הלאה. התיאור הזה נכון מבחינה לוגית, אבל מעבדים מודרניים עובדים בצורה הרבה יותר מורכבת.
אם המעבד היה באמת מסיים הוראה אחת לגמרי לפני שמתחיל את הבאה, הוא היה מבזבז המון זמן. חלקים שונים של המעבד היו יושבים בטל בזמן שחלק אחד עובד. הפתרון? צינור הוראות - instruction pipeline - מנגנון שמאפשר למעבד לעבוד על מספר הוראות בו-זמנית, כל אחת בשלב שונה של הביצוע.
צינור ההוראות הקלאסי - the classic RISC pipeline¶
הרעיון של הצינור דומה לפס ייצור במפעל. במקום שעובד אחד יבנה מכונית שלמה מתחילה ועד סוף, כל עובד מבצע שלב אחד ומעביר את המוצר הלאה. כך כמה מכוניות נמצאות על הפס בו-זמנית, כל אחת בשלב שונה.
במעבדי RISC קלאסיים, כל הוראה עוברת 5 שלבים:
| שלב | שם מקוצר | שם מלא | מה קורה |
|---|---|---|---|
| 1 | IF | Instruction Fetch | המעבד קורא את ההוראה מהזכרון (או מהcache) לפי הכתובת שבIP |
| 2 | ID | Instruction Decode | המעבד מפענח את ההוראה - מזהה איזו פעולה צריך לבצע ומאילו אוגרים לקרוא |
| 3 | EX | Execute | יחידת הALU מבצעת את החישוב (חיבור, חיסור, השוואה, חישוב כתובת) |
| 4 | MEM | Memory Access | אם ההוראה דורשת גישה לזכרון (load/store), הגישה מתבצעת כאן |
| 5 | WB | Write Back | התוצאה נכתבת חזרה לאוגר היעד |
בלי צינור, כל הוראה לוקחת 5 מחזורי שעון (cycles), וההוראה הבאה מתחילה רק אחרי שהקודמת סיימה. עם צינור, ברגע שהוראה מסיימת את שלב הIF ועוברת לID, הוראה חדשה נכנסת לשלב IF. כך בכל רגע נתון יש עד 5 הוראות שונות בתוך הצינור.
דיאגרמת צינור - pipeline diagram¶
הנה דוגמה של 5 הוראות שזורמות בצינור. כל עמודה היא מחזור שעון אחד:
מחזור: 1 2 3 4 5 6 7 8 9
הוראה 1: [IF] [ID] [EX] [MEM] [WB]
הוראה 2: [IF] [ID] [EX] [MEM] [WB]
הוראה 3: [IF] [ID] [EX] [MEM] [WB]
הוראה 4: [IF] [ID] [EX] [MEM] [WB]
הוראה 5: [IF] [ID] [EX] [MEM] [WB]
שימו לב למה שקורה כאן:
- במחזור 5, כל 5 השלבים של הצינור עובדים בו-זמנית - הוראה 1 ב-WB, הוראה 2 ב-MEM, הוראה 3 ב-EX, הוראה 4 ב-ID, הוראה 5 ב-IF.
- בלי צינור, 5 הוראות היו לוקחות 5 x 5 = 25 מחזורים. עם צינור, לקח רק 9 מחזורים.
תפוקה מול חביון - throughput vs latency¶
חשוב להבין נקודה מהותית: הצינור לא מאיץ הוראה בודדת. הוראה אחת עדיין לוקחת 5 מחזורים מתחילתה ועד סופה (זה החביון - latency). מה שהצינור משפר הוא את התפוקה - throughput - כמה הוראות מסתיימות ביחידת זמן.
כשהצינור מלא ועובד בצורה חלקה, הוראה אחת מסתיימת בכל מחזור שעון. כלומר:
- חביון (latency): 5 מחזורים להוראה אחת (לא השתנה)
- תפוקה (throughput): הוראה אחת למחזור (שיפור פי 5!)
זה בדיוק כמו פס ייצור - מכונית אחת עדיין לוקחת שעה לייצר, אבל כל 12 דקות מכונית חדשה יורדת מהפס.
סכנות בצינור - pipeline hazards¶
במציאות הצינור לא תמיד זורם בצורה חלקה. יש מצבים שבהם הוראה לא יכולה להתקדם לשלב הבא כי היא תלויה במשהו שעדיין לא מוכן. מצבים אלה נקראים סכנות - hazards, והם הבעיה המרכזית בתכנון צינור יעיל.
סכנת נתונים - data hazard¶
סכנת נתונים קורית כשהוראה צריכה תוצאה של הוראה קודמת שעדיין לא סיימה. דוגמה:
add eax, ebx ; הוראה 1: eax = eax + ebx
sub ecx, eax ; הוראה 2: ecx = ecx - eax (צריכה את eax החדש!)
הוראה 2 צריכה לקרוא את eax, אבל הוראה 1 תכתוב את התוצאה ל-eax רק בשלב WB (מחזור 5). הוראה 2 מגיעה לשלב ID (קריאת אוגרים) כבר במחזור 3 - שני מחזורים מוקדם מדי!
מחזור: 1 2 3 4 5
add eax, ebx: [IF] [ID] [EX] [MEM] [WB] <-- כותב eax כאן
sub ecx, eax: [IF] [ID] ... <-- צריך eax כאן!
eax עדיין לא מוכן
פתרון 1 - העברה - forwarding (bypassing):
במקום לחכות שהתוצאה תיכתב לאוגר, המעבד מעביר אותה ישירות מפלט שלב EX של הוראה 1 לכניסת שלב EX של הוראה 2. זהו קיצור דרך בחומרה - חוטים שמחברים את הפלט של יחידת הALU ישירות לכניסה שלה.
מחזור: 1 2 3 4 5 6
add eax, ebx: [IF] [ID] [EX] [MEM] [WB]
|
forwarding| (התוצאה מועברת ישירות)
v
sub ecx, eax: [IF] [ID] [EX] [MEM] [WB]
בזכות forwarding, הוראה 2 מקבלת את הערך החדש של eax בזמן - בלי עיכוב.
פתרון 2 - השהייה - stalling (bubbles):
כשforwarding לא מספיק (למשל, כשהוראה 1 היא load מזכרון - התוצאה מוכנה רק אחרי שלב MEM), המעבד מכניס "בועות" - מחזורים ריקים שבהם הצינור עוצר ומחכה:
מחזור: 1 2 3 4 5 6 7
ldr eax, [mem]: [IF] [ID] [EX] [MEM] [WB]
|
| (התוצאה מוכנה רק כאן)
v
sub ecx, eax: [IF] [ID] [--] [EX] [MEM] [WB]
בועה!
הבועה (bubble) היא מחזור מבוזבז - הצינור עוצר לרגע. זו הסיבה שload מזכרון ואחריו שימוש מיידי בתוצאה יוצר עיכוב.
סכנת בקרה - control hazard¶
סכנת בקרה קורית כשיש הוראת הסתעפות (branch) - כמו jz, jne, jmp - שמשנה את זרימת התוכנית. הבעיה: בזמן שהוראת ההסתעפות עדיין נמצאת בשלב ID או EX (וטרם חושב התנאי), הצינור כבר הספיק לטעון הוראות מהכתובת הבאה. אם ההסתעפות כן מתקיימת, ההוראות שנטענו הן שגויות!
cmp eax, 0
jz label ; אם eax == 0, קפוץ ל-label
add ebx, 1 ; נטען לצינור - אבל אולי לא צריך לרוץ!
mov ecx, 5 ; גם זה נטען מוקדם מדי
label:
sub edx, 1
פתרון 1 - חיזוי הסתעפויות - branch prediction:
המעבד מנחש לאן ההסתעפות תלך, וממשיך לטעון הוראות מהכתובת המנוחשת. אם ניחש נכון - מעולה, אין עיכוב. על חיזוי הסתעפויות נרחיב בפרק 8.2.
פתרון 2 - שטיפת צינור - pipeline flush:
אם הניחוש היה שגוי, המעבד צריך לשטוף את כל ההוראות השגויות מהצינור ולהתחיל מחדש מהכתובת הנכונה. כל העבודה שנעשתה על ההוראות השגויות הולכת לפח. בצינור של 5 שלבים זה אומר אובדן של 2-3 מחזורים. בצינור עמוק של 15-20 שלבים, זה אובדן של 15-20 מחזורים - מחיר כבד מאוד!
סכנת מבנה - structural hazard¶
סכנת מבנה קורית כששתי הוראות צריכות את אותו רכיב חומרה בו-זמנית. דוגמה קלאסית: הוראה אחת נמצאת בשלב IF (צריכה לקרוא הוראה מהזכרון) בזמן שהוראה אחרת בשלב MEM (צריכה לקרוא/לכתוב נתונים מהזכרון). אם יש רק יציאה אחת לזכרון, הן לא יכולות לעבוד בו-זמנית.
פתרון: שכפול חומרה
הפתרון הנפוץ הוא לשכפל את הרכיב הבעייתי. למשל:
- מטמון הוראות ומטמון נתונים נפרדים (L1I ו-L1D) - כך שלב IF קורא ממטמון אחד ושלב MEM קורא ממטמון אחר, ללא התנגשות
- יציאות קריאה מרובות בregister file - מאפשרות לכמה הוראות לקרוא אוגרים בו-זמנית
- יחידות חישוב מרובות - כמה ALU-ים, כך שכמה הוראות יכולות להיות בשלב EX בו-זמנית (נושא שנרחיב בפרק 8.4 על ביצוע superscalar)
מעבדי x86 מודרניים - צינור עמוק¶
הצינור של 5 שלבים שראינו הוא מודל פשוט ומוקדם. מעבדי x86 מודרניים של Intel ו-AMD משתמשים בצינורות הרבה יותר עמוקים:
| מעבד | שנה (בערך) | עומק צינור |
|---|---|---|
| Intel Pentium | 1993 | 5 שלבים |
| Intel Pentium III | 1999 | 10 שלבים |
| Intel Pentium 4 (Northwood) | 2002 | 20 שלבים |
| Intel Pentium 4 (Prescott) | 2004 | 31 שלבים! |
| Intel Core (Skylake) | 2015 | 14-19 שלבים |
| AMD Zen 4 | 2022 | 19 שלבים |
למה צינור עמוק יותר? כי כל שלב עושה פחות עבודה, מה שמאפשר תדר שעון (clock frequency) גבוה יותר. ה-Pentium 4 עם 31 שלבים הגיע ל-3.8 GHz - תדר גבוה מאוד לתקופתו.
אבל יש מחיר: ככל שהצינור עמוק יותר, המחיר של branch misprediction גדל (יותר הוראות לזרוק בshutdown), ויותר מקומות שבהם יכולים להיווצר hazards. ב-Pentium 4 Prescott עם 31 שלבים, branch misprediction עלתה 31 מחזורים! Intel למד את הלקח וחזר לצינורות קצרים יותר (14-19 שלבים) בארכיטקטורת Core.
CISC מול RISC¶
RISC - Reduced Instruction Set Computer¶
ארכיטקטורות כמו ARM ו-RISC-V הן ארכיטקטורות RISC:
- הוראות באורך קבוע (32 ביט בARM)
- מספר קטן של צורות הוראה פשוטות
- רק הוראות load/store ניגשות לזכרון - פעולות חישוב עובדות רק על אוגרים
- קל לפענח, קל לבנות צינור יעיל
CISC - Complex Instruction Set Computer¶
ארכיטקטורת x86 היא CISC:
- הוראות באורך משתנה (1 עד 15 בתים ב-x86!)
- הוראות מורכבות שיכולות לבצע כמה פעולות (למשל add eax, [ebx+ecx*4+8] שקוראת מזכרון ומחברת במכה אחת)
- מצבי כתובת (addressing modes) רבים ומורכבים
הסוד של x86 מודרני - פירוק מיקרו - micro-op decomposition¶
כאן מגיע החלק המעניין באמת. מעבדי x86 מודרניים (מאז ה-Pentium Pro ב-1995) הם RISC מבפנים. בשלב הדקוד, המעבד מפרק כל הוראת CISC מורכבת לסדרה של מיקרו-פעולות - micro-ops (uops) - הוראות פנימיות פשוטות בדומה להוראות RISC.
לדוגמה, ההוראה:
מפורקת בתוך המעבד לכמה uops:
1. חישוב הכתובת: ebx + ecx*4 + 8
2. קריאה מהזכרון: load temp, [address]
3. חיבור: add eax, temp
ה-uops הפנימיים זורמים בצינור הפנימי בדיוק כמו הוראות RISC - אורך קבוע, פשוטים, קלים לתזמן. כך מעבדי x86 נהנים משני העולמות:
- תאימות לאחור - אפשר להריץ את אותו קוד x86 מאז 1978
- ביצועים גבוהים - הליבה הפנימית היא RISC-like עם צינור יעיל
זו הסיבה שכשנדבר על ביצועים של מעבדי x86 מודרניים, לעתים קרובות נדבר על uops ולא על הוראות x86 - כי ה-uops הם מה שבאמת זורם בצינור.
סיכום¶
| מושג | הסבר |
|---|---|
| צינור הוראות - pipeline | חפיפה בין שלבי ביצוע של הוראות שונות |
| תפוקה - throughput | כמה הוראות מסתיימות ביחידת זמן (משתפרת) |
| חביון - latency | כמה זמן לוקח להוראה אחת (לא משתנה) |
| סכנת נתונים - data hazard | תלות בתוצאה של הוראה קודמת |
| העברה - forwarding | קיצור דרך בחומרה לתוצאות |
| סכנת בקרה - control hazard | הסתעפות שמשנה את זרימת ההוראות |
| שטיפת צינור - pipeline flush | זריקת הוראות שנטענו בטעות |
| סכנת מבנה - structural hazard | שני שימושים באותו רכיב חומרה |
| מיקרו-פעולות - micro-ops | הוראות RISC פנימיות שx86 מפורק אליהן |
בפרק הבא (8.2) נעמיק בחיזוי הסתעפויות - אחד המנגנונים הקריטיים ביותר בביצועי מעבדים מודרניים.