עבור לתוכן

C# זכרון של תכנית מתנפח לי אין לי מושג על מה

Featured Replies

פורסם

שלום אנשים.

אני קצת עושה התנסויות בסוקטים. כתבתי תכנית ששולחת קבצים לסרבר.

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

המצב הוא כזה:

כשאני מריץ את התכנית (WINDOWS FORM), היא תופסת בזכרון כ 3.7MB

לאחר שאני מוסיף 80 קבצים לרשימת LISTVIEW, הזכרון עולה לכ 4.3MB

עד כאן זה בסדר גמור.

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

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

לאחר שכל הקבצים נשלחו, כשאני מסתכל על כמות הזכרון, היא על 47MB!

ניסיתי לעשות DISPOSE לאובייקטים שאפשר, ולתת לאובייקטים בסיום השימוש NULL (כדי שאוסף הזבל יעלה עליהם?..)

איך אני פותר בעיות זכרון?

יש לציין, שאם אחרי זה אני שולח שוב את אותם 80 קבצים, כשהזכרון הוא כבר על 47MB, הוא נשאר בערך אותו דבר לאחר השליחה השניה של כל הקבצים. כלומר, הזכרון לא מתנפח בכל פעם שאני מבצע שליחה, הוא מתייצב על 47 לאחר כל שליחה. אבל אני מצפה שלאחר השליחה, הוא יחזור ל 3-4 מגה כמו שהוא היה בתחילת הריצה של התכנית.

רעיונות מישהו?

פורסם

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

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

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

אני מציע לך לעשות עוד דבר.

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

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

פורסם
  • מחבר

תודה רבה על התגובה.

המצב הוא ממש מוזר.

ניסתי לראות מה קורה עם שליחה של קובץ אחד.

הזכרון קובץ ל11 מגה.

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

הבעיה העיקרית פה, היא שאני לא ממש מוצא עקביות.

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

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

אני אחטט שם שוב יותר ביסודיות לראות מה לא משתחרר שם.

יש טיפים לגבי ניקוי זכרון ב C# חוץ מלעשות DISPOSE למה שאפשר או להציב NULL באובייקטים?

הנה הפונקציה ששולחת קובץ:


public int SendFile(string FilePath)
{
if (!this.IsConnected()) //Connect if neccesary
if (this.Connect() == false)
return -1; //server offline

StreamWriter writeFileData;
NetworkStream nStream;
string Base64ImageData;
string BlockData;
int RemainingStringLength = 0;
bool Done = false;
nStream = client.GetStream();
writeFileData = new StreamWriter(nStream);
int i;
for (i = FilePath.Length - 1; i >= 0 && FilePath[i] != '\\' && FilePath[i] != '/'; i--) ;//set i to begining of filename
i++;
string filename=FilePath.Substring(i,FilePath.Length-i);//get filename without path

FileStream fs = File.OpenRead(FilePath); //Open file
byte[] ImageData = new byte[fs.Length];
fs.Read(ImageData, 0, ImageData.Length); //Read file bytes to fs

Base64ImageData = Convert.ToBase64String(ImageData);
int startIndex = 0;

try
{
byte[] filename_len = new byte[sizeof(int)];
writeFileData.Write('F');
writeFileData.Flush();
filename_len = BitConverter.GetBytes((int)filename.Length);
nStream.Write(filename_len,0,sizeof(int));
writeFileData.Write(filename);
writeFileData.Flush();
while (Done == false)
{
while (startIndex < Base64ImageData.Length)
{
try
{
BlockData = Base64ImageData.Substring(startIndex, 1000);
writeFileData.WriteLine(BlockData);
writeFileData.Flush();
startIndex += 1000;
}
catch
{
RemainingStringLength = Base64ImageData.Length - startIndex;
BlockData = Base64ImageData.Substring(startIndex, RemainingStringLength);
writeFileData.WriteLine(BlockData);
writeFileData.Flush();
Done = true;
break;
}
}
}
writeFileData.Close();

//CLEAN
writeFileData.Dispose();
nStream.Dispose();
Base64ImageData = null;
BlockData = null;
nStream.Dispose();
writeFileData.Dispose();
fs.Dispose();
Array.Resize(ref ImageData, 0);
ImageData = null;
}
catch (Exception er)
{
MessageBox.Show("Unable to connect to server\r\n"+er.Message);
}

//if all went OK return Bytes sent:
//return Base64ImageData.Length;
return 1;
}

עדכון

אוקיי אחרי דבאג צעד צעד, אני חושב שמה שמקפיץ את הזכרון זו השורה הבאה:

Base64ImageData = Convert.ToBase64String(ImageData);

אחרי שהשורה הזאת מתבצעת הזכרון קופץ ב 3 מגה בערך.

איך אני יכול לסדר את זה?

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

הבעיה היא לא ב Base64ImageData שאליו אני עושה את ההשמה.

הזכרון קופץ גם אם אני ארשום רק את השורה :

Convert.ToBase64String(ImageData);

פורסם

אני לא רוצה להפריע.. אבל כאשר אתה עושה את הפעולה הזו:

Base64ImageData = Convert.ToBase64String(ImageData);

נראה לי שאתה מכניס את כל התמונה לתוך משתנה (והמשתנה כמובן נמצא בזיכרון).

האם אני טועה?

פורסם

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

פורסם
  • מחבר

אני לא רוצה להפריע.. אבל כאשר אתה עושה את הפעולה הזו:

Base64ImageData = Convert.ToBase64String(ImageData);

נראה לי שאתה מכניס את כל התמונה לתוך משתנה (והמשתנה כמובן נמצא בזיכרון).

האם אני טועה?

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

מה שלא מתפנה מהזכרון, זה הפעולה ש CONVERT עושה, בלי שום קשר להשמה שעשיתי.

טיפ קטן (אני לא חושב שזה מה שגורם לבעיה' date=' אבל בכל זאת): עדיף להשתמש ב-using מאשר להשתמש ב-Dispose. משתמשים ב-Dispose רק כאשר אתה מקצה ומשחרר את האובייקט בשתי פונקציות שונות (ואז אתה לא יכול להשתמש ב-using).

[/quote']

מה זאת אומרת להשתמש ב USING? לא הבנתי ממש מה הם קשורים. USING זה כמו INCLUDE עד כמה שאני יודע, לא? ולמה בדיוק אני יעשה USING?

אפשר דוגמא?

--- לא משנה הבנתי מה זה. תודה. (אגב, לא פותר את הבעיה).

פורסם

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

תפעיל את ה-Garbage Collection ידנית.

http://www.developer.com/net/csharp/article.php/3343191/C-Tip-Forcing-Garbage-Collection-in-NET.htm

פורסם
  • מחבר

וואי תודה, מגניב. חשבתי שלא ניתן להפעיל אתו ידנית.

בכל מקרה, עכשיו הצלחתי סידרתי את הבעיה, שיניתי קצת את המימוש של שליחת הקובץ.

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

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

תודה לכולם.

פורסם

זה מה שניסיתי לומר לך קודם.

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

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

הייתי ממליץ לך ליצור CONST ב-INT שיסמן כמה בתים לשלוח כל פעם.

ככה יהיה לך יותר יעילות בקוד ונוחות לשדרוג.

פורסם

גם ליידע את הGC מתי לרוץ בקוד זה לא כזה "תכנות חכם".

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

אני חושב שהבנתי את ה"בעיה" שיש לך.

אתה קורא קובץ גדול לתוך אובייקט (זה קורה כאשר אתה עושה את הbase64 של התמונה - שאגב מגדיל את האובייקט ב20% בערך מגודל התמונה). לכן הפריימוורק שומר את האובייקט בlarge object heap (איזור בזכרון אשר מוגדר לאובייקטים שהם מעל 20000 בייטים), שההבדל בינו לבין שאר הheapים שיש לפרוסס dotnetי (generation0,1,2) הוא שלאחר שהGC ניקה את האובייקט מהזכרון, הוא לא מקטין את הheap.

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

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

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

זה דיי על רגל אחת, אני מציע לך לקרוא קצת על המנגנון הזה.

כדאי להתחיל עם זה במאמר הזה:

http://msdn.microsoft.com/en-us/magazine/bb985010.aspx

http://msdn.microsoft.com/en-us/magazine/bb985011.aspx (בעמוד השני מציגים שיטה לבחון את מצב הheapים של הפרוסס שלך)

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

פורסם
  • מחבר

תודה על התגובה המפורטת.

זה באמת מייצג את הבעיה שהייתה לי.

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

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

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

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

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

תודה.

ארכיון

דיון זה הועבר לארכיון ולא ניתן להוסיף בו תגובות חדשות.

דיונים חדשים