مدیریت حافظه و ماژول gc در پایتون

امیرحسین بیگدلو 3 ماه قبل

# مدیریت حافظه یا Memory Management در پایتون

 

آیا تا به حال فکر کرده اید که پایتون چگونه داده های شما را در پشت صحنه مدیریت می کند؟ متغیرهای شما چگونه در حافظه ذخیره می شوند؟ چه زمانی حذف می شوند؟

 

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

 

همچنین درک بخش داخلی پایتون به شما در فهمیدن بهتر برخی از رفتارهای پایتون کمک می کند. منطق زیادی در پشت صحنه اتفاق می افتد تا اطمینان حاصل شود که برنامه شما همانطور که انتظار دارید کار کند.

 

دوره پیشنهادی: دوره آموزش پایتون (python)

 

# حافظه یک کتاب خالی است

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

 

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

 

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

 

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

 

نویسندگان مانند برنامه ها یا فرایندهای مختلفی هستند که باید داده ها را در حافظه ذخیره کنند. مدیر، که تصمیم می گیرد نویسندگان در کجای کتاب می توانند بنویسند، به نوعی نقش مدیر حافظه را ایفا می کند. شخصی که داستانهای قدیمی را حذف کرد تا جا برای داستانهای جدید باز کند، یک جمع کننده زباله(garbage collector) است.

 

 

# مدیریت حافظه: از سخت‌افزار تا نرم‌افزار

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

 

از طرف دیگر، وقتی دیگر به داده ها نیازی نیست، می توان آنها را حذف یا آزاد کرد. اما آزاد به کجا؟ این "حافظه" از کجا آمده است؟

 

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

 

یکی از لایه های اصلی بالای سخت افزار (مانند RAM یا هارد دیسک) سیستم عامل (OS) است. سیستم عامل درخواست های خواندن و نوشتن حافظه را انجام می دهد (یا رد می کند).

 

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

 

مقاله پیشنهادی: آموزش ساخت وب سرور با پایتون

 

# پیاده سازی پیش فرض پایتون

پیاده سازی پیش فرض پایتون ، CPython ، در واقع به زبان برنامه نویسی C نوشته شده است.

 

وقتی این را برای اولین بار شنیدم، ذهنم را درگیر کرد. زبانی که به زبان دیگری نوشته شده است ؟! خوب، نه واقعا، اما به نوعی  :|  

 

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

 

همچنین برای اجرای کد تفسیر شده بر روی کامپیوتر به چیزی نیاز دارید. پیاده سازی پیش فرض پایتون هر دو مورد را برآورده می کند. مفسر پایتون، کد پایتون شما را به دستورالعمل هایی تبدیل می کند که سپس روی یک ماشین مجازی اجرا می شود.

 

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

 

آیا تا به حال فایل .pyc یا پوشه __pycache__ را دیده اید؟ این کد بایت است که توسط ماشین مجازی تفسیر می شود.

 

توجه به این نکته ضروری است که پیاده سازی هایی غیر از CPython وجود دارد. IronPython کامپایل می شود تا در زمان اجرا زبان مشترک مایکروسافت اجرا شود. Jython برای اجرا بر روی ماشین مجازی جاوا به بایت کد جاوا کامپایل می شود.

 

برای اهداف این مقاله، من روی مدیریت حافظه انجام شده توسط پیاده سازی پیش فرض Python ، یعنی CPython تمرکز می کنم.

 

بسیار خب، بنابراین CPython به زبان C نوشته شده است و بایت کد پایتون را تفسیر می کند. این چه ربطی به مدیریت حافظه دارد؟ الگوریتم ها و ساختارهای مدیریت حافظه در کد CPython هستند. برای درک مدیریت حافظه پایتون، باید یک درک اساسی از خود CPython داشته باشید.

 

CPython به زبان C نوشته شده است که به طور پیشفرض از برنامه نویسی شی گرا پشتیبانی نمی کند. به همین دلیل، طرح های جالبی در کد CPython وجود دارد.

 

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

 

PyObject، پدربزرگ همه اشیاء موجود در پایتون، تنها شامل دو مورد است:

  • ob_refcnt: تعداد مرجع
  • ob_type: اشاره گر به نوع دیگر

 

تعداد مرجع برای جمع آوری زباله استفاده می شود. سپس یک اشاره گر به نوع آبجکت واقعی دارید. آن نوع آبجکت فقط ساختار دیگری است که یک آبجکت پایتون (مانند dict یا int) را توصیف می کند.

 

هر آبجکت دارای تخصیص دهنده حافظه مخصوص شیء خاص خود است که می داند چگونه می تواند حافظه را برای ذخیره آن آبجکت جلب کند. هر آبجکت همچنین دارای یک تخصیص دهنده حافظه مخصوص آبجکت است که حافظه را هنگامی که دیگر مورد نیاز نیست "آزاد" می کند.

 

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

 

مقاله پیشنهادی: GIL پایتون چیست؟

 

# قفل مترجم جهانی (GIL)

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

 

دوباره قیاس کتاب را در نظر بگیرید. فرض کنید دو نویسنده سرسختانه تصمیم می گیرند که نوبت نویسندگی آنها است. نه تنها این، بلکه هر دو باید همزمان در یک صفحه از کتاب بنویسند.

 

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

 

یک راه حل برای این مشکل، قفل کلی و واحد مفسر بر روی مترجم (GIL)است، زمانی که یک موضوع با منبع مشترک (صفحه کتاب) در تعامل است. به عبارت دیگر، فقط یک نویسنده می تواند در یک زمان بنویسد.

 

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

 

مقاله پیشنهادی: آموزش برنامه نویسی فانکشنال در پایتون

 

# آشغال جمع کن(Garbage Collection)

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

 

این داستان قدیمی و بدون رفرنس را می توان با یک شی در پایتون مقایسه کرد که تعداد مرجع آن به 0 کاهش یافته است. به یاد داشته باشید که هر شی در پایتون دارای یک عدد مرجع و اشاره گر به یک نوع است.

 

تعداد مرجع به دلایل مختلف افزایش می یابد. به عنوان مثال، اگر مرجع را به متغیر دیگری اختصاص دهید، تعداد مرجع افزایش می یابد:

numbers = [1, 2, 3]
# Reference count = 1

more_numbers = numbers
# Reference count = 2

 

همچنین اگر شی را به عنوان آرگومان ارسال کنید ، افزایش می یابد:

total = sum(numbers)

 

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

matrix = [numbers, numbers, numbers]

 

پایتون به شما اجازه می دهد تا تعداد مرجع فعلی یک شی را با ماژول sys بررسی کنید. می توانید از sys.getrefcount (numbers) استفاده کنید ، اما به خاطر داشته باشید که عبور از شی برای getrefcount () تعداد مرجع را 1 افزایش می دهد.

 

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

 

اما "آزاد کردن" حافظه به چه معناست و اجسام دیگر چگونه از آن استفاده می کنند؟ بیایید مستقیماً به مدیریت حافظه CPython بپردازیم.

 

 

# مدیریت حافظه در CPython

ما قصد داریم عمیقاً به معماری حافظه و الگوریتم های CPython بپردازیم، پس آماده شوید.

 

همانطور که قبلاً ذکر شد، لایه های انتزاعی از سخت افزار فیزیکی تا CPython وجود دارد. سیستم عامل (OS) حافظه فیزیکی را انتزاع می کند و یک لایه حافظه مجازی ایجاد می کند که برنامه ها (از جمله پایتون) می توانند به آن دسترسی داشته باشند.

 

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

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

 

پایتون از قسمتی از حافظه برای استفاده داخلی و حافظه غیرآبجکتی استفاده می کند. قسمت دیگر به ذخیره سازی آبجکت‌ها (int ، dict و موارد مشابه) اختصاص داده شده است. توجه داشته باشید که این امر تا حدودی ساده شده است. اگر می خواهید درک کامل باشد، می توانید کد منبع CPython را بررسی کنید، جایی که این همه مدیریت حافظه اتفاق می افتد.

 

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

 

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

 

کامنت‌های موجود در کد منبع، تخصیص دهنده را "یک تخصیص دهنده حافظه سریع و ویژه برای بلوک های کوچک توصیف می کند که در بالای یک malloc همه منظوره استفاده می شود." در این مورد، malloc عملکرد کتابخانه C برای تخصیص حافظه است.

 

اکنون ما استراتژی تخصیص حافظه CPython را بررسی می کنیم. ابتدا، ما در مورد 3 قسمت اصلی و نحوه ارتباط آنها با یکدیگر صحبت می کنیم.

 

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

مدیریت حافظه در CPython

 

در داخل arenaها استخرهایی(pool) وجود دارند که یک صفحه حافظه مجازی (4 کیلوبایت) هستند. اینها مانند صفحات کتاب ما هستند. این استخرها به بلوک های کوچکتر حافظه تقسیم می شوند.

 

همه بلوک ها در یک استخر معین از یک "size class" هستند. با توجه به مقداری از داده های درخواست شده، یک کلاس اندازه، سایز بلوک خاصی را تعریف می کند. نمودار زیر مستقیماً از کامنت کد منبع گرفته شده است:

Request in bytes Size of allocated block Size class idx
1-8 8 0
9-16 16 1
17-24 24 2
25-32 32 3
33-40 40 4
41-48 48 5
49-56 56 6
57-64 64 7
65-72 72 8
497-504 504 62
505-512 512 63

 

به عنوان مثال ، اگر 42 بایت درخواست شود، داده ها در یک بلوک با اندازه 48 بایت قرار می گیرند. 

 

 

+ استخرها (pools)

استخرها از بلوک های یک کلاس واحد تشکیل شده اند. هر استخر یک لیست پیوند دوگانه برای سایر استخرهای هم کلاس دارد. به این ترتیب، الگوریتم می تواند به راحتی فضای موجود برای اندازه بلوک معین را حتی در استخرهای مختلف پیدا کند.

 

یک لیست usedpools، همه استخرهایی را که دارای فضای کافی برای داده ها برای هر کلاس اندازه هستند، ردیابی می کند. هنگامی که اندازه بلوک معینی درخواست می شود، الگوریتم این لیست مورد استفاده برای لیست استخرها برای اندازه بلوک را بررسی می کند.

 

خود استخرها باید در یکی از 3 حالت استفاده شده، full یا empty یا used باشند. یک استخر used دارای بلوک های موجود برای ذخیره داده ها است. استخر full همه بلوک های اختصاص داده شده حاوی داده هستند. یک استخر empty هیچ داده ای ذخیره نمی کند و می تواند در صورت نیاز هر کلاس اندازه ای برای بلوک ها اختصاص دهد.

 

لیست freepools همه استخرها را در حالت خالی ذخیره می کند. اما چه موقع استخرهای خالی استفاده میشوند؟

 

فرض کنید کد شما به یک تکه حافظه 8 بایتی نیاز دارد. اگر در استخرهای مورد استفاده در کلاس اندازه 8 بایت استخری وجود نداشته باشد ، یک استخر خالی تازه برای ذخیره بلوک های 8 بایت راه اندازی می شود. این استخر جدید سپس به لیست استفاده شده در استخرها اضافه می شود تا بتواند برای درخواست های بعدی استفاده شود.

 

یک استخر کامل برخی از بلوک های آن را آزاد می کند زیرا دیگر به حافظه نیاز نیست. این استخر برای کلاس اندازه خود به لیست usedpools اضافه می شود.

 

اکنون می توانید ببینید که چگونه استخرها می توانند با این الگوریتم بین این حالتها (و حتی کلاسهای اندازه حافظه) آزادانه حرکت کنند.

 

 

+ بلاک‌ها

مدیریت بلاک‌ها در حافظه پایتون

 

همانطور که در نمودار بالا دیده می شود، استخرها دارای یک اشاره گر به بلوک های "free" حافظه خود هستند. یک طرز کار جزئی در این کار وجود دارد. با توجه به نظرات موجود در کد منبع، این تخصیص دهنده "در همه سطوح (arena، استخر و بلوک) تلاش می کند تا قسمتی از حافظه را لمس نکند تا زمانی که واقعاً مورد نیاز باشد."

 

این بدان معناست که یک استخر می تواند در 3 حالت بلوک داشته باشد. این حالتها را می توان به شرح زیر تعریف کرد:

 

  • untouched: بخشی از حافظه که چیزی به آن اختصاص داده نشده است
  • free: بخشی از حافظه اختصاص داده شده اما بعداً توسط CPython "آزاد" شد و دیگر داده های مربوطه را شامل نمی شود.
  • allocated: بخشی از حافظه که در واقع حاوی داده های مربوطه است

 

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

 

همانطور که مدیر حافظه بلوک ها را "آزاد" می کند، این بلوک های آزاد اکنون به لیست بلوک های آزاد اضافه می شوند. لیست واقعی ممکن است بلوک های مجاور حافظه نباشد، مانند اولین نمودار. ممکن است چیزی شبیه نمودار زیر باشد:

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

 

+ arena

arenaها دارای استخر هستند. این استخرها می توانند used, full, empty باشند. هرچند arena به خودی خود به وضوح وضعیت استخرها را ندارد.

 

در عوض arenaها در یک لیست پیوند دوگانه به نام usable_arenas سازماندهی شده اند. این لیست بر اساس تعداد استخرهای رایگان موجود طبقه بندی شده است. هر چه تعداد استخرهای رایگان کمتر باشد، عرصه به جلوی لیست نزدیکتر است.

 

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

 

این بدان معناست که arenaای که پر از داده است برای قرار دادن داده های جدید در آن انتخاب می شود. اما چرا برعکس نیست؟ چرا داده ها را در جایی که بیشترین فضا در دسترس است قرار ندهید؟

 

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

 

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

 

مقاله پیشنهادی: آموزش اتصال به mongodb با پایتون

 

# نتیجه گیری

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

 

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

 

اگر این مقاله را دوست داشتید، پیشنهاد میکنیم مطالب زیر را نیز ببینید:

تفاوت بین ماژول، پکیج، لایبرری و فریمورک در پایتون

11 توصیه به مبتدیان برای یادگیری پایتون

استفاده از آرگومان های اختیاری توابع پایتون

دوره آموزش Multi Threading در پایتون

ویدیوهای مشابه



ارسال نظر


باب

1 ماه قبل پاسخ به نظر

درود امیرجان
یه سوالی داشتم ، جایی آموزش دادی که به یک فایل چطوری دستورات ارسال کنیم ؟ مثلا python manage.py migrate - در واقعا داریم به این فایل از طریق ترمینال یه دستوری میفرسیتم - توی کدام ویدیوهات هست ؟

ارسال نظر



امیرحسین بیگدلو

1 ماه قبل

سلام
میتونی دوره شل اسکریپت نویسی یا ویدیو تک قسمتی argparse رو ببین


باب

1 ماه قبل

عالی دستت درد نکنه

aryo

3 ماه قبل پاسخ به نظر

عالی بود

ارسال نظر



امیر

7 ماه قبل پاسخ به نظر

شما بهترین مدرسی هستید که برای پایتون میشناسم
خیلی ممنونم برای وقتی که میزارید و این ویدیوها رو برامون درست میکنید

ارسال نظر



Alireza

8 ماه قبل پاسخ به نظر

درود بر شما
عالی بود
ممنون بابت توضیحات مفیدتون

ارسال نظر



میلاد

8 ماه قبل پاسخ به نظر

امیر جان
یکی از منابعی هستی که واقعاً قبولت دارم برای پایتون امیدوارم باز هم قوی تر از قبل ادامه بدی و روحیه تو حفظ کنی
ما به آدمایی مثل شما نیاز داریم❤️

ارسال نظر



محمد

8 ماه قبل پاسخ به نظر

عجب ویدیو خفنی بود. چقدر خوب توضیح دادید نحوه مدیریت حافظه در پایتون رو

ارسال نظر



مونگارد