-->

יום רביעי, 25 ביולי 2012

מפרשן מול מהדר - מה ההבדל?

למי שלא עשה קורס ב"קומפילציה" (ואולי גם למי שכן), עולה מדי פעם השאלה: "מה ההבדל בין מפרשן (interpreter) למהדר (compiler)?"
- שניהם הרי גורמים לקוד של תוכנה לרוץ על מכונה כלשהי.

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

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

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

מפרשן יכול לספק debugger בהשקעה אפסית כמעט - עוד יתרון. הקלות בכתיבת מפרשן יכולה להיות מתורגמת בקלות לכתיבת שפה cross-platform - יתרון נוסף.

כשעבדתי ב NICE, השקעתי כמה חודשים טובים בכתיבת מפרשן לשפה מוזרה ללא שם. זה היה אתגר מרתק ומעשיר, עד לשלב בו נתבקשתי לממש מצביעים (pointers). זה היה מספיק על מנת שאלחץ שוב, עד שהצלחתי לשכנע את המנהל שלי להשקיע כ 200$ ברכישת רישיון למפרשן JavaScript - שפה שמילאה בקלות אחר כל הצרכים והיה אלגנטית בהרבה (תארו לעצמכם).

לכתוב מפרשן, זה בסה"כ דיי פשוט. קוראים שורה מקובץ (להלן ה"קוד"), מפרסרים מה כתוב שם (הכי פשוט שהפקודה היא המילה הראשונה) ואז מבצעים בשפה שלכם (#C למשל) את מה שרציתם לבצע. הדברים נהיים קצת יותר מורכבים כאשר יש משתנים, פונקציות, Scope ויותר. על המשמעות של הכנסת מצביעים אני לא יודע לספר.

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

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

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


נוסטלגיה. כנראה שרוב קורסי ה"קומפילציה" בארץ נעשו על בסיס ספר זה מ 1986. מקור: amazon.com


המצב בפועל
בואו נבחן כמה שפות מוכרות: האם הן שפות-מהדר או שפות-מפרשן?

שפת אסמבלי
בואו נתחיל מאסמבלי: טעות נפוצה היא לקרוא לשפת אסמבלי (assembly) בשם "אסמבלר". אסמבלר הוא השם של המהדר שלה. האם האסמבלר "מתרגם תוכנה שנכתבה בשפה 'קריאה לאדם', כיחידה שלמה, לקוד בשפת מכונה (אפסים ואחדים) שמבוצע ישירות על המעבד"? כן. 
שפת אסמבלי היא בעצם ייצוג טקסטואלי, בעזרת מילים "בשפת אדם" כגון JMP ו MOV, לפקודות מכונה. האסמבלר פשוט עושה מין "Search and Replace" של "JMP" ל 00010010101101, למשל.

יש לי הסתייגות קלה מהגדרת שפת אסמבלי כ"קריאה לאדם". אני חושב שכדאי מאוד לעשות את ההבחנה בין "Human Readable" לבין "Human Parsable". ברוב הפעמים שמדברים על Human Readable (לדוגמה קובץ XML) רוצים לומר שזה טקסט שאדם יכול "לפענח", לאו דווקא "לקרוא".


שפת ++C
הנה דוגמה טובה למהדר "קלאסי". בניגוד לאסמבלי, התרגום משפת ++C לשפת-מכונה הוא מורכב וכולל טרנספורמציות ואופטימיזציות רבות. הרבה דברים קורים מאחורי הקלעים. ניתן אפילו לומר ש ++C "שותל" מפרשנים קטנים בתוך הקוד (למשל לטיפול ב virtual functions) כך שמבחינה מסוימת יש במהדר של ++C אלמנטים של מפרשן!




שפת JavaScript
ג'אווהסקריפט הייתה במשך שנים דוגמה קלסית לשפת מפרשן. הסיבה העיקרית הייתה כנראה הרצון להריץ קוד שלה על סביבות שונות: כתוב את הקוד פעם אחת והוא ירוץ על ספארי (מק), FF (לינוקס) או IE (חלונות). אם הקוד קומפל לשפת מכונה אחת - הוא לא יתאים למכונה אחרת ולכן האפשרות היחידה היא לשלוח את קוד המקור למחשב עליו רץ הדפדפן - ושם לפרשן אותו.
בשנים האחרונות, הפכו נפוצים מהדרי JIT [ב] לשפת ג'אווהסקריפט. דוגמה בולטת היא מהדר ה JIT של V8 - מנוע  הגא'ווהסקריפט של גוגל. התוצאה: קוד המקור נשלח לדפדפן לפני ההרצה, אך הדפדפן מחליט להדר אותו ואז להריץ אותו בשפת מכונה - כך שבפועל כיום, JavaScript רצה מהודרת. הנה דוגמה ליתרון שבהידור. דמיינו את הקוד הנפוץ הבא:

var a.b.c.d.e.f.g.h.x = 100;
var a.b.c.d.e.f.g.h.y = 200;

כאשר a.b.c.d.e.f.g.h הם שמות namespaces או אובייקטים בג'אווהסקריפט. כאשר יש מפרשן, עליו לקחת את אובייקט a ואז לחפש בו property בשם b, ואז על b לחפש property בשם c וחוזר חלילה. ללא שיפורי ביצועים מיוחדים יכול להיות שמדובר בעשרות פעולות בזיכרון.
מהדר לעומת זאת קורא את 2 השורות ומבין שיש פה אופציה לייעל את הקוד. הוא יכול להפוך את הקוד ל:

var _p = a.b.c.d.e.f.g.h;
var p.x = 100;
var p.y = 200;

שיהיה יעיל בהרבה. מכיוון שג'אווהסקריפט היא שפה דינמית, עליו לוודא ש a עד h לא השתנו בעקבות ההשמה של x. כאן שוב יש מין "מיקרו מפרשן" שהמהדר מייצר ופועל בזמן ריצה.






שפת Java
Java היא שפת מהדר - נכון?
כן. בג'אווה יש מהדר שהופך קוד ג'אווה לקוד bytecode, אבל קוד ה bytecode עובר פרשון (מכיוון שהוא אמור להיות cross-platform). כבר מזמן יש JIT Compilers ל bytecode, כך שבפועל ג'אווה עוברת הידור ראשון בסביבת הפיתוח והידור שני על פלטפורמת ההרצה - מה שמתגלה כנוסחה יעילה למדי!
גם כאשר מהדרים קוד ++C למעבד מסויים (למשל מעבדי אינטל 64-ביט) יש לדגמים שונים יכולות מעט שונות - יכולות שלא ניתן לנצל בקוד המהודר מכיוון שעל המהדר לפנות למכנה המשותף הנמוך. הידור דו-שלבי כגון זה של ג'אווה מסוגל לבצע אופטימיזציות ייעודיות לחומרה הקיימת - ולנצל את כל היכולות / התכונות של החומרה.

שפת CoffeeScript
לקופיסקריפט יש מהדר, אך הוא עושה דבר מוזר: הוא מתרגם קופיסקריפט לג'אווהסקריפט. בשל הבא קוד הג'אווהסקריפט  (המהודר) נשלח למכונת משתמש-הקצה ועובר הידור ב JIT Compiler - כך שיש לו הידור כפול, אך קצת שונה משל ג'אווה מכיוון ששפת הביניים (intermediate language) היא ג'אווהסקריפט - שפה גבוהה בהרבה מ bytecode והיכולת לבצע אופטימיזציה בקוד - פחותה.
בפועל, אתם מבינים, יכולה להיות שרשרת דיי מורכבת של מהדרים ומפרשנים בדרכו של קוד תוכנה להיות מורץ. הדרך לתאר את כל האפשרויות הקיימות היא מורכבת, ויתרה מכך - כנראה לא מעניינת.


(DSL (Domain Specific Languages
סוג חדש של שפות שהופיע לאחרונה[ג] הן שפות מבוססות דומיין, כלומר שפות מתאימות לפתור בעיות מסוג מאוד מסוים: ויזואליזציה של נתונים בתחום מסוים, ביצוע שאילתות על נתונים, קביעת תצורה של Firewall וכו'.
לרוב לא מדובר בשפה חדשה לגמרי, אלא שפה ש"מורכבת" על שפה קיימת. אלו יכולים להיות פונקציות או אובייקטים שמתארים עולם מונחים שמקל על פתרון הבעיה ובעצם מהווה "שפה חדשה".

לדוגמה: ניתן לחשוב על "שפה לחישוב שכר". מי שמגדיר את החוזה (להלן ה"קוד") יכול בעצם לכתוב בשפה גבוהה כמו שפת Scripting או קובץ קונפיגורציה, ולהשתמש במונחים שנכונים לעולם זה. הקוד יהיה קריא למדי למישהו מתחום השכר, גם אם איננו יודע תכנות.

האם שפת השכר שלנו, "P" נקרא לה, היא שפת-מהדר או שפת-מפרשן?
אם היא ממומשת מעל Groovy, למשל, אזי אפשר להתייחס אליה כסוג של שפה-מפורשנת (לגרובי).
כלומר: שפה-מפורשנת (ע"י גרובי), שעוברת קומפלציה (לבייטקוד) ואז מקומפלת (ב JVM JIT Compiler) לשפת מכונה שכוללת מיני-מפרשנים (שגם הם קומפלו בדרך לשפת מכונה). באאאאההה.


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

בפרק הבא בסדרה: "מה ההבדל בין חומרה לתוכנה"?
האם חומרה היא "מה שניתן לגעת בו", או אולי ייתכן שחיווט של בקרי דיסק (בהחלט ניתן לגעת בחוטים) הוא סוג של תוכנה, בשפת DSL של טכנאי-מחשבים?


עדכון: הנה נתקלתי בבסיס נתונים שמהדר SQL ל ++C ואז לשפת מכונה.

----

[א] זה כמובן איננו טיעון שפוסל שפות שירוצו על מפרשן. שפות מסוימות (בעיקר שפות gluing) לא זקוקות לפרשון מהיר. קחו לדוגמה שפות כמו Bash או Windows PowerShell - הן מפעילות תוכנות יעילות, אך אם לכל שורה שלהן לוקח עוד כמה מילי-שניות - אין לכך כל חשיבות.

[ב] (JIT (Just In Time הוא מונח של "ייצור רזה" שאומר "עשה את הפעולה רק מתי שצריך - לא לפני כן". הרעיון הוא שאם עושים פעולה (כגון הזמנה של מלאי חדש למחסן) לפני הרגע האחרון - היעילות היא איננה מרבית. JIT Compiler הוא כזה שמקמפל את הקוד ממש לפני ההרצה - ולא לפני כן. יש בכך יתרונות וחסרונות - מצד אחד יודעים בדיוק את הסביבה בה הקוד עתיד לרוץ וניתן לבצע אופטימיזציות לפלטפורמה הספציפית (לדוגמה דגם המעבד), מצד שני כנראה שהמשתמש לא ימתין לאופטימיזיות קוד שאורכות זמן רב.

[ג] בעצם הוא קיים המון זמן, אבל המודעות לקיים - היא זו שהתפתחה לאחרונה + קיבלה fancy name.

2 comments:

  1. אני חושב שתרגום מדוייק יותר עבור "interpreter" הינו "מפרשן", בעיקר כי כך אני רגיל כבר במשך שנים ;)

    מפענח באנגלית זה decoder, שזו בעצם פעולה פשוטה בהרבה, תרגום ממפת יצוג אחת לאחרת. להבדיל מפענוח משמעות המורכבת מצירוף של מס' הגדרות ופעולות (ע"י "הרצתם").

    אגב, אם אתה כבר עושה השוואות בין מפרשן למהדר, אולי יש מקום גם למאמר המשווה בין שפת תסריט לשפת תכנות?!

    והעיקר... תמשיך לכתוב :)

    השבמחק
    תשובות
    1. היי ישראל,

      תודה על הסיוע הלשוני. קיבלתי את ההערה וראה - הפוסט עודכן.

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

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

      ליאור

      מחק