FPC Static's vs GCC Dynamic's פרק אחרון

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

הניסוי שעשיתי התחלק ל2:

  1. מדידת משאבים לפי vmSize/vmRSS
  2. מדידת maps של התוכניות

דבר ראשון יצרתי 5 תוכניות בפסקל ובC שעושות את הדבר הבא:

מדפיסות על המסך Hello World בלולאה אין סופית ובין כל הדפסה יש sleep של שניה אחת.

עכשיו התוכנית הראשונה מדפיסה Hello World,השנייה Hello Worl,השלישית Hello Wor,הרביעית Hello Woוהחמישית Hello W.

קוד פסקל נראה ככה:

program test;
uses SysUtils;
begin
while true do begn
writeln('Hello World');
sleep(1000);
end;
end.

קוד C נראה ככה:

#include <stdio.h>
#include <unistd.h>
int main() {
while (1) {
printf("Hello World\n");
sleep(1000);
}
return 1;
}

את כולם הרצתי בייחד בצורה הבאה:

test1 & test2 & test3 & test4 &teest5

אני לא יודע למה בחרתי בשיטה הזו, למרות שאני יכול לחשוב על יותר מדרך אחת להריץ את הכל…

את החלק הראשון אפשר למצוא בתמונות הבאות (המדיד הוא מתוך "משמר המערכת" של KDE).

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

צפריר הציע לי להשתמש ב pmap ע"מ לראות את מיפוי הזכרון של כל תהליך.

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

הנה דוגמא ל 2 תוכנות C ול2 תוכנות פסקל (קוד C ואז פסקל):

C:

map 14487
14487: test1
0000000000400000 4K r-x– /tmp/c/test1
0000000000600000 4K rw— /tmp/c/test1
00002b179f72a000 104K r-x– /lib64/ld-2.7.so
00002b179f744000 12K rw— [ anon ]
00002b179f943000 4K r—- /lib64/ld-2.7.so
00002b179f944000 4K rw— /lib64/ld-2.7.so
00002b179f945000 1324K r-x– /lib64/libc-2.7.so
00002b179fa90000 2044K —– /lib64/libc-2.7.so
00002b179fc8f000 16K r—- /lib64/libc-2.7.so
00002b179fc93000 4K rw— /lib64/libc-2.7.so
00002b179fc94000 24K rw— [ anon ]
00007fff0b36b000 84K rw— [ stack ]
00007fff0b3fe000 8K r-x– [ anon ]
ffffffffff600000 4K r-x– [ anon ]
total 3640K

ik@white-star:~$ pmap 14488
14488: test2
0000000000400000 4K r-x– /tmp/c/test2
0000000000600000 4K rw— /tmp/c/test2
00002b58f9bab000 104K r-x– /lib64/ld-2.7.so
00002b58f9bc5000 12K rw— [ anon ]
00002b58f9dc4000 4K r—- /lib64/ld-2.7.so
00002b58f9dc5000 4K rw— /lib64/ld-2.7.so
00002b58f9dc6000 1324K r-x– /lib64/libc-2.7.so
00002b58f9f11000 2044K —– /lib64/libc-2.7.so
00002b58fa110000 16K r—- /lib64/libc-2.7.so
00002b58fa114000 4K rw— /lib64/libc-2.7.so
00002b58fa115000 24K rw— [ anon ]
00007fffb0ee9000 84K rw— [ stack ]
00007fffb0ffe000 8K r-x– [ anon ]
ffffffffff600000 4K r-x– [ anon ]
total 3640K

Pascal:

pmap 14472
14472: test1
0000000000400000 272K r-x– /tmp/test1
0000000000643000 76K rwx– /tmp/test1
0000000000656000 8K rwx– [ anon ]
00002b23000b1000 352K rwx– [ anon ]
00007fffaa9e3000 84K rwx– [ stack ]
00007fffaa9fe000 8K r-x– [ anon ]
ffffffffff600000 4K r-x– [ anon ]
total 804K
ik@white-star:~$ pmap 14473
14473: test2
0000000000400000 272K r-x– /tmp/test2
0000000000643000 76K rwx– /tmp/test2
0000000000656000 8K rwx– [ anon ]
00002b124f4f8000 352K rwx– [ anon ]
00007fff5b59d000 84K rwx– [ stack ]
00007fff5b5fe000 8K r-x– [ anon ]
ffffffffff600000 4K r-x– [ anon ]
total 804K

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

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

אז איך זה יתכן ?! הרי FPC ייצר לנו קוד סטטי, איך הוא גרם לזה שהקוד יהיה משותף ?

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

ד"א ההבדל (לא היחיד, אבל העיקרי) בין ספרייה משותפת לבין קובץ ריצה "רגיל" הוא חלק האיתחול. בספרייה משותפת אין חלק אתחול (הוא קיים רק בלי קוד בתוכו, הוא בד"כ יכיל null), אלא יש חלק שממפה כתובות זכרון של פונקציות משתנים וכו' לזכרון משותף, כאשר מערכת ההפעלה ממפה בין הקוד האמיתי לבין קוד שמשתנה בין תהליך אחד לשני (בשביל לשמור על הריצה מפני חילול זה או אחר של הזכרון). זה לפחות ממש בגדול🙂

עכשיו יבוא צפריר וגיד "אבל רגע, אם הזכרון משותף, אז כל תהליך יכול לכתוב לאותו זכרון". ובכן, זכרון של תהליך הוא פרטי בprotected mode אלא אם המערכת הפעלה החליטה אחרת, כך שאם המערכת שלך החליטה לשתף את הזכרון, אז הבעית אבטחה היא במערכת הפעלה ולא בתוכנה שלך ! עם ספריות משותפות, אני יכול לעשות override ל LD_LIBRARY_PATH ולטעון ספרייה שלי עם אותן כתובות לטעינה רק עם קוד שלי, ואתה לעולם לא תדע (ככה חלק מה rootkits בעולם עובדים).

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

יש לי החלטה של הספרייה libc היא גדולה מידי ו3/4 מהפונקציות שם אינן בשימוש ע"י רוב התוכנות, ולכן אני ארצה ספרייה נוספת שתכיל רק את הקוד שלא בשימוש, ו libc.so יכיל רק את הפונקציות שנמצאות בשימוש תדיר.

רגע זה לא כזה פשוט ! פתאום *כל* המערכת שלי פרט למערכת הפעלה צריכה להיות מהודרת מחדש. אח"כ מתפלאים למה יש הרבה זבל (כמובן שזה מקרה קיצוני, אבל להראות שאין טוב בלי רע בשני הצדדים) ….

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

The problems that exist in the world today cannot be solved by the level of thinking that created them.
Albert Einstein

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

אז אני לא הולך להגיב לאף אחד שיכתוב כאן תגובה, כי את מה שרציתי להגיד אמרתי, אבל אתם מוזמנים להגיב בכל זאת, אם יש לכם מה להגיד, או תנצרו את דעתכם לעד🙂

5 מחשבות על “FPC Static's vs GCC Dynamic's פרק אחרון

  1. ארתיום

    עידו צר לי לאכזב אותך אבל המדידות שלך אינן נכונות.

    יצרת 100 תכנות של FPC הרצתי אותן התסכלי בכמה צריכת הזכרון השתנתה 20MB.

    יצרתי 100 תכנות gcc הרצתי אותן, צריכת הזכרון הייתה 14MB.

    איך עשיתי? עשיתי 100 עותקים של תכנה והרצתי את כולם מלולאת bash.

    (שינוי בצריכת זכרון בדקתי עם free).

    לא שהפרש של 6MB זה משמעותי עבור 100 תהליכים. יותר מזה, זה כלום וזה מה שכנע אותי באופן אישי שמה ש־FPCמייצר זה ממש לא נורא… רק שהוא עדיין צורך קצת יותר זכרון מ־gcc. צ'טערת…

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

  2. צפריר כהן

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

    מה כן רואים כאן?.

    * אין שום קובץ (חוץ מהתוכנית עצמה) שממופית ישירות לזכרון. כל מה שאינו ממופה לקובץ בדיסק יתפוס בשעת מחסור זכרון מקום מיותר ב־swap.
    * רוב הבלוקים ממופים כ־rwx . מותר גם לכתוב אליהם ומותר גם להריץ מהם קוד. עצוב שהם לא שמעו על non-executable stack. אני לא מניח שתהליך אחד מעיז לשתף קטע זכרון של קוד עם תהליך אחר אם שניהם יכולים לכתוב אליו.

  3. elcuco

    אתה מתקרב לשנות את הדעה שלי….

    נכון, כל התהליכים יש להם זכרון משותף (.text נכון) אבל זה כי כל היישומים באים מאותו קובץ. אתה יכול לחזור על הניסוי, וכמו שארתיום אמר:
    תעתיק את הקובץ 100 פעמים, ותריץ את ה־100 עותקים במקביל, ככה למערכת הפעלה לא תהיה אפשרות לדעת שזה בעצם אותו text segment.

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

    אשמח את תעשה את הניסוי על תוכנה קצת יותר מתוסבכת (למשל תוכנה שנחשבת את הערך של e בלולאה ומדפיסה אותו למסך).

  4. Shai

    יש כמה בלבולים ושגיאות בפוסט ובתגובות, ואני רוצה לנסות לפרט יותר ולהבהיר:

    1. דיאגו — מה שאתה מציע לעשות, זה הניסוי שעידו כבר עשה. הוא אף הגדיל לעשות ודאג שה-binaries יהיו טיפה שונים בתוכן, ולא רק בקבצים נפרדים.

    2. צפריר צודק, אבל אני חושב שהוא כתב בצורה לא בהירה, ושווה לפרט: במערכת הפעלה נורמלית, כל תהליך מקבל מרחב-כתובות מלא, כלומר אשליה שכל הזיכרון שלו. אבל כל כתובת שהתהליך בעצם ניגש אליה היא "כתובת לוגית", כלומר, מישהו עדיין ממפה אותה לכתובת פיסית בזכרון, וכתובת 400000 של תהליך אחד איננה בהכרח זהה לכתובת 400000 של תהליך אחר (הן יכולות להיות זהות, אם יש שיתוף קוד, אבל הן לא חייבות להיות זהות). מה שרואים בפלט של pmap הוא כתובות לוגיות כאלה, ולכן העובדה שרואים בפלטים עבור שני תהליכים את אותה הכתובת — ואפילו לבלוק באותו הגודל — לא מוכיחה שהם משתפים קוד. להיפך, העובדה שהבלוק מופיע כממופה לקבצים שונים בפלטים השונים (test1 ו-test2) מוכיחה שאין שיתוף קוד.

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

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

  5. Amos

    Tzafrir – I hope I'm not dispersing misinformation but I think I heard that the FPC depends on trampolines (temporary code in the stack) in order to implement some of the built-in commands.

    (A quick google didn't come up with a definite reference, sorry).

כתיבת תגובה

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

הלוגו של WordPress.com

אתה מגיב באמצעות חשבון WordPress.com שלך. לצאת מהמערכת / לשנות )

תמונת Twitter

אתה מגיב באמצעות חשבון Twitter שלך. לצאת מהמערכת / לשנות )

תמונת Facebook

אתה מגיב באמצעות חשבון Facebook שלך. לצאת מהמערכת / לשנות )

תמונת גוגל פלוס

אתה מגיב באמצעות חשבון Google+ שלך. לצאת מהמערכת / לשנות )

מתחבר ל-%s