‏הצגת רשומות עם תוויות openssl. הצג את כל הרשומות
‏הצגת רשומות עם תוויות openssl. הצג את כל הרשומות

יום חמישי, ינואר 18, 2024

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

לפני זמן מה עבדתי לפתור בעייה בספרייה מסויימת, במחשבים של המפתחים לא הייתה שום בעייה, אבל במחשבים אצל הלקוח בחלק מהזמן היו פונקציות שפתאום היה לוקח להם באזור השנייה לסיים לסיים בעוד במצב נורמאלי באותו המחשב זה היה לוקח כ 340 מילי שניות. לאחר חפירה די גדולה הגעתי למקטע קוד כזה:
 
     while (0 != (len = (fread(buffer, 1, sizeof(buffer), file))))
     {
         doSomething(buffer,sizeof(buffer));
     }
    
קוד דומה קיים ב openssl

     for (;;) {
         inlen = fread(inbuf, 1, 1024, in);
         if (inlen <= 0)
             break;
         if (!EVP_CipherUpdate(ctx, outbuf, &outlen, inbuf, inlen)) {
             /* Error */
             EVP_CIPHER_CTX_free(ctx);
             return 0;
         }
         fwrite(outbuf, 1, outlen, out);
     }
את זה אפשר לראות  בתיעוד של OpenSSL, עכשיו ל openssl לא ממש צריך להיות אכפת מהביצועים במקרה הקצה הזה, אבל במקומות אחרים זה יכול להיות מאוד חשוב.

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

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

מכיוון שזה היה מקרה מעניין בדקתי מצב דומה על המחשב האישי שלי וייצרתי את שני חלקי הקוד הבאים, ובדקתי תזמון בproduction וראיתי שזה באמת מוריד את המהירות
 
קריאה של בלוק גדול בפעם אחת
#include <stdio.h>
#include <stdlib.h>

int main()
{
    FILE * pt_file;
    char buff[1024];
    int i;
    pt_file= fopen("/tmp/binaryfile","rb");
    if (NULL == pt_file)
    {
       perror("Failed to read binaryfile");
       return 1;
    }
    for (i = 0 ;i < 1024*1024; i++)
    {
       if (1 != fread(buff,sizeof(buff),1 , pt_file))
       {
          fclose(pt_file);
          return 1;
       }
    }
    fclose (pt_file);
   return 0;
}

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

#include <stdio.h>
#include <stdlib.h>

int main()
{
    FILE * pt_file;
    char buff[1024];
    int i;
    pt_file= fopen("/tmp/binaryfile","rb");
    if (NULL == pt_file)
    {
       perror("Failed to read binaryfile");
       return 1;
    }
    for (i = 0 ;i < 1024*1024; i++)
    {
       if (1024 != fread(buff,1, sizeof(buff) , pt_file))
       {
          fclose(pt_file);
          return 1;
       }
    }
    fclose (pt_file);
   return 0;
}
ונבצע בדיקת ביצועיים ע"י hyperfine מקבלים על גבי nvme : 


Benchmark 1: ./out
 Time (mean ± σ):     103.6 ms ±   4.0 ms    [User: 23.6 ms, System: 80.0 ms]
 Range (minmax):   101.3 ms121.6 ms    24 runs

 
ו 
 
hyperfine ./out2
Benchmark 1: ./out2
 Time (mean ± σ):     104.1 ms ±   3.7 ms    [User: 23.5 ms, System: 80.5 ms]
 Range (minmax):   101.1 ms119.3 ms    24 runs

 
 
 

כן אנחנו רואים שאין שום הבדל אמיתי בבדיקה הזאת.

בין הפעלה של כל אחד מהם אני ייצרתי קובץ זבל ע"י dd if=/dev/urandom of=./binaryfile bs=1M count=1024

מכיוון שיש קריאות לcache אז hyperfine לא ייתן לנו זמן אמיתי לבדיקה של הקריאה ממערכת ההפעלה אז בדקתי גם על ידי 
#!/bin/bash
for i in $(seq 1 1 100) ;  
do  
  dd if=/dev/urandom of=./binaryfile bs=1M count=1024
  (time ./out)  >> out.txt 2>&1
done
וגם
#!/bin/bash
for i in $(seq 1 1 100) ;  
do  
  dd if=/dev/urandom of=./binaryfile bs=1M count=1024
  (time ./out2)  >> out2.txt 2>&1
done

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

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

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

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

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