مدیریت حافظه در یونیتی

مدیریت حافظه در یونیتی

گام 1: مقدمه

تمامی آرایه ها، رشته ها و شی هایی را که می سازیم توسط حافظه به یک مکان مرکزی به نام HEAP یا پشته اختصاص داده می شوند و هر زمان که این بخش های خاص برای مدت طولانی مورد استفاده قرار نگیرند، حافظه برای فعالیت های دیگری مورد استفاده قرار می گیرد. در گذشته تخصیص و آزاد کردن بلاک حافظه از هیپ ها صریحا بر عهده برنامه نویس بود و این کار از طریق فراخوانی توابع مرتبط صورت می پذیرفت، اما امروزه مدیریت حافظه به طور خودکار و با استفاده از سیستم ران تایمی مانند موتور توسعه Mon یونیتی صورت می پذیرد. مدیریت خودکار حافظه به کمی کدنویسی نیز نیاز دارد، اما نسبت به تخصیص و آزاد کردن صریح حافظه به میزان چشم گیری از نشتی حافظه می کاهد.

گام 2: رفرنس و انواع مقادیر

زمانی که یک تابع فراخوانی می شود، مقادیر آرگومنت های آن در فضایی از حافظه که برای این فراخوانی خاص در نظر گرفته شده کپی می شوند. انواع داده ها که تنها مقدار بایت کمی را به خود اختصاص می دهند به سرعت و سادگی کپی می شوند، اما شرایط برای آرایه ها، شی ها و رشته ها کمی متفاوت است، چرا که این موارد حجم بیشتری دارند و کپی شدن به صورت منظم برای آنها مناسب نیست.

خوشبختانه این حالت اجباری نیست، حافظه واقعی جهت ذخیره سازی یک آیتم بزرگ از یک اشاره گر کوچک تخصیص داده می شود و مقدار پشته برای به خاطرسپاری آدرس آن در حافظه مورد استفاده قرار می گیرد. پس از آن، اشاره گر مذکور در طول ارسال آرگومنت کپی می شود. آیتمی که با اشاره گر شناخته شده قادر به قرارگیری در سیستم ران تایم است و می توان از یک کپی منفرد از آیتم داده ها برای هر نیازمندی استفاده کرد.

انواعی که در طول ارسال آرگومنت ها کپی شده و به طور مستقیم ذخیره می شوند، انواع مقادیر نام دارند که شامل کاراکتر، مقادیر شناور، مقادیر صحیح، بولین و انواع ساختاری مختص یونیتی مانند رنگ و Vector3 می باشند. انواعی که در ناحیه پشته ذخیره شده و سپس با استفاده از اشاره گر به آنها دسترسی پیدا می شود رفرنس نام دارند، چرا که مقادیر ذخیره شده در متغیر صرفا به داده واقعی ارجاع پیدا می کند. رشته ها، شی ها و آرایه ها مثال هایی از انواع رفرنس هستند.

گام 3: تخصیص حافظه و Garbage Collection

بخش حافظه استفاده نشده از پشته به طور مستمر توسط مدیر حافظه پیگیری می شود، مدیر حافظه زمانی که یک بلاک جدید از حافظه موردنیاز باشد، فضای استفاده نشده را به آن اختصاص می دهد. این روال تا زمانی که درخواست های بعدی مدیریت شوند همچنان ادامه دارد. به احتمال بسیار کمی تمامی حافظه تخصیص داده در حال استفاده می باشد. یک آیتم رفرنس در پشته تنها تا زمان وجود متغیرهای رفرنس که مکان آن را مشخص می کنند، قابل دسترسی است. چنانچه تمامی رفرنس های یک بلاک حافظه آزاد شوند، حافظه ای که درگیر آن بوده با امنیت کامل مجددا تخصیص داده می شود.

به منظور تشخیص بلاک های پشته که در حال حاضر استفاده نمی شوند، مدیر حافظه در تمامی متغیرهای ارجاعی فعال جستجو می کند و آنها را live برچسب گذاری می نماید. در پایان فرآیند جستجو، مدیر حافظه هر فضایی بین بلاک های حافظه را خالی در نظر می گیرد و می توان از آنها برای تخصیص های بعدی حافظه استفاده کرد. فرآیند تخصیص و آزاد کردن حافظه استفاده نشده garbage collection یا به اختصار GC نام دارد.

گام 4: بهینه سازی حافظه

GC به طور دستی نبوده و قابل مشاهده توسط برنامه نویس نمی باشد، اما فرآیند تخصیص نیازمند تایم CPU چشم گیری است. چنانچه به طور دقیق از مدیریت حافظه خودکار استفاده شود، با تخصیص حافظه دستی برابری می کند و گاه از لحاظ کارآیی کلی بهتر نیز ظاهر می شود. توسعه دهندگان باید از خطاهایی که به طور بیهوده کالکتور را تریگر می کنند اجتناب ورزند، چرا که این امر به وقفه هایی در طول فرآیند اجرا تن می دهد. برخی از الگوریتم در ظاهر خوبند، اما در عمل به کابوسی برای Garbage Collection تبدیل می شوند.

گام 5: مثال

functionStringConatinationExample (integerArray: int[])

{

varmyLine=integerArray[0].ToString();

for(var i=1; i<integerArray.Length;i++)

{

myLine +=”,”+integerArray[i].ToString();

}

return myLine;

}

شایان توجه است که بخش های جدید یکی یکی به رشته جاری اضافه نمی شوند، در حقیقت در طول حلقه، آخرین محتوا از متغیر myLine از بین می رود و یک رشته جدید برای در بر گرفتن بخش اصلی همراه با موقعیت جدید در آخر تخصیص داده می شود. از آنجایی که رشته با افزایش مقادیر متغیرها بزرگ می شود، مقدار فضای پشته مصرفی نیز افزایش پیدا کرده و به راحتی هزاران بایت از فضای خالی پشته با هر بار فراخوانی متد مورد استفاده قرار می گیرد. برای الحاق می توانید از کلاس System.Text.StringBuilder استفاده کنید، الحاق تکراری در صورتی که به صورت مکرر فراخوانی نشود، مشکل آفرین نیست ولی در یونیتی که معمولا بروزرسانی مکرر فریم داریم باید مورد توجه قرار گیرد.

برای مثال:

varmyScoreBoard: GUIText;

varmyScore: int;

function Update()

{

varmyScoreText: String=”My Score: “+myScore.ToString();

myScoreBoard.text=myScoreTxt;

}

هر بار با فراخوانی تابع Update یک رشته جدید تخصیص داده شده و تریکل ثابتی از garbage جدید تولید می شود، چنانچه تنها در زمانی که myScore تغییر می کند اقدام به آپدیت کردن متن کنید، صرفه جویی صورت می پذیرد.

مثال 1:

varmyScoreBoard: GUIText;

varmyScoreText: String;

varmyScore: int;

varmyOldScore: int;

function Update()

{

if(myScore != myOldScore)

{

myScoreText = “My Score: “+myScore.ToString();

myScoreBoard.text=myScoreText;

myOldScore=myScore;

}

}

زمانی که یک مقدار آرایه توسط تابع بازگشت داده می شود، مشکلی دیگر بوجود می آید.

functionRandomList(numberOfElements:int)

{

varmyResult=new float[numberOfElements];

for(var i=0; i<numberOfElements; i++)

{

myResult[i]=Random.value;

}

returnmyResult;

}

این نوع تابع زمانی که آرایه با مقادیر پر شده باشد، بسیار مناسب است. با این حال اگر به طور ثابت فراخوانی شود، حافظه جدید هر بار تخصیص داده می شود. از آنجایی که به احتمال زیاد آرایه ها بسیار بزرگ هستند، فضای آزاد پشته به سرعت مورد استفاده قرار می گیرد و به garbage collections تکراری منجر می شود. به منظور پیشگیری از این مشکل، از این واقعیت که آرایه از نوع رفرنس است استفاده کنید. یک آرایه که به عنوان یک آرگومنت به تابع فرستاده شده را می توان داخل تابع تغییر داد و نتیجه تا زمان بازگشت داده شدن تابع باقی می ماند.

functionRandomList(myArray: int)

{

for(var i=0;i<myArray.Length;i++)

{

myArray[i]=Random.value;

}

}

مثال 2:

در این مثال مقادیر کنونی درون آرایه با آخرین مقادیر جایگزین می شوند، این کار نیازمند تکمیل تابع فراخوانی کد برای تخصیص اولیه آرایه است.

Garbage Collection

همانطور که پیش تر نیز اشاره شد، جلوگیری از تخصیص های غیرضروری تا جای ممکن بسیار خوب است، با این حال که نمی توان آنها را به طور کامل حذف کرد، اما دو استراتژی وجود دارد که می توانید آنها را مورد استفاده قرار دهید.

Garbage collection سریع و مستمر (با پشته کوچک)

این استرتژی برای بازی هایی که گیم پلی بزرگی دارند و نرخ فریم روان بخش اصلی بازی است، بهترین گزینه می باشد. یک بازی که به طور معمول اندازه بلاک کوچکی را به طور مستمر تخصیص می دهد، اما این بلاک ها تنها به طور خلاصه مورد استفاده قرار خواهند گرفت. اندازه پشته در صورت استفاده از این استراتژی در آی او اس چیزی حدود 200 کلیوبایت است و garbage collection نیز چیزی حدود 5 میلی ثانیه در یک آیفون 3G به طول می انجامد. چنانچه اندازه پشته به 1 مگابایت افزایش پیدا کند، کالکشن حدود 7 میلی ثانیه طول می کشد. درخواست garbage collection در هر وقفه زمانی مناسب بسیار سودمند خواهد بود و باعث می شود که کالکشن به مراتب بیشتری به وقوع بپیوندد و پردازش سریع تر شده و کمترین تاثیر منفی را بر روی گیم پلی بگذارد.

if(Time.frameCount % 30 ==0)

{

System.GC.Collect();

}

اما حتی با این وجود، باید در صورت استفاده از این تکنیک آمار پروفایلر را بررسی کنید و مطمئن شوید که واقعا در حال کاهش زمان کالکشن است.

 Garbage collection آهسته اما به ندرت برای پشته های بزرگ

این استراتژی برای بازی هایی که تخصیص به ندرت به وقوع می پیوندد مورد استفاده قرار می گیرد و می توان از آن برای مدیریت در طول وفقه ها در گیم پلی استفاده کرد. بهترین حالت زمانی است که پشته به اندازه ممکن بزرگ باشد، اما نه آنقدر بزرگ که بازی توسط سیستم عامل متوقف شود و یا به حافظه پایین سیستم منجر شود. ران تایم Momo در صورت امکان از گسترش خودکار پشته جلوگیری می کند. با پیش تخصیص مقداری فضای پلیس-هولدر در زمان راه اندازی می توانید پشته را به صورت دستی گسترش دهید.

function Start()

{

var tmp =new System.Object[1024];

//make allocations in smaller blocks to avoid them to be treated in a special way, which is designed for large blocks

for(var i : int=0; i<1024; i++)

tmp[i]=new byte[1024]

}

//release reference

tmp=null;

}

یک پشته به اندازه کافی بزرگ را نباید به طور کامل بین این وقفه ها در گیم پلی که برای کالکشن آماده سازی می شوند، پر کرد. زمانی که چنین وقفه ای به وقوع می پیوندد، می توانید به طور صریح تقاضای یک کالکشن کنید.

System.GC.Collect();

همواره باید در استفاده از این استراتژی به آمار پروفایلر توجه کنید، چرا که تصور اینکه تاثیر مورد انتظار را خواهد داشت، اشتباه است.

گام 6: استفاده مجدد از مجموعه آبجکت ها

موارد متعددی وجود دارند که می توان با کاهش تعداد آبجکت ها ساخته شده و از بین رفته، از تولید garbage جلوگیری کرد. برخی از انواع آبجکت ها در بازی ها، مانند projectiles، ممکن است مرتبا استفاده شوند، حتی اگر تعداد کمی به طور  همزمان درگیر باشند. در مواردی مشابه، این امکان وجود دارد که به جای از بین بردن آبجکت های قدیمی، آنها را مجددا مورد استفاده قرار داده و آنها را با آبجکت های جدید جایگزین نمایید.

 

http://www.theappguruz.com برگرفته از

اینها را هم بخوانید