יום שישי, 14 באוקטובר 2011

Performance: דחיינות = מקצוענות?

הפוסט עודכן ב 16/10


שאלה: כיצד כותבים קוד שבנוי לביצועים גבוהים?
תשובה: בדיוק אותו הדבר.


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


הקדמה
פעם, עבדתי עם מתכנתת שקיבלה הערות על מסמך Design שכתבה ונראתה פגועה: "כתבו לי שה-Design שלי ילדותי". מה?? שאלתי – תראי לי. קראתי את ההערה והיא הייתה: "premature optimization".

Premature Optimization
"Premature Optimization" למי שלא הבין אינה קשורה לילדותיות, אלא לאופטימיזציה שלא הגיעה זמנה. הזקנים שביננו שלמדו פיתוח בשפת C יכולים עוד לזכור את הסנסיי (במקרה זה מרצה מזדקן למדעי המחשב שעסק במקור במתמטיקה) סופר לנו כל ביט וכל פעולת if. "מה?, כתבת x/4? ברור ש x*0.25 זה מהיר יותר. בכלל הייתי מצפה ל x>>2" - הם הכו בנו תורה (סלחו לי אם השיפט הוא לכיוון ההפוך, מרגיז אותי בכלל לבדוק).
בשנות השבעים, שהמחשב ביצע מאות פעולות בשנייה – הייתה לאופטימיזציה כזו חשיבות. טראומת הקוד-הלא-אופטימלי היה כ"כ קשה שעד היום יש זכר לצלקות (במיוחד באקדמיה).

כיום, מחשב ביתי ממוצע מכיל 2 או 4 ליבות, בקצב של לפחות 2 מיליארד פעולות בשנייה (Ghz), עם instruction-set (כגון MMX או SSE - אליהם ניתן לגשת בעזרת ספריות מיוחדות בשפת C) שמבצעים פעולות ארוכות ומסובכות בצורה יעילה יותר. רק אזכיר שהיום לכל מחשב חדש יש גם (GPU - Graphical Processing Unit) אותו כרטיס גרפי שמובנה בלוח האם, בתוך ה CPU או על לוח נפרד. יחידות עיבוד אלה בנויות לביצוע פעולות מתמטיות פשוטות במקביליות גבוהה ויכולים להציג כח חישוב מפחיד.

דוגמא טובה לשימוש אינטנסיבי במעבד היא פיצוח ססמאות. הרי ממשלות, משטרות (וארגוני פשע) בונים מחשבי על בכדי לפצח מסרים מוצפנים של אוייביהם. בימנו המאיצים הגרפיים (GPU) התקדמו כ"כ  עד שהם מאפשרים פיצוח סיסמאות בקצב מהיר פי 20 - בעזרת חומרה במחיר זהה. בשל הכניסה של תכנות ל GPU לשימוש**, המליצו כבר גופי אבטחה לחייב את המשתמשים בסיסמא מינימלית באורך 12 תווים. אויי לא!

למה לעשות אופיטמיזציה?
אז מה ניתן להסיק? שחשיבה על יעילות היא לא חשובה? כן - ולא.
כוח העיבוד הפך זול, הזכרונות יכולים לשמש כתחליף לנייר טואלט, אבל זמן המתכנת הוא יקר. כל אלה מובילים עוד ועוד אנשים למסקנה שיש לדחות אופטימיזציות performance כמה שרק אפשר, וחלקן הגדול אין בכלל לעשות. חשוב לציין 2 סייגים חשובים מאוד:
  • אלגוריתמים (או מבני נתונים שעליהם מבוססים האלגוריתמים) עלולים להיות חסיני-כח-מחשוב. חיפוש ב O(N) מול O(logN), כאשר רשימת האלמנטים עולה על כמה אלפי איברים, יעשה את ההבדל בין תגובה מהירה להפסקת קפה (של המשתמש, כמובן). כאן אין על מה להתפשר.
  • לחזור לפונקציה אחרי שנה ולשפר כל אלמנט למקסימום - זה קל (במיוחד אם יש unit tests). לשפר ארכיטקטורה – זה קשה מאוד. על הארכיטרטורה להיות יעילה מבחינת ביצועים מההתחלה.
אתם עשויים להשאר ספקנים, אז אציג דוגמא אחת של שיפור ביצועים שחוויתי על בשרי: דפדפנים.
תחום הדפדפנים החל ברצינות באמצע שנות התשעים – כלומר, ידע רב הצטבר בתחום בחמש עשר השנים האחרונות. 
אני זוכר מקרה שבו אפליקציית web שכתבנו הייתה איטית להחריד. לדף מלא לקח 56 שניות (!) לעלות על reference client hardware, שהיתה מכונה חלשה מהרגיל כדי לייצג את המשתמש עם המחשב שמיושן. המעבר מ explorer 6 ל explorer 7 (שאז היה חדש) שיפר את טעינת הדף לפחות מ 12 שניות. מקור הבעיה היה קובץ javascript גדול במיוחד. ניתוח מקיף הראה שאספלורר 6 ביזבז יותר מ 40 שניות על פיענוח (parsing) של הקובץ בעוד מנוע פיענוח חדש באספלורר 7 הוריד את הזמן לשניות בודדות (הנה דוגמא לסדרי גודל).
"כיצד יכול להיות שיפור כ"כ גדול בגירסה מס' 7 של מוצר?" תהיתי זמן רב. האם הדפדפנים הגיעו לקצה גבול היכולת? עם כל אופטימזציה אפשרית? (צחוק גדול ברקע)

הנה התבוננו במספרים מתוך מבחני ביצועים של האתר הראוי Tomshardware. המבחן בחן דפדפנים שונים על חומרה זהה שבבסיסה מעבד i7-750.


מרכיב משמעותי בימנו הוא הרצה של קוד JavaScript. בשנים האחרונות היו שיפורים משמעותיים בכל הדפדפנים, האם אפשר להשיג יותר?
הנה פיירפוקס 3.6 וכרום 10 ו אקספלורר 9 (מרס 2011) מול פיירפוקס 7, כרום 14 ואקספלורר 9 עם מספר עדכונים (ספטמבר 2011). מוצרים לא לגמרי חדשים, שנוצרו ע"י טובי המהנדסים:
הרצת JavaScript במבחן Kraken 1.1
מהההה???

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


הנה עוד מבט, כמה אפשר עוד לשפר ממוצר קיים ובוגר את זמן העלייה (startup time)?


בחצי השנה הזו Chrome הצליח להוריד את זמן העליה מ6 ל2 שניות - שליש מהזמן המקורי (תוך כדי שיפורים בזמן טעינת הדף) וגם Opera הותיק הציג שיפור חסר תקדים. שאר הדפדפנים נותרו עם 4 עד 5 שניות (בחומרה הנ"ל).


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

והשיפורים לא הגיעו למיצוי: לאחרונה הבחנו בעבודה ש chrome 14 התחיל להשתמש באופטימיזציה של פרוטוקול SSL שקיימת כ10 שנים בתאוריה אך אף דפדפן לא השתמש בה עד עכשיו. להלן חשיבה על ביצועים בארכיטקטורה.


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

אז למה לדחות?
למה עלינו לדחות את שיפור הביצועים? כדי להתמקד בדברים יותר קריטיים מוקדם יותר:
האם ה Software Design מוכיח את עצמו? אם השקעתי יום בשיפור הקוד והדזיין לא טוב – זהו יום מבוזבז. האם המוצר בכלל מעניין לקוחות ומצליח להמכר? אם שיפרתי ביצועי קוד עשרות פעמים עד השחרור כדי לגלות שייצרתי מוצר לא מעניין (אך מהיר) – אלו עשרות ימים שהלכו לפח. גישת האג'ייל מפרטת את הנושא הזה בצורה יסודית.
עוד נקודה היא קריאות הקוד: קוד אופטימלי הוא לרוב פחות קריא. נקודה זו רק מחזקת את הנקודה הקודמת - לא ארצה להשקיע בקריאת קוד קשה לקריאה במשך זמן פיתוח רק בכדי לגלות שהמוצר לא מעניין.
נקודה חשובה אחרונה היא עקרון פארטו שמתאר יפה מאוד את תחום הביצועים התוכנה: 80 אחוז מהשיפורים יושגו ב 20 אחוז מאזורי הקוד. אולי אפילו יותר. להשקיע ולהקשות את הקוד לקריאה ב 100% מהאזורים של הקוד - זו פשוט השקעה לא משתלמת. וב Performance כמו ב Performance יש תמיד הפתעות. שיפור במקום אחד יכול לפתוח מספר צווארי בקבוק ולהשיג שיפור משמעותי מאוד בעוד ששיפור אחר שנראה נהדר על הנייר לא ישפיע כ"כ בפועל כי הקוד ייתקע במקום אחר. חשוב מאוד לבצע pilots של שיפורים ולעשות profiling שוב ושוב בתצורות שונות בכדי להבין את התנהגות ביצועי המערכת.


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


** כדי להשתמש ביחידות אלה יש לכתוב קוד ל instruction-set מיוחד, כגון CUDA. עקרון זה של כתיבת קוד כללי ל GPU נקרא GPGPU.


*** במקור המילה Hacker הייתה תיאור כבוד יקר-ערך. האקרים היו אותם מומחי מחשבים, יחידים במינם, שהבינו לעומק את ה Linux Kernel, דקויות של פרוטוקולי תקשורת וידעו לכתוב קוד יעיל שמתקיים על כמעט-אפס זיכרון או פעולות מעבד. מאז מחולקים באינטרנט כלים אוטומטים לפירוק אתרי-ענק לגורמים, שלא דורשים יותר מהקלדת כתובת ה IP של הקורבן. ההאקרים קראו למשתמשים אלו Script Kiddies בקול מלא בוז – אך השם לא כ"כ תפס. על כל האקר אמיתי שמפצח פירצה וכותב ספרייה לנצל אותה, יש עשרות אלפי Script Kiddies שזכו בתהילה. טננבאום, ה"אנלייזר", הוא דוגמא טובה. עצוב.

5 תגובות:

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

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

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

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

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

    השבמחק