לדלג לתוכן

זכרון מטמון מוסבר - CPU Cache, ולמה "קוד ידידותי למטמון" הוא לא קלישאה שיווקית

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

הבעיה שהמטמון בא לפתור

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

מבנה בעל שכבות - L1, L2, L3

המטמון עצמו לא בנוי כגוש אחד, אלא כהיררכיה של שכבות. L1 היא השכבה הקטנה והמהירה ביותר, הכי קרובה לליבת המעבד. L2 גדולה יותר אבל קצת יותר איטית. L3 גדולה עוד יותר, ולרוב משותפת בין כל הליבות במעבד. כל שכבה היא בעצם פשרה בין גודל למהירות - ככל שהמטמון קרוב וקטן יותר, הוא מהיר יותר, וככל שהוא רחוק וגדול יותר, הוא איטי יותר אבל יכול להכיל יותר נתונים.

העיקרון שהופך את כל זה לאפשרי - Locality

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

פגיעה מול החטאה - Cache Hit ו-Cache Miss

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

למה סדר הגישה לזיכרון משנה בפועל

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

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

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

הצטרפו לקהילה בדיסקורד

לסיכום

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