ArrayList vs Array as a sample to misleading performance point of view

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

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

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

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

אם נלך לחפש ספר על ביצועים ב- JAVA  - http://www.google.co.il/search?sourceid=chrome&ie=UTF-8&q=amazon+java+performance
 בראש רשימת התוצאות:

הספר הוא על JDK1.3 , היום אנחנו כבר ב-7. מאז מעטים הספרים שיצאו והספר כבר לא יצא במהדורה חדשה.

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

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

אני רוצה להתייחס כאן למספר נקודות ביחס לביצועים:
באחד הפוסטים באינטרנט ראיתי השוואת ביצועים של קריאה בין array to arraylist.
המסקנה שלו שהבדלי הביצועים הם פי -5.
בואו נראה מה הוא עושה שם:
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class ArrayListvArray {

      private static final Integer INT_VALUE = 1000;

      public static Integer[] INT_ARRAY = new Integer[50000];
      public static List ARRAY_LIST;

      public static void main(String... args) {

            ARRAY_LIST = Collections.nCopies(INT_ARRAY.length, INT_VALUE);
            Arrays.fill(INT_ARRAY, INT_VALUE);

            readFromArray();
            readFromArrayList();
      }

      private static long readFromArray() {

            int j;
            long start = System.nanoTime();

            for (int i = 0; i < INT_ARRAY.length; i++) {
                  j = INT_ARRAY[i]; //
            }
            long timeTaken = (System.nanoTime() - start);
            System.out.println("to read Array     " + timeTaken + " nano seconds");
            return timeTaken;
      }

      private static long readFromArrayList() {

            int j;
            long start = System.nanoTime();
            for (Integer integer : ARRAY_LIST) {
                  j = integer;
            }
            long timeTaken = (System.nanoTime() - start);

            // the console output should prove what ARRAY_LIST really is e.g.
            // ARRAY_LIST.getClass().getSimpleName()
            System.out.println("to read Arraylist " + timeTaken + " nano seconds");
            return timeTaken;
      }
}

to read Array     989346 nano seconds
to read Arraylist 4753743 nano seconds
ratio  =  4.8
שינוי של ה- MAIN  באופן הבא יוביל ליחס ביצועים נמוך יותר:
      INT_ARRAY = new Integer[3000000];
ARRAY_LIST = Collections.nCopies(INT_ARRAY.length, INT_VALUE);
        Arrays.fill(INT_ARRAY, INT_VALUE);
       
        readFromArray();
        readFromArrayList();
       
        INT_ARRAY = new Integer[50000];
ARRAY_LIST = Collections.nCopies(INT_ARRAY.length, INT_VALUE);
        Arrays.fill(INT_ARRAY, INT_VALUE);
        readFromArray();
        readFromArrayList();

to read Array     986588 nano seconds
to read Arraylist 3101551 nano seconds
ration =3.14

מדוע-? התשובה – JIT  ועל כך בהמשך.
והנה למדנו כי ישנם דרכים מסוימות שישפיעו על מדידת הביצועים.

נשים את ה- hot spot בצד ונחזור לגדלים המקוריים ונראה איך אנחנו משפרים את הביצועים.
בואו נשנה את האיטרציה לקריאה מה- arraylist לאופן הבא :
            for (int i = 0; i < ARRAY_LIST.size(); i++) {
                  j = ARRAY_LIST.get(i);
            }

to read Array     921603 nano seconds
to read Arraylist 2910928 nano seconds
ratio =  3.15

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

מה המסקנה:
1.   אל תאמינו לכל מה שאתם קוראים באינטרנט.
2.   בידוד משתנים  –  אחד הגורמים החשובים בניסוי השוואתי הוא בידוד משתנים. כך גם כאשר מודדים ביצועים יש לבודד משתנים. למשל בדוגמה הזאת בקריאה נכנסו כמה משתנים נוספים כמו autoboxing. כאשר מודדים ביצועים יש לבודד היטב את הגורם אותו רוצים לבדוק וזה אומר כתיבה מבודדת של הגורם. התחשבות ב- cache של מסד הנתונים, דיסק, כמות אובייקטים שכבר בזיכרון , J ITועוד מגוון גדול של דברים.



אבל!!!!!!!
הנקודה היותר חשובה שצריכה להילקח כאן- את מי זה מעניין.
את מי זה מעניין שביצועי arraylist הם פי 5.
את מי זה מעניין ביצועים כפקטור של אחוזים- המעניין בביצועים זה מה התקורה הנוספת במינוחים אבסולוטיים על פני האלטרנטיבה ולאור כמות הנתונים וה – use case  שקיים לכם.

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

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



אין תגובות:

הוסף רשומת תגובה