ארכיון עבור הקטגוריה תכנות

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

אז מה הבעיה עם זליגת זכרון?
הבעיה הראשונה והטריויאלית ביותר היא שתוכנה שזולגת משתמשת ביותר זכרון ממה שהיא צריכה, והמצב מחמיר בהדרגה ככל שאותו קוד שזולג רץ יותר. תוכנה שצורכת יותר מדי זכרון היא לא בהכרח בעיה חמורה. למעשה – כמעט כל תוכנה ברמת מורכבות בינונית ומעלה שנכתבה בשפת C או C++ זולגת זכרון כאילו אין מחר, ובדרך כלל זה לא גורם בעיות כי המשתמש סוגר את התוכנה לפני שהיא מגיע למצב של חוסר זכרון.
הבעיה הזו היא קצת יותר חמור משנדמה במבט ראשון:
כאשר המתכנת מבקש להקצות זכרון, נניח 1MB, הוא מצפה לקבל 1MB של זכרון רציף, שאפשר לעבור עליו בלולאה אחת מתחילתו ועד סופו.
דמיינו מערכת עם 2 מגה זכרון שעומדים לרשות תוכנה שרצה שם, מספיק לכאורה להקצות 2 בלוקים של 1MB.
עכשיו, אם נקצה סך הכל של 1K זכרון, ניתן להניח שנוכל להקצות עוד 1999K ובוודאי שנוכל להקצות 1MB, נכון?
אז זהו – שלא בדיוק : אם נקצה את ה1K בארבע בלוקים של 250 בתים, ובהפרשים של כ500K, למעשה נמנע מהתוכנה אפשרות לקבל זכרון רציף של יותר מכ500K.
כלומר, למרות שהזכרון הפנוי מספיק – בפועל הזכרון הפנוי שבור לחתיכות קטנות מדי (Fragmented), ואין לנו יכולת להקצות את הזכרון הדרוש.
מכאן אפשר להסיק שגם דליפת זכרון קטנה עלולה במצבים מסויימים לגרום לחוסר זכרון קריטי.
אני מקווה שזה שיכנע אתכם שדליפת זכרון זה דבר רע שראוי לטפל בו.

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

import java.util.HashSet;
import java.util.Set;

public class a {
        public static void main(String[] args) {
                Set<String> s = new HashSet<String>();
                int a = 0;
                while(true)
                     s.add(String.valueOf(a++));
        }
}

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

בשפות עם ניהול זכרון ישיר יש מגוון רחב בהרבה של שגיאות שקשורות לזכרון, החל מזכרון שמוקצה ולא משוחרר, זכרון שמשוחרר פעמיים, זכרון שמוקצה עם Malloc של C ומשוחרר עם delete של C++, כתיבה או קריאה אל מחוץ לגבולות הזכרון שהוקצה ועוד ועוד.
הנה תוכנית C++ קטנה שמדגימה חלק מהשגיאות האפשריות:

#include <stdlib.h>

char* get1(){
        return new char[10];
}

char* get2(){
        return (char*)malloc(sizeof(char)*10);
}

char* get3(){
        static char c[10];
        return c;
}

int main() {
        char *s1 = get1();
        char *s2 = get2();
        char *s3 = get3();

        s1[11] = ‘x’; // out of bounds access. mistake
        free(s1); // mistake, should be delete[]
        delete s2; // mistake should be free
        delete s3; // mistake, should not be freed or deleted

        char *s4 = new char[10];
        delete s4; // mistake, should delete with delete[] and not with delete.
        delete s4; // mistake, deleted twice.
        char *s5 = new char[10]; // mistake, never deleted
        return 0;
}

כמו תמיד, C++ נותנת הרבה יותר חבל למתכנת התמים שרוצה לתלות את אח של אח שלו על עץ.
למעשה רק השגיאה האחרונה היא ממש זליגת זכרון, אבל כל השגיאות נפוצות וקשורות לזכרון.
יש טכניקות ידועות ומקובלות שמקטינות מאוד את הסבירות לטעויות שקשורות להקצאה ושחרור זכרון, ספציפית יש טכניקה שמוזכרת בספר The C++ programming language של סטראוסטרופ שנקראת Resource Acquisition Is Initialization או RAII בקיצור.
RAII הוא נושא שראוי לפוסט שלם בפני עצמו, אבל הנה דוגמת שימוש פשוטה (מדי, יש בה לא מעט חורים):

void direct_test(){
        char *buffer = new char[100];
        try{
                work(buffer);
                delete[] buffer;
        }
        catch(){
                delete[] buffer;
                throw;
        }
}

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

class Buffer{
        char *buffer;
        size_t size;

public:
        Buffer(int size){
                buffer = new char[size];
        }

        ~Buffer(){
                delete[] buffer;
        }

        char *get() {return buffer;}
};

void raii_test(){
        Buffer buffer(100); // 100 bytes allocated in constructor
        work(buffer.get());
        // automatically freed when buffer destructor is called (will be called even if an exception is thrown).
}

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

אחד הכלים החזקים ביותר לזיהוי בעיות שקשורות לעבודה עם זכרון הוא valgrind.
ואלגרינד הוא למעשה קבוצה של כלים, כשהידוע בהם הוא memcheck שבמגלה שגיאות בעבודה עם זכרון.
הוא כולל כלים אחרים לניתוח ביצועים (cachegrind, callgrind) ועוד.
ואלגרינד עובד על רוב מערכות ההפעלה ממשפחת היוניקסים (BSD, Linux, MacOS X וכו’), בתאוריה אפשר לבדוק איתו תוכנות חלונות דרך WINE.
ואלגרינד עובד בשורת הפקודה, מה שמרתיע משתמשים רבים. לאחרונה חיפשתי ומצאתי פתרון שמאפשר אינטגרציה של ולגרינד עם CDT:
התמיכה היא כחלק מפרוייקט בשם LinuxTools שמפותח על ידי RedHat (כנראה).
יש פה דמו וידאו של השימוש בפלאגין.
קובץ הוידאו נמצא כאן למי שלא מצליח לראות אותו בבלוג (דורש פיירפוקס 3.5 או דפדפן שתומך בתג וידאו).

video

element.

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

בתקופה האחרונה עבדנו קשה בface.com על API פתוח למציאה וזיהוי פנים, ואתמול בערב השקנו אותו.
זיהוי פנים מורכב מכמה שלבים:
השלב הראשון הוא למצוא אוטומטית היכן בתמונה יש פנים, או face detection.
השלב השני הוא להשוות את הפנים שנמצאו למודל (או מודלים) שהכינונו מבעוד מועד של מי שאנחנו רוצים למצוא. (face recognition)
השלב המקדים הוא להכין את המודל, ובשביל זה צריך לתת דוגמאות אימון של אותו אדם למערכת (traininng).
הAPI תומך כרגע בשלוש קריאות שמתאימות לשלושת השלבים האלו ובעוד כמה קריאות עזר כמו שמירה ואיחזור של טאגים וכו’.
כמובן, יש שימושים רבים למציאת פנים שלא מערבים זיהוי.

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

שחררנו ספריית PHP וספריית JavaScript, אבל זה לא יהיה קשה לכתוב ספריות בשפות נוספות (הAPI מבוסס HTTP, והתשובות חוזרות כJSON או XML).
תתפרעו :)

נ.ב:
יש מגבלה של 200 בקשות זיהוי/מציאת פנים לשעה לAPI KEY, זה בטוח מספיק להתנסות וגם לרוב השימושים, יש עוד מידע פה.

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

נתון מערך בגודל NxN של פיקסלים כאשר כל הפיקסלים שחורים חוץ מאחוז מסויים מהם שאינו שחור ומפוזר באופן אקראי,
בהינתן קואורדינטה (x,y) מסויימת בתמונה, מצא במהירות את כל הנקודות שנמצאות בתוך חלון בגודל K על K (כאשר K קבוע בבעיה) שמרכזו (x,y), משהו כזה:

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

אז הנה הפתרון שלי:
נחלק את התמונה למשבצות בגודל KxK, ונשמור רשימה של כל הנקודות בכל משבצת.
כאשר מבקשים מאיתנו לתת את רשימת הנקודות בחלון מסויים, נסתכל בארבעת החלונות שהחלון הזה חותך ונסנן את כל הנקודות בארבעת החלונות שלא נמצאות בתוך החלון המבוקש.
זה הכל בעצם.
הביצועים של הפתרון הזה מעולים, כדי להכין את הגריד (Grid) נעבור על כל הפיקסלים בתמונה, ולכל נקודה נמצא בזמן קבוע את המשבצת שאליה היא שייכת.
כשממש מחפשים את הנקודות בחלון מסויים, נבדוק במקרה הגרוע 2K*2K פיקסלים (אם כל הנקודות לבנות) אבל במקרה הרגיל נבדוק הרבה פחות (ואם החלון לא מכיל נקודות כלל נדע את זה מהר מאוד).
זה הקוד של העיבוד המקדים:

private static int [][][] buildgrid(byte[][] image, int width, int height, int gridSize)
{
    int grid[][][] = new int[width / gridSize][height / gridSize][];
    // count the number of points in each grid cell
    int numpoints[][] = new int[width / gridSize][height / gridSize];
    for(int x=0;x<width;x++)
    {
        for(int y=0;y<height;y++)
        {
            if (image[x][y] > Byte.MIN_VALUE)
            {
                numpoints[x/gridSize][y/gridSize]++;
            }
        }
    }
   
   
    for(int x=0;x<width;x++)
    {
        for(int y=0;y<height;y++)
        {
            int c = image[x][y];
            if (c > Byte.MIN_VALUE)
            {
                int gy = y/gridSize;
                int gx = x/gridSize;
                int list[] = grid[gx][gy];
                if (list == null)
                {
                        int np = numpoints[gx][gy];
                    list = new int[np * 2];
                    grid[gx][gy] = list;
                    numpoints[gx][gy] = 0;
                }
               
                list[numpoints[gx][gy]++] = x;
                list[numpoints[gx][gy]++] = y;
            }
        }
    }
    return grid;
}

הקוד הבא סופר את המספר הנקודות בתוך החלון שמרכזו (x,y)’ שימו לב שהלולאות עוברות על ארבעה ריבועים בגריד בלבד:

// window boundaries:
int rx1 = x – grid_size/2;
int ry1 = y – grid_size/2;
int rx2 = rx1 + grid_size;
int ry2 = ry1 + grid_size;

// top left grid cell (out of 2×2 group to be checked)
int gx = rx1 / grid_size;
int gy = ry1 / grid_size;

for(int grid_x=gx;grid_x<gx+2 && grid_x >= 0 && grid_x < gwidth;grid_x++)
{
    for(int grid_y=gy;grid_y<gy+2 &&  grid_y >= 0 && grid_y < gheight;grid_y++)
    {
        int list[] = grid[grid_x][grid_y];
        if (list == null) continue;
        for(int i=0;i<list.length/2;i++)
        {
            int jj = i*2;
            int dx = list[jj];
            int dy = list[jj+1];
            if (dx >= rx1 && dx < rx2 && dy >= ry1 && dy < ry2)
            {
                c++;
            }
        }
    }
}

יתכן שאפשר לשפר את הביצועים אם נחלק את הגריד לריבועים יותר קטנים ונכלול ריבועים שלמים שנופלים בתוך החלון בלי לבדוק כל נקודה באופן פרטני, אבל לא ניסיתי.
הביצועים של הפתרון הזה די מטורפים. על Q6600 במהירות 2.4GHZ, עם תמונה בגודל 500X500 פיקסלים וחלון של 20X20, בדיקה של כמה זמן לוקח לספור את הפיקסלים בכל 250,000 החלונות האפשריים לקחה כ160 מילישניות, מה שנותן מהירות בדיקה של מעל 1500 חלונות במילישניה (או יותר ממליון וחצי חלונות בשניה), בלי תלות בגודל התמונה שנבדקת (על תמונה של 1000X1000 המהירות היתה דומה).

האפלט הבאה מדגימה את האלגוריתם. אפשר להוריד אותה מפה. הקוד כלול בתוך הJAR (אפשר לפתוח את הJAR עם תוכנה לפתיחת קבצי ZIP).
בנוסף, האפלט יכולה לרוץ כתוכנית רגילה : java -jar fast_window.jar
אפשר ללחוץ בתוך האפלט כדי לסמן את הנקודות בחלון.


הדפדפן שלך לא תומך בג’אווה, ככה האפלט ניראית (רק שהיא אינטראקטיבית) :


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

כדי לתת מה שהספריה נותנת, חייבים להשתמש בקריאה לפונקציות של מערכת ההפעלה.
בלינוקס העניין היה פשוט יחסית: קטע הקוד המקומי (native code) הוא מינימלי ביותר ומסתמך על inotfiy. הAPI שלו מאוד פשוט אבל הוא לא תומך בהאזנה ריקורסיבית ולכן רוב הקוד כתוב בג’אווה ונועד להוסיף יכולת לקבלת ארועים על ספריות באופן ריקורסיבי (צפיה בתת ספריה על כל בניה).

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

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

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

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

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

JNotify התעורר.

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

  • jps : מראה תהליכי ג’אווה
  • jstack : מציג את רשימת הת’רדים בתהליך ג’אווה, ומה כל אחד מהם עושה.
  • jmap: מאפשר שמירה לקובץ של תמונת הזכרון (heap dump) של תהליך ג’אווה. אפשר לבחון את הקובץ עם jhat או עם VisualVM.
  • jhat : מאפשר ניתוח קבצי heap dump שנוצרו בעזרת jmap.
  • jconsole : ממשק פשוט שמאפשר בחינה של שימוש הזכרון, שימוש בזמן מעבד ועוד.
  • visualvm: כלי חזק יותר מjconsole, שהחל מעדכון 18 של JDK 1.6.0 מגיע עם הJDK. ויז’ואל VM מאפשר גם ניתוח ביצועים פשוט (cpu and memory profiling).

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

VisualVM

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

על המחשב המרוחק:

1. צרו קובץ permissions.txt שיכיל את זה:

grant {
permission java.security.AllPermission;
};

2. הריצו את jstatd (שמאפשר לתהליכים מרוחקים לקבל רשימה של מכונות ג’אווה שרצות על המכונה ולהתחבר אליהן) :

jstatd -J-Djava.security.policy=permissions.txt

3. הריצו את התוכנית שלהם עם הפרמטר הבא:

java -Dcom.sun.management.jmxremote MainClass

על המחשב המקומי:

1. צרו תעלות SSH לשרת כך:

ssh -D9696 -L1101:localhost:1099 your_server

שימו לב שיש פה שתי ‘תעלות’:

  • תעלת TCP מ1101 אל 1099 בשרת המרוחק. זו תעלה שמאפשרת להתחבר אל הjstatd ישירות.
  • פרוקסי SOCKS5 שמאזין על פורט 9696

2. הפעילו את visualvm עם הפרמטרים הבאים :

visualvm -J-Dnetbeans.system_socks_proxy=localhost:9696 -J-Djava.net.useSystemProxies=true

שאומרים לו להשתמש בפרוקסי SOCKS5 שהקמנו.

3. מתוך VisualVM, הוסיפו לחיבור המקומי חיבור לjstatd בפורט 1101.

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

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

שרת חדש

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

למעשה הייתי מרוצה מספיק מeSecureData (אליהם עברתי מHCOOP לפני פחות משנה וחצי).
מהירות התקשורת היתה קצת מאכזבת אבל לא היו יותר מדי תקלות.
אז למה עברתי?
במסגרת העבודה שלי בface.com (כן, אני עובד בface.com, לא חושב שיצא לי להזכיר את זה פה עד עכשיו) אני עובד מול חברת אכסון בשם CentralHost בסן דייגו, ומחזיק אצלם מספר לא מבוטל של שרתים. בשנה וחצי בשנה חצי שאני עובד שם, הייתי מאוד מרוצה מהם ומרמת התמיכה שלהם – אם כי המחיר שלהם הוא גבוה משמעותית מזה של eSecureData, לפחות למכונות החזקות שאני מחזיק שם במסגרת העבודה.
בכל אופן, יש לי יחסים מצויינים עם בעל החברה, וכששאלתי אם יש לו הצעה אלטרנטיבית שתאפשר לי להעביר את השרת שלי אליו הוא הסכים לתת לי מחשב באותו מחיר שאני מקבל בeSecureData – שזה $79 לחודש, עם יותר זכרון (4 ג’יגה במקום 2 ג’יגה), ושני הרדיסקים של 500 בתצורת רייד 1 (ראי, אם הרדיסק אחד מת לא קורה כלום ופשוט מחליפים אותו) לעומת הרדיסק אחד בלבד בשרת הישן.
העברתי בעזרתו את השרת למכונה החדשה, ולמעט שינויי הגדרות קטנים בשרת הDNS שאני מריץ, ובהגדרות הDNS ברשת הדומיינים שלי הכל פשוט עובד.
אני בטוח שיהיו עוד חבלי לידה, אבל אם אתם קוראים את זה, אתם עובדים מול השרת החדש.
אז כמו תמיד, יש לי מקום לאורחים. מי שחושב לעבור שרת למשהו יותר רציני מאיכסון משותף בגודדי שיצור קשר. (רק לאנשים שמרגישים בנוח עם SSH).

BoneCP

השתמשתי בProxool בתור Connection pool לבסיס הנתונים במשך די הרבה זמן, ועם הזמן גיליתי בעיות – שאומנם די נדירות אבל מספיק חמורות כדי שאני אחפש פתרון.
חשבתי לפתור אותן בעצמי, וגיליתי שהוא לא מתקמפל על JDK מודרני. שלחתי מייל לרשימת התפוצה שלו לגבי העניין, וחודשים עברו בלי שום תגובה ובלי הודעות נוספות.
היה ריח של פרוייקט מת באוויר, וכשנתקלתי בבעיה נוספת (100% CPU בקוד של פרוקסול לעיתים נדירות), החלטתי לחפש Connection pool אחד.
בסוף הגעתי לBoneCP שאומנם מוגדר כבטא, אבל עובד היטב – וחשוב יותר – המפתח שלו עונה לאימיילים ומתקן באגים.

טיפוסי אישיות

מי שלמד פסיכולוגיה (לא אני) מכיר את העניין הזה לעומק כנראה, אבל אני מניח שרוב הקוראים פה לא למדו פסיכולוגיה אני אכנס לעניין:
במהלך שיטוטי באינטרנט בחיפוש אחרי ספרייה לDecoding של תמונות בC או C++ (יש מילה בעברית לDecoding? פענוח? פרישה?) נתקלתי בCxImage, וראיתי בדף האודות של המפתח שהוא טוען שהאישיות שלו היא מסוג INTJ.
אני די בטוח שנתקלתי בסוג הקטלוג הזה למחלקות אישיות בעבר, אבל הפעם הסתקרנתי, אם הוא INTJ אז מה אני? וכמה הדבר הזה בכלל עובד?

התחלתי לחקור, מסתבר שמדובר במבחן פסיכומטרי שמבוסס על העבודה של קארל יאנג מ1920 ופותח על ידי קתרין קוק בריגס וביתה איזבל בריגס מיירס, והמבחן קרוי על שמן: Myers-Briggs Type Indicator או MTBI.
בגדול, הרעיון הוא שהאישיות של בני אדם נקבעת על ידי ארבעה תכונות, כאשר לכל תכונה יש שני צדדים (מוחצן – מופנם, חושב – מרגיש) וכו’, ואצל כל אחד תכונה אחת מכל זוג היא יותר דומיננטית מהשניה.

הזוגות הם:

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

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

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

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

בעיית החלוקה

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

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

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

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

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

התקן של HTTP כולל קביעה של שם משתמש וסיסמא ישירות בURL, למשל:

http://user:password@site.com/file.txt

מסיבה לא ברורה, ג’אווה לא מסתדר עם URLים כאלו ונראה שהוא לא מעביר את שם המשתמש והסיסמא לאתר.
הנה פתרון לעניין (עקום אך גנרי) :

Authenticator.setDefault(new Authenticator()
{
    @Override
    protected PasswordAuthentication getPasswordAuthentication()
    {
        URL url = getRequestingURL();
        String userInfo = url.getUserInfo();
        String user;
        String password;
        int i = userInfo.indexOf(‘:’);
        if (i == -1)
        {
            user = userInfo.substring(0);
            password = “”;
        }
        else
        {
            user = userInfo.substring(0, i);
            password = userInfo.substring(i+1);
        }
        PasswordAuthentication ps = new PasswordAuthentication(user, password.toCharArray());
        return ps;
    }
});

עוגיה למי שסביר למה המימוש הסטנדרטי של ג’אווה לא תומך בדבר הטריויאלי הזה.

לפני קצת יותר משנה גוגל שחררו ברשיון קוד פתוח פרוייקט בשם Protocol Buffers, ובקיצור protobuf.
protobuf מגדיר שפה להגדרת מבני נתונים (IDL), ויודע לייצר קוד יעיל שקורא וכותב את אותם מבני נתונים בC++, JAVA או פייתון.
יש תמיכה חיצונית לפורמט בשפות נוספות, C#, D, רובי PHP* ועוד.
הדוגמאות שאני אתן יהיו בג’אווה, אבל השתמשתי בו גם עם C++ והוא עובד היטב גם שם.
כל מפתח מגיע מתי שהוא לשלב שבו תוכנית שהוא כותב צריכה להעביר נתונים לתוכנית אחרת (או למופע אחר של עצמה) דרך הרשת, או לשמור נתונים לקובץ כדי לקרוא אותם אחר כך.
מקובל לקודד את הנתונים בתוך הודעות, כשיש הרבה מאוד דרכים לכתוב את ההודעות, ובדרך כלל מדובר בקוד די סיזיפי שחוזר על עצמו וכתיבתו היא די משעממת (בפעם העשירית שעושים משהו כזה).
נניח שאנחנו רוצים להעביר אובייקט שמייצג אימייל על גבי הרשת (ונתעלם רגע מהפרוטוקולים המקובלים להעברת אימיילים).
לאובייקט אימייל שלנו יש שדה של שולח, אחד או יותר שדות של מקבלים, נושא, טקסט, ואפס או יותר קבצים נלווים ואפילו עדיפות.
אפשר לייצג אותו במבנה הלוגי הזה:

package messages;
message Email
{
        enum Priority
        {
                LOW = 1;
                NORMAL = 2;
                HIGH = 3;
        }

        required string from = 1;
        repeated string to = 2;
        required string subject = 3;
        required string message = 5;
        repeated Attachment attch = 4;
        optional Priority priority = 6;
        optional string date = 7;

        message Attachment
        {
                optional string data = 1;
                required string name = 2;
        }
}

מה שיש לנו פה זה הגדרת הודעה תקינה בשפת הIDL של protobuf.
השפה תומכת בהגדרת חבילה (שמתורגמת לpackage בג’אווה ולnamespace בC++), וכן בהגדרות מקוננות של הודעות.
המספרים אחרי כל שורה נקראים טאגים ומשמשים לזיהוי של השדות בפרוטוקול הבינארי, ולכן אחרי שהם נקבעים אי אפשר לשנות אותם.
אפשר לראות שהשפה תומכת בהגדרה של enum, וכן ברשימות של אובייקטים (שיכולים בעצמם להכיל אובייקטים וכן הלאה).
חוץ מטיפוסים שאתם מגדירים, השפה תומכת גם בטיפוסים פנימיים למשל מחרוזת, משתנה בוליאני, מספר ברוחב קבוע (למשל ארבעה בתים), מספר ברוחב משתנה ועוד.
המספרים ברוחב משתנה מקודדים בצורה דומה קצת לקידוד של UTF8, אבל בצורה קצת יותר פשוטה:

הביט השמאלי (MSB) בכל בייט מוגדר כך: 1 אם המספר המועבר כולל בייטים נוספים, או 0 אם זה סוף המספר.
7 הביטים האחרים בכל בייט משמשים להעברת 7 ביטים של המספר עצמו. בצורה כזו, מספרים קטנים מ128 יתפסו בייט אחד בלבד, מספרים קטנים מ65000 בקירוב יתפסו שני בתים וכן הלאה. אם התוכנית שלכם מעבירה מספרים קטנים שיכולים להיות גדולים לעיתים נדירות – הקידוד הזה הוא אידיאלי כי בדרך כלל המספרים לא יתפסו הרבה מקום, בניגוד לקידוד ברוחב קבוע שבו כל מספר תופס למשל ארבעה בתים.

חלק מהטיפוסים הנתמכים בשפה הם ברוחב קבוע, למשל מספרים בנקודה צפה (Floating point) ושלמים שמוגדרים כFIXED, למשל FIXED64 יהיה תמיד 64 ביטים או 8 בתים.
עוד על השפה אפשר לקרוא פה.

את הקובץ שמכיל את הגדרת ההודעות “מקמפלים” עם protoc, שמייצר ממנו קוד בג’אווה C++ או פייתון:

$ protoc messages.proto –java_out src/

הנה דוגמא לקוד ג’אווה שמייצר הודעה, כותב אותה לקובץ, קורא אותה ומדפיס אותה בפורמט טקסטואלי:

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

import test.Messages.Email;
import test.Messages.Email.Attachment;
import test.Messages.Email.Builder;

class Test
{
    public static void main (String args[]) throws FileNotFoundException, IOException
    {
        Builder b = Email.newBuilder();
        b.addTo(“test@abc.com”);
        b.addTo(“test@loogle.com”);
        b.setFrom(“someone@there.com”);
        Attachment aa = Attachment.newBuilder().setName(“viruts.exe”).build();
        b.addAttch(aa);
        b.setSubject(“A present for you”);
        b.setMessage(“Please open the attached Virus”);
        Email email = b.build();
        System.out.println(email.toString());
        FileOutputStream out = new FileOutputStream(“email.dat”);
        email.writeTo(out);
        out.close();

        System.out.println(Email.parseFrom(new FileInputStream(“email.dat”)));
    }
}

נקמפל ונריץ:

$ javac -cp lib/protobuf-java-2.2.0.jar src/Test.java src/test/Messages.java
$ java -cp lib/protobuf-java-2.2.0.jar:src Test
from: “someone@there.com”
to: “test@abc.com”
to: “test@loogle.com”
subject: “A present for you”
attch {
  name: “viruts.exe”
}
message: “Please open the attached Virus”

אפשר לראות שבמפתיע הפלט הטקסטואלי הוא כמעט קובץ Swush תקני, אני נשבע שלא הסתכלתי עליו כשהגדרתי את Swush :)
זו דוגמא לשימוש בprotobuf בג’אווה, השימוש בC++ בשפות אחרות פשוט בצורה דומה.
כל העניין מאוד קל לשימוש, ואני ממליץ מאוד לכל מי שצריך לכתוב אובייקטים לקובץ או לרשת לבדוק את protobu.
אגב, הקוד המחולל נראה טוב מאוד, כמעט כאילו כתבתם אותו בעצמכם.

* PHP : הספריה לתמיכה בPHP היא pb4php, והיא לא מאוד מוצלחת. למרות שיתכן שבקרוב הפרוייקט יקבל PATCH משמעותי שהופך את הספריה ליותר שמישה (לפחות לצרכים שלי).

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

public class EmailSender
{
        private static EmailSender s_instance;
       
        private EmailSender(){} // private constructor
       
        public static synchronized EmailSender getInstance()
        {
                if (s_instance == null)
                        s_instance = new EmailSender();
                return s_instance;
        }
       
        public void sendEmail(String recepient, String subject, String text)
        {
                // …
        }
}

כדי להשתמש בסינגלטון ככה:

EmailSender.getInstance().sendEmail();

כמה דברים לשים אליהם לב:
* יש בנאי פרטי, כדי למנוע מכל מני גורמים “לא מוסמכים” לאתחל עותק משל עצמם של הסינגלטון.
* הפונקציה getInstance צריכה להיות מסונכרנת כדי למנוע מצב שבו שני ת’רדים שונים ניגשים במקביל לפונקציה וגורמים לאיתחול של שני אובייקטים במקום אחד.

עד כאן, טרויאלי לכל מי שכתב תוכנה בג’אווה.
הפוסט הזה הוא על דרך אלטרנטיבית לכתיבת סינגלטון, והרי היא:

public enum EmailSender
{
        instance;
       
        public void sendEmail(String recepient, String subject, String text)
        {
                // …
        }
}

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

EmailSender.instance.sendEmail();

אלגנטי, לא?

גוגל וויב (Wave) הוא מיקס מגניב של אימייל, צ’אט, וויקי ועוד.
סיכמתי פה כמה נקודות מהווידאו, אבל כדאי לראות את הווידאו ולהשתכנע בעצמכם.

וויב

  • אפשרות לפצל שיחה לכמה קווי שיחה בקלות (Threads)
  • צ’אט חי, כמו בימי הבבסים (רואים מה האדם השני מקליד בזמן שהוא מקליד).
  • אפשרות לחבר אדם נוסף לשיחה בדיעבד על ידי הוספה שלו למשתתפים, הוא יוכל לראות את השיחה מההתחלה, כולל באמצעות מצב ניגון שמנגן את השיחה בשבילו צעד אחר צעד כאילו הוא השתתף בה מההתחלה.
  • אפשרות להגביל גישה להודעות למשתתפים מסויימים (מסרים פרטיים)
  • תמיכה בגרור וזרוק כדי להצמיד תמונות (וקבצים באופן כללי) להודעות, נכון לכרגע זה דורש גוגל gears, אבל הכוונה היא לדחוף את היכולת הזו בהצעה לHTML 5.
  • אפשרות לשתול שיחה בקלות בתוך בלוג (Embed), מכיוון שהשיחה מאוכסנת בשרת מרכזי זה לא משנה אם מגיבים מהבלוג אם מתוך Wave, היא תשאר שיחה אחת.
  • כל שיחה יכול להיות בעצם מסמך משותף שכל המשתתפים תורמים לו, בדומה לוויקי, כולל פיצול ואיחוד של גרסאות (Branch and merge), כולל עריכה חיה (כמו הצ’אט החי) על ידי כמה משתתפים במקביל.

הרחבות

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

פרוטוקול

  • ההצלחה של אימייל נובעת מכך שהפרוטוקול פתוח וכל אחד יכול להריץ שרת דואר שידבר עם כל שרת דואר אחד בפרוטוקול SMTP, שמוגדר היטב וברור לכל.
  • וויב יעבוד בצורה דומה. הפרוטוקול יהיה פתוח, ובנוסף גוגל ישחררו את כל הקוד של השרת כדי לאפשר לכל אחד שרוצה להריץ שרת וויב שידבר עם שרתי וויב אחרים בצורה שקופה למשתמשים (כמו Jabber).

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

מחכה בקוצר רוח שהדבר הזה יבשיל, יהיה טעים.

FireStats icon ‏מריץ FireStats‏