מה המעבד באמת עושה

הסבר מעשי על Fetch, Decode, Execute, אוגרים, Cache, Pipeline, IPC, ליבות, GHz ולמה לולאת Python אחת לא משתמשת אוטומטית בכל המעבדים.

פורסם 2026-07-11. תגיות: CPU, Performance, Python.

כשאנחנו כותבים תוכנה, קל לחשוב שהמחשב פשוט "מריץ את הקוד". בפועל, מאחורי כל שורת קוד עומדת שרשרת שלמה של תהליכים, שבסופה המעבד מבצע הוראות מכונה פשוטות מאוד.

המעבד, או CPU, הוא הרכיב שאחראי על ביצוע ההוראות של התוכניות הפועלות במחשב. הוא מבצע חישובים, משווה ערכים, מעביר נתונים, מקבל החלטות ומתקדם בין הוראות התוכנית.

אבל המעבד אינו מבין ישירות קוד Python, Java או JavaScript. הוא מבין רק את סט ההוראות של הארכיטקטורה שלו, למשל ARM64 או x86-64.

כיצד המעבד מבצע הוראה?

באופן מפושט, ביצוע הוראה עובר דרך כמה שלבים:

Fetch -> Decode -> Execute -> Write Back

Fetch - הבאת ההוראה

המעבד צריך לדעת מהי ההוראה הבאה שעליו לבצע.

לשם כך קיים אוגר מיוחד בשם Program Counter, או בקיצור PC. הוא מחזיק את כתובת ההוראה הבאה.

בשלב ה-Fetch המעבד מביא את ההוראה שנמצאת בכתובת שאליה מצביע ה-PC. בדרך כלל ההוראה מגיעה מה-Instruction Cache, ואם היא אינה נמצאת שם, ייתכן שיהיה צורך להביא אותה מרמה נמוכה יותר בהיררכיית הזיכרון.

לאחר הבאת ההוראה, ה-PC מתקדם בדרך כלל לכתובת ההוראה הבאה. בקפיצה, קריאה לפונקציה או חזרה מפונקציה, ערכו עשוי להשתנות לכתובת אחרת.

Decode - פענוח ההוראה

בשלב ה-Decode המעבד מזהה:

לדוגמה:

ADD R3, R1, R2

משמעות ההוראה היא לחבר את הערך שב-R1 לערך שב-R2 ולשמור את התוצאה ב-R3.

Execute - ביצוע

בשלב ה-Execute יחידת הביצוע מבצעת את הפעולה בפועל.

אם:

R1 = 10
R2 = 7

אז יחידת החישוב מבצעת:

10 + 7 = 17

המעבד עשוי גם לעדכן דגלי מצב, למשל:

Write Back - כתיבת התוצאה

לאחר החישוב, התוצאה נכתבת בדרך כלל לאוגר היעד:

R3 = 17

חשוב להבדיל בין כתיבה לאוגר לבין כתיבה לזיכרון. הוראת חיבור רגילה אינה חייבת לכתוב ל-RAM. כדי לכתוב תוצאה מהאוגר לזיכרון נדרשת בדרך כלל הוראת `STORE`.

מה תפקיד האוגרים?

האוגרים הם אזורי אחסון קטנים ומהירים מאוד שנמצאים בתוך המעבד.

הם משמשים לשמירת:

זרימת הנתונים הנפוצה היא:

RAM או Cache
↓
Registers
↓
יחידת הביצוע
↓
Registers
↓
לעיתים STORE חזרה לזיכרון

המעבד מעדיף לעבוד עם אוגרים משום שהם מהירים בהרבה מה-RAM.

למה צריך Cache?

המעבד מהיר בהרבה מזיכרון ה-RAM. אילו היה צריך להמתין ל-RAM לפני כל הוראה, הוא היה מבזבז חלק גדול מזמנו בהמתנה.

לכן קיימות כמה רמות של זיכרון מטמון:

L1 -> L2 -> L3 -> RAM

ככל שהזיכרון קרוב יותר לליבה, הוא בדרך כלל מהיר יותר אך קטן יותר.

ה-Cache שומר נתונים והוראות שסביר שהמעבד יצטרך בקרוב. הוא מסתמך בין היתר על שני עקרונות:

Cache יעיל יכול לגרום למעבד בעל תדר נמוך יותר לנצח מעבד בעל תדר גבוה יותר.

מהו Pipeline?

ה-Pipeline הוא מנגנון פנימי של המעבד שמאפשר לכמה הוראות להימצא בשלבי ביצוע שונים במקביל.

לדוגמה, בזמן שהוראה אחת נמצאת בשלב Execute, הוראה אחרת יכולה להיות בשלב Decode והוראה נוספת בשלב Fetch.

אפשר לדמות זאת למפעל:

הוראה א': Execute
הוראה ב': Decode
הוראה ג': Fetch

אין פירוש הדבר שכל הוראה מסתיימת מיד או שכל ההוראות פועלות ללא תלות. תלות בין הוראות, החטאת Cache או חיזוי הסתעפות שגוי עלולים ליצור עיכובים.

מעבדים מודרניים משתמשים גם בטכניקות כמו:

כל אלה משפיעים על מספר ההוראות שהמעבד מצליח להשלים בפועל.

מדוע GHz אינו מדד מספיק?

GHz מציין את מספר מחזורי השעון שהמעבד מבצע בכל שנייה.

מעבד בתדר של 4GHz מבצע בקירוב ארבעה מיליארד מחזורים בשנייה, אך מחזור שעון אינו בהכרח הוראה אחת.

מדד נוסף הוא IPC:

IPC = Instructions Per Cycle

כלומר, כמה הוראות המעבד משלים בממוצע בכל מחזור.

בקירוב:

ביצועי ליבה ≈ תדר × IPC

לדוגמה:

מעבד א': 4GHz × IPC 2 = 8
מעבד ב': 3GHz × IPC 3 = 9

למרות שהתדר של מעבד ב' נמוך יותר, הוא עשוי להיות מהיר יותר משום שהוא מבצע יותר עבודה בכל מחזור.

גם ה-IPC אינו מספר קבוע. הוא תלוי בתוכנית, ב-Cache, בתלויות בין ההוראות, בחיזוי הסתעפויות ובמיקרו-ארכיטקטורה של המעבד.

בנוסף, תדר המעבד משתנה בזמן אמת. בעומס גבוה המעבד עשוי להתחמם ולהפעיל Thermal Throttling, כלומר להוריד את התדר כדי להגן על עצמו.

מדוע יותר ליבות לא מאיצות כל תוכנית?

ליבה היא יחידת ביצוע שיכולה להריץ זרם הוראות.

כאשר למחשב יש כמה ליבות, הוא יכול לבצע כמה משימות במקביל. אבל כדי שתוכנית אחת תשתמש בכמה ליבות, העבודה שלה צריכה להיות ניתנת לחלוקה.

לולאת Python רגילה אינה מתחלקת אוטומטית בין כל הליבות:

total = 0

for number in range(20_000_000):
    total += number * number

כדי לנצל כמה ליבות אפשר, לדוגמה, לחלק את הטווח בין כמה תהליכים:

תהליך 1 -> מחשב חלק מהטווח
תהליך 2 -> מחשב חלק אחר
התהליך הראשי -> מחבר את התוצאות

גם אז לא נקבל בהכרח שיפור לינארי.

יש לכך כמה סיבות:

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

כיצד תוכנית Python מגיעה למעבד?

המסלול ב-CPython נראה באופן מפושט כך:

Python Source
↓
Python Compiler
↓
Python Bytecode
↓
CPython Interpreter
↓
Machine Code של המפרש
↓
CPU

קוד המקור של Python מיועד לבני אדם.

מהדר Python יוצר ממנו Bytecode. ה-Bytecode אינו קוד מכונה של ARM64 או x86-64, אלא סט הוראות עבור המכונה הווירטואלית והמפרש של Python.

המפרש CPython הוא תוכנה שכבר קומפלה לקוד המכונה של המחשב. המעבד מריץ את קוד המכונה של המפרש, והמפרש קורא ומטפל בהוראות ה-Bytecode.

לכן אותה תוכנית Python יכולה לפעול גם במחשב ARM64 וגם במחשב x86-64, כל עוד מותקן בכל מחשב מפרש Python המתאים לארכיטקטורה שלו.

הניסוי המעשי

הרצתי לולאת Python שמחשבת סכום ריבועים על מחשב macOS מבוסס ARM64 עם 18 מעבדים לוגיים.

התוצאות היו:

20 מיליון איטרציות:
0.42 שניות
CPU: 99.9%
Peak RSS: 16.69MB

40 מיליון איטרציות:
0.83 שניות
CPU: 99.9%
Peak RSS: 16.69MB

היחס בין הזמנים היה:

0.83 ÷ 0.42 ≈ 1.98

כלומר, הכפלת מספר האיטרציות הכפילה כמעט בדיוק את זמן הריצה.

השימוש של כ-100% CPU בתהליך ב-macOS העיד על ניצול כמעט מלא של ליבה אחת, ולא של כל 18 המעבדים הלוגיים.

צריכת הזיכרון נשארה זהה משום שהתוכנית לא שמרה את כל התוצאות. היא החזיקה בעיקר את הסכום הנוכחי, את מונה הלולאה ואובייקטים זמניים. לכן צריכת הזיכרון הנוספת הייתה בקירוב קבועה, אף שכמות החישוב הוכפלה.

הניסוי הראה שלושה דברים:

  1. לולאת CPython יחידה אינה מתחלקת אוטומטית בין כל הליבות.
  2. במשימה כזו זמן הריצה גדל בקירוב בהתאם לכמות העבודה.
  3. מספר רב של ליבות אינו עוזר לתוכנית שלא נכתבה לניצול מקביליות.

סיכום

המעבד מבצע הוראות מכונה באמצעות תהליך שכולל הבאת הוראה, פענוח, קריאת נתונים, ביצוע וכתיבת התוצאה.

ה-PC מחזיק את כתובת ההוראה הבאה, האוגרים משמשים כאזור העבודה המהיר של המעבד, וה-Cache מקטין את זמני ההמתנה לזיכרון.

ביצועי המעבד אינם נקבעים רק על פי GHz. הם תלויים גם ב-IPC, ב-Cache, במבנה ה-Pipeline, בתלויות, בחיזוי ובטמפרטורה.

גם מספר הליבות לבדו אינו מבטיח שיפור. כדי להשתמש בכמה ליבות, התוכנית צריכה להיות ניתנת לחלוקה, ועלות החלוקה צריכה להיות קטנה מספיק ביחס לעבודה עצמה.

בסופו של דבר, השאלה החשובה אינה רק "כמה GHz וכמה ליבות יש למעבד?", אלא:

כמה עבודה המעבד מסוגל להשלים בפועל עבור התוכנית המסוימת שאנחנו מריצים?