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

מה ההבדל בין מפרשן (interpreter) למהדר (compiler)? שניהם הרי גורמים לקוד תוכנה לרוץ על מכונה כלשהי.

תמונה: flickr, cc-by, mutednarayan

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

התשובה התיאורית היא כזו:

מהדר (Compiler) – מתרגם תוכנה שנכתבה בשפה “קריאה לאדם”, כיחידה שלמה, לקוד בשפת מכונה (אפסים ואחדים) שמבוצע ישירות על המעבד.

מפרשן (Interpreter) – מריץ את התוכנה, שורה אחר שורה ומפרשן כל שורה בנפרד. כל שורה עוברת תרגום “משפת אדם” לשפת מכונה – ואז מורצת, וחוזר חלילה.

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

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

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

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

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

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

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

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

המצב בפועל

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

שפת אסמבלי

בואו נתחיל מאסמבלי: טעות נפוצה היא לקרוא לשפת אסמבלי (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 של טכנאי-מחשבים?

*   *   *

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

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

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

הפוסט פורסם לראשונה בבלוג ארכיטקטורת תוכנה.

 

ליאור בר-און

ליאור בר-און הוא Chief Architect בחברת סטארטאפ ישראלית גדולה.

הגב

6 תגובות על "מפרשן מול מהדר – מה ההבדל?"

avatar
Photo and Image Files
 
 
 
Audio and Video Files
 
 
 
Other File Types
 
 
 

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

סידור לפי:   חדש | ישן | הכי מדורגים
ערן
Guest

בשביל הסדר הטוב, מהדר לא מתרגם שפה קריאה לאדם לשפת מכונה. הוא מתרגם שפה אחת לשפה אחרת. המהדר הראשון של ++C, למשל (CFront), תירגם קוד של ++C לקוד של C, ואז הריץ תהליך הידור נוסף על קוד ה-C שהתקבל. מהעבר השני, אפשר לכתוב מהדר שלוקח אסמבלי של מהדר אחד, ומתרגם אותו לאסמבלי של מהדר אחר. אז ברוב המקרים, מהדר אכן יקח שפה עילית ויתרגם אותה לשפת מכונה, אבל הוא יכול גם לעשות דברים אחרים.

גלעד
Guest

הרבה שטויות,

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

מעבר לזה המבנה של מהדר ומפרשן מאוד דומה וברור שמפרשן לא “מריץ את התוכנה, שורה אחר שורה ומפרשן כל שורה בנפרד”

ANTLR זה לא ספריה אלה “Parser Generator”

לא ברור מה ההיתרון של מפרשן שעולה 200$ על פני מפרשנים שהם קוד פתוח.

התרגום של DSL ל “שפות מבוססות דומיין” הוא תרגום גרוע.

Gilad Naaman
Guest

מה זה בדיוק מפרשן? הלחם של פרשן ומפרש?

Gilad Naaman
Guest

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

רן
Guest

1. תודה רבה על המאמר!
2. אינטרפטר כן ממיר שורה, מריץ אותה, וחוזר חלילה, שורה אחר שורה.
3. הדבר היחידי שלא הבנתי הוא למה לקרוא לשפת השכר שלנו “P”
אם לא ה”P” הזו לא זוכרה אף לא פעם אחת מאז שנאמר שזה יהיה הכינוי של השפה.
בוא נאמר שאם היית מנסה “להריץ” את המאמר שלך ב C#
אז הדיבאגר לא היה מאפשר לך :)
..מכיוון שב C# לא ניתן להגדיר משתנה מבלי לעשות בו שימוש.

רן
Guest

מה שכתבתי בסעיף 2 מתייחס לתגובה של גלעד, ולא למאמר.
מה שכתוב במאמר – נכון.
רק הקטע עם ה P בסוף הוא לא ברור ונדמה שמיותר.

wpDiscuz

תגיות לכתבה: