Connect with us

קטגוריות

Javascript

Javascript: איך עובדת קומפילציית Just In Time (או JIT)

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

במאמר זה אסביר בקצרה על קומפילציית Just In Time (או בקיצור JIT) ואיך בדיוק היא גורמת לקוד שכתבנו לרוץ בצורה מהירה ויעילה יותר.

יאללה מתחילים.

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

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

בתכנות יש שתי דרכים לתרגם את הקוד שכתבנו לשפת מכונה, האחת מפרש (interpreter) והשניה – מהדר (compiler).

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

מפרש (interpreter)

מפרש (interpreter) מריץ את הקוד במהירות מפני שהוא לא צריך לעבור על כל הקוד שלנו לפני הריצה, הוא יקרא את השורה הראשונה וירוץ קדימה.

בעבר דפדפנים השתמשו במפרש בלבד, ועם הזמן מפתחי הדפדפנים למדו שהמפרש לא יעיל במיוחד

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

מהדר (compiler)

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

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

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

ואז הגיע JIT

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

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

במנוע ה-Javascript של הדפדפנים שולב מהדר שנקרא מוניטור (או profiler), כשהקוד שלנו רץ, המוניטור מנטר (כאילו דה) את הקוד שלנו ושם לב לדברים מסויימים כמו כמה פעמים קטע קוד מסויים רץ, באיזה סוגי משתנים הקוד משתמש וכ'ו.

המוניטור ישים לב אם קטע קוד מסויים רק כמה פעמים, וכשזה קורה הוא מסמן את אותו קטע קוד כ-warm, אם המוניטור שם לב לקטע קוד שרץ הרבה מאוד פעמים הוא יסמן אותו כ-hot.

מהדר ה-Baseline 

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

כל שורה בקטע הקוד מתקמפלת ל-stub, הstubs-ים מאונדקסים לפי מספר שורה וסוג משתנה, אם המוניטור מבין שאותו קטע קוד הולך לרוץ פעם נוספת עם אותם סוגי משתנים, המוניטור ישלוף את קטע קוד המקומפל מהזכרון.

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

אך אם קטע קוד מסויים מסומן כ-hot (רץ הרבה פעמים), הדברים משתנים, המוניטור מבין שעכשיו שווה לבצע אופטימיזציות נוספות.

מהדר האופטימיזציה

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

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

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

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

לפני שקטע הקוד המקופל רץ, הוא צריך להיבדק שההנחות שהניח מהדר האופטימיזציה נכונות, אם ההנחה נכונה אז אין בעיה – קטע הקוד המקומפל ירוץ, אך אם ההנחה שגויה מהדר ה-JIT ינקה את קטע הקוד המקומפל מהזכרון ובשלב הזה אנחנו חוזרים למפרש או לגרסה מקופלת (warm) קודמת יותר של אותה פונקציה, התהליך הזה נקרא דה-אופטימיזציה (deoptimization).

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

לדוגמא, אם קטע הקוד שרשמתם עובר תהליך אופטימיזציה ותהליך דה-אופטימיזציה בצורה עיקבית, בסופו של דבר הקוד שלכם ירוץ לאט יותר מאשר להריץ את אותה גרסה במהדר ה-Baseline.

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

לסיכום

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

אני מקווה שגם נהנתם מהקריאה, עד הפעם הבאה.

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

עוד ב Javascript

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

מאמרים פופולריים

נושאים פופולריים

חזרה למעלה