مدیریت حافظه

مرور کلی

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

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

مهم است بدانید که مدیریت هیپ پایتون توسط خودِ مفسر انجام می‌شود و کاربر هیچ کنترلی بر آن ندارد، حتی اگر به‌طور منظم اشاره‌گرهای اشیاء به بلوک‌های حافظه درون آن هیپ را دستکاری کند. تخصیص فضای هیپ برای اشیاء پایتون و بافرهای داخلی دیگر، به‌صورت درخواستی توسط مدیر حافظه پایتون از طریق توابع Python/C API فهرست‌شده در این سند انجام می‌شود.

برای پرهیز از خرابی حافظه، نویسندگان ماژول‌های توسعه‌ای هرگز نباید سعی کنند با توابع اکسپورتشده توسط کتابخانه C: malloc()، calloc()، realloc() و free() روی اشیاء پایتون عمل کنند. این کار به فراخوانی‌های ترکیبی میان تخصیص‌دهنده C و مدیر حافظه پایتون با پیامدهای مرگبار منجر خواهد شد، زیرا آن‌ها الگوریتم‌های متفاوتی را پیاده‌سازی می‌کنند و روی هیپ‌های متفاوتی کار می‌کنند. با این حال، می‌توان برای مقاصد شخصی، بلوک‌های حافظه را با تخصیص‌دهنده کتابخانه C به‌طور ایمن تخصیص داد و آزاد کرد، همان‌طور که در مثال زیر نشان داده شده است:

PyObject *res;
char *buf = (char *) malloc(BUFSIZ); /* برای ورودی/خروجی */

if (buf == NULL)
    return PyErr_NoMemory();
...انجام برخی عملیات ورودی/خروجی با buf...
res = PyBytes_FromString(buf);
free(buf); /* تخصیص‌یافته با malloc */
return res;

در این مثال، درخواست حافظه برای بافر ورودی/خروجی توسط تخصیص‌دهنده‌ی کتابخانه C برطرف می‌شود. مدیر حافظه‌ی پایتون تنها در تخصیص شیء بایتی که به‌عنوان نتیجه بازگردانده می‌شود نقش دارد.

با این حال، در بیشتر موارد توصیه می‌شود حافظه از هیپ پایتون تخصیص داده شود، دقیقاً به این دلیل که دومی تحت کنترل مدیر حافظه پایتون است. برای مثال، این کار زمانی لازم است که مفسر با نوع‌های جدید شیء نوشته‌شده به زبان C توسعه یابد. دلیل دیگر استفاده از هیپ پایتون، تمایل به آگاه‌سازی مدیر حافظه پایتون از نیازهای حافظه‌ای ماژول توسعه‌ای است. حتی وقتی حافظه درخواست‌شده صرفاً برای مقاصد داخلی و بسیار خاص استفاده می‌شود، واگذاری تمام درخواست‌های حافظه به مدیر حافظه پایتون باعث می‌شود مفسر تصویری دقیق‌تر از ردپای کلی حافظه خود داشته باشد. در نتیجه، در شرایط خاص، مدیر حافظه پایتون ممکن است کنش‌های مناسب مانند زباله‌روبی، فشرده‌سازی حافظه یا سایر رویه‌های پیشگیرانه را فعال کند یا نکند. توجه داشته باشید که با استفاده از تخصیص‌دهنده کتابخانه C، همان‌طور که در مثال قبلی نشان داده شد، حافظه تخصیص‌یافته برای بافر ورودی/خروجی کاملاً از دسترس مدیر حافظه پایتون خارج می‌شود.

همچنین ملاحظه نمائید

متغیر محیطی PYTHONMALLOC می‌تواند برای پیکربندی تخصیص‌دهنده‌های حافظه‌ای که پایتون به کار می‌گیرد، استفاده شود.

متغیر محیطی PYTHONMALLOCSTATS می‌تواند هر بار که یک آرنا (arena) جدید برای اشیاء pymalloc ایجاد می‌شود و هنگام خاموش شدن، آمار تخصیص‌دهنده حافظه pymalloc را چاپ کند.

دامنه‌های تخصیص‌دهنده

تمام توابع تخصیص‌دهنده به یکی از سه «قلمرو» (domain) متفاوت تعلق دارند (همچنین PyMemAllocatorDomain را ببینید). این قلمروها نماینده‌ی راهبردهای تخصیص متفاوت هستند و برای اهداف مختلف بهینه شده‌اند. جزئیات دقیق اینکه هر قلمرو چگونه حافظه را تخصیص می‌دهد یا هر قلمرو چه توابع داخلی را فراخوانی می‌کند، از جزئیات پیاده‌سازی به شمار می‌آید، اما برای اهداف اشکال‌زدایی می‌توان یک جدول ساده‌شده را در تخصیص‌دهنده‌های حافظه پیش‌فرض یافت. APIهایی که برای تخصیص و آزاد کردن یک بلوک حافظه استفاده می‌شوند، باید از یک قلمرو واحد باشند. به‌عنوان مثال، برای آزاد کردن حافظه‌ای که با PyMem_Malloc() تخصیص داده شده است، باید از PyMem_Free() استفاده کرد.

سه دامنه‌ی تخصیص عبارت‌اند از:

  • دامنه خام: برای تخصیص حافظه به بافرهای حافظه‌ی همه‌منظوره در مواردی در نظر گرفته شده است که تخصیص باید به تخصیص‌دهنده‌ی سیستم برود یا تخصیص‌دهنده بتواند بدون attached thread state کار کند. حافظه مستقیماً از سیستم درخواست می‌شود. به رابط حافظه خام مراجعه کنید.

  • دامنه‌ی "Mem": برای تخصیص حافظه به بافرهای پایتون و بافرهای حافظه‌ی همه‌منظوره در مواردی که تخصیص باید با attached thread state انجام شود، در نظر گرفته شده است. حافظه از هیپ خصوصی پایتون گرفته می‌شود. به رابط حافظه مراجعه کنید.

  • دامنه‌ی شیء: برای تخصیص حافظه به اشیای پایتون در نظر گرفته شده است. این حافظه از هیپ خصوصی پایتون گرفته می‌شود. به تخصیص‌دهنده‌های شیء مراجعه کنید.

توجه

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

برای مثال، بافرها (اشیاء غیرپایتونی) باید با استفاده از PyMem_Malloc()، PyMem_RawMalloc() یا malloc() تخصیص داده شوند، اما نه با PyObject_Malloc().

به API‌های تخصیص حافظه مراجعه کنید.

رابط حافظه خام

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

تخصیص‌دهنده‌ی حافظه‌ی خام پیش‌فرض از توابع زیر استفاده می‌کند: malloc()، calloc()، realloc() و free()؛ هنگام درخواست صفر بایت، malloc(1) (یا calloc(1, 1)) فراخوانی می‌شود.

اضافه شده در نسخه‌ی 3.4.

void *PyMem_RawMalloc(size_t n)
قسمتی از ABI پایدار از نسخه‌ی 3.13.

n بایت تخصیص می‌دهد و اشاره‌گری از نوع void* به حافظه‌ی تخصیص‌یافته برمی‌گرداند، یا اگر درخواست شکست بخورد، NULL.

درخواست صفر بایت در صورت امکان یک اشاره‌گر متمایز غیر NULL برمی‌گرداند، گویی به جای آن PyMem_RawMalloc(1) فراخوانی شده است. حافظه به هیچ شکلی مقداردهی اولیه نخواهد شد.

void *PyMem_RawCalloc(size_t nelem, size_t elsize)
قسمتی از ABI پایدار از نسخه‌ی 3.13.

nelem عنصر که هر یک به اندازه‌ی elsize بایت است تخصیص می‌دهد و اشاره‌گری از نوع void* به حافظه‌ی تخصیص‌یافته برمی‌گرداند، یا در صورتی که درخواست شکست بخورد، NULL برمی‌گرداند. حافظه با صفر مقداردهی اولیه می‌شود.

درخواست صفر المان یا المان‌هایی با اندازه‌ی صفر بایت، در صورت امکان یک اشاره‌گر متمایز غیر NULL برمی‌گرداند؛ گویی به جای آن PyMem_RawCalloc(1, 1) فراخوانی شده است.

اضافه شده در نسخه‌ی 3.5.

void *PyMem_RawRealloc(void *p, size_t n)
قسمتی از ABI پایدار از نسخه‌ی 3.13.

اندازه بلوک حافظه‌ای را که p به آن اشاره می‌کند به n بایت تغییر می‌دهد. محتویات تا کمینه‌ی اندازه‌های قدیمی و جدید بدون تغییر باقی می‌مانند.

اگر p برابر NULL باشد، این فراخوانی معادل PyMem_RawMalloc(n) است؛ در غیر این صورت، اگر n برابر صفر باشد، بلوک حافظه تغییر اندازه می‌یابد اما آزاد نمی‌شود، و اشاره‌گر برگردانده‌شده غیر NULL است.

مگر آنکه p برابر NULL باشد، باید توسط فراخوانی قبلیِ PyMem_RawMalloc()، PyMem_RawRealloc() یا PyMem_RawCalloc() بازگردانده شده باشد.

اگر درخواست شکست بخورد، PyMem_RawRealloc() مقدار NULL را برمی‌گرداند و p همچنان اشاره‌گر معتبری به ناحیه حافظه قبلی باقی می‌ماند.

void PyMem_RawFree(void *p)
قسمتی از ABI پایدار از نسخه‌ی 3.13.

بلوک حافظه‌ای را که p به آن اشاره می‌کند آزاد می‌کند، که باید قبلاً توسط فراخوانی PyMem_RawMalloc()، PyMem_RawRealloc() یا PyMem_RawCalloc() بازگردانده شده باشد. در غیر این صورت، یا اگر PyMem_RawFree(p) قبلاً فراخوانی شده باشد، رفتار تعریف‌نشده رخ می‌دهد.

اگر p برابر NULL باشد، هیچ عملیاتی انجام نمی‌شود.

رابط حافظه

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

در ساخت‌ دارای قفل مفسر سراسری (ساخت‌ پیش‌فرض)، تخصیص‌دهنده‌ی حافظه‌ی پیش‌فرض از تخصیص‌دهنده‌ی حافظه‌ی pymalloc استفاده می‌کند، در حالی که در free-threaded build، پیش‌فرض تخصیص‌دهنده‌ی حافظه‌ی mimalloc است.

هشدار

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

تغییر یافته در نسخه‌ی 3.6: تخصیص‌دهنده پیش‌فرض اکنون pymalloc است، به‌جای malloc() سیستمی.

تغییر یافته در نسخه‌ی 3.13: در ساخت‌ نخ‌آزاد، تخصیص‌دهنده‌ی پیش‌فرض اکنون mimalloc است.

void *PyMem_Malloc(size_t n)
قسمتی از ABI پایدار.

n بایت تخصیص می‌دهد و اشاره‌گری از نوع void* به حافظه‌ی تخصیص‌یافته برمی‌گرداند، یا اگر درخواست شکست بخورد، NULL.

درخواست صفر بایت در صورت امکان یک اشاره‌گر متمایز و غیر NULL برمی‌گرداند، گویی که به‌جای آن PyMem_Malloc(1) فراخوانی شده است. این حافظه به هیچ شکلی مقداردهی اولیه نشده خواهد بود.

void *PyMem_Calloc(size_t nelem, size_t elsize)
قسمتی از ABI پایدار از نسخه‌ی 3.7.

nelem عنصر که هر یک به اندازه‌ی elsize بایت است تخصیص می‌دهد و اشاره‌گری از نوع void* به حافظه‌ی تخصیص‌یافته برمی‌گرداند، یا در صورتی که درخواست شکست بخورد، NULL برمی‌گرداند. حافظه با صفر مقداردهی اولیه می‌شود.

درخواست صفر المان یا المان‌هایی با اندازه‌ی صفر بایت، در صورت امکان یک اشاره‌گر متمایز غیر NULL برمی‌گرداند، گویی به‌جای آن PyMem_Calloc(1, 1) فراخوانی شده بود.

اضافه شده در نسخه‌ی 3.5.

void *PyMem_Realloc(void *p, size_t n)
قسمتی از ABI پایدار.

اندازه بلوک حافظه‌ای را که p به آن اشاره می‌کند به n بایت تغییر می‌دهد. محتویات تا کمینه‌ی اندازه‌های قدیمی و جدید بدون تغییر باقی می‌مانند.

اگر p برابر NULL باشد، این فراخوانی معادل PyMem_Malloc(n) است؛ در غیر این صورت، اگر n برابر صفر باشد، بلوک حافظه تغییر اندازه می‌یابد اما آزاد نمی‌شود و اشاره‌گر بازگردانده‌شده غیر NULL است.

مگر اینکه p برابر NULL باشد، این مقدار باید توسط یک فراخوانی قبلی به PyMem_Malloc()، PyMem_Realloc() یا PyMem_Calloc() بازگردانده شده باشد.

اگر درخواست ناموفق باشد، PyMem_Realloc() مقدار NULL را برمی‌گرداند و p همچنان اشاره‌گر معتبری به ناحیه‌ی حافظه قبلی باقی می‌ماند.

void PyMem_Free(void *p)
قسمتی از ABI پایدار.

بلوک حافظه‌ای را که p به آن اشاره می‌کند آزاد می‌کند؛ این بلوک باید قبلاً توسط فراخوانی PyMem_Malloc()، PyMem_Realloc() یا PyMem_Calloc() بازگردانده شده باشد. در غیر این صورت، یا اگر PyMem_Free(p) پیش‌تر فراخوانی شده باشد، رفتار تعریف‌نشده رخ می‌دهد.

اگر p برابر NULL باشد، هیچ عملیاتی انجام نمی‌شود.

ماکروهای نوع‌محور زیر برای سهولت فراهم شده‌اند. توجه داشته باشید که TYPE به هر نوع C اشاره می‌کند.

PyMem_New(TYPE, n)

مانند PyMem_Malloc() است، اما (n * sizeof(TYPE)) بایت حافظه تخصیص می‌دهد. اشاره‌گری قالب‌ریزی‌شده به TYPE* برمی‌گرداند. حافظه به هیچ شکلی مقداردهی اولیه نشده است.

PyMem_Resize(p, TYPE, n)

مانند PyMem_Realloc() است، اما بلوک حافظه به (n * sizeof(TYPE)) بایت تغییر اندازه می‌یابد. اشاره‌گری قالب‌ریزی‌شده به TYPE* را برمی‌گرداند. هنگام بازگشت، p اشاره‌گری به ناحیه حافظه جدید خواهد بود، یا در صورت شکست NULL.

این یک ماکروی پیش‌پردازنده C است؛ p همیشه دوباره انتساب می‌شود. مقدار اصلی p را ذخیره کنید تا هنگام مدیریت خطاها حافظه از دست نرود.

void PyMem_Del(void *p)

مانند PyMem_Free() است.

نام‌های مستعار منسوخ

این‌ها نام‌های مستعار منسوخ نرم برای توابع و ماکروهای موجود هستند. این نام‌های مستعار صرفاً برای سازگاری با نسخه‌های پیشین وجود دارند.

نام مستعار منسوخ

تابع یا ماکروی متناظر

PyMem_MALLOC(size)

PyMem_Malloc()

PyMem_NEW(type, size)

PyMem_New

PyMem_REALLOC(ptr, size)

PyMem_Realloc()

PyMem_RESIZE(ptr, type, size)

PyMem_Resize

PyMem_FREE(ptr)

PyMem_Free()

PyMem_DEL(ptr)

PyMem_Free()

تغییر یافته در نسخه‌ی 3.4: این ماکروها اکنون نام‌مستعار توابع و ماکروهای متناظر هستند. پیش‌تر، رفتار آن‌ها یکسان بود، اما استفاده از آن‌ها لزوماً سازگاری دودویی را بین نسخه‌های پایتون حفظ نمی‌کرد.

منسوخ شده از نسخه‌ی 2.0.

تخصیص‌دهنده‌های شیء

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

توجه

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

تخصیص‌دهنده پیش‌فرض اشیاء از تخصیص‌دهنده حافظه pymalloc استفاده می‌کند. در ساخت نخ‌آزاد، پیش‌فرض به‌جای آن تخصیص‌دهنده حافظه mimalloc است.

هشدار

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

void *PyObject_Malloc(size_t n)
قسمتی از ABI پایدار.

n بایت تخصیص می‌دهد و اشاره‌گری از نوع void* به حافظه‌ی تخصیص‌یافته برمی‌گرداند، یا اگر درخواست شکست بخورد، NULL.

درخواست صفر بایت در صورت امکان یک اشاره‌گر متمایز غیر NULL بازمی‌گرداند، گویی که به جای آن PyObject_Malloc(1) فراخوانی شده است. حافظه به هیچ شکلی مقداردهی اولیه نخواهد شد.

void *PyObject_Calloc(size_t nelem, size_t elsize)
قسمتی از ABI پایدار از نسخه‌ی 3.7.

nelem عنصر که هر یک به اندازه‌ی elsize بایت است تخصیص می‌دهد و اشاره‌گری از نوع void* به حافظه‌ی تخصیص‌یافته برمی‌گرداند، یا در صورتی که درخواست شکست بخورد، NULL برمی‌گرداند. حافظه با صفر مقداردهی اولیه می‌شود.

درخواست صفر عنصر یا عناصری با اندازه‌ی صفر بایت، در صورت امکان، یک اشاره‌گر متمایز غیر NULL برمی‌گرداند، گویی که به جای آن PyObject_Calloc(1, 1) فراخوانی شده باشد.

اضافه شده در نسخه‌ی 3.5.

void *PyObject_Realloc(void *p, size_t n)
قسمتی از ABI پایدار.

اندازه بلوک حافظه‌ای را که p به آن اشاره می‌کند به n بایت تغییر می‌دهد. محتویات تا کمینه‌ی اندازه‌های قدیمی و جدید بدون تغییر باقی می‌مانند.

اگر p برابر NULL باشد، این فراخوانی معادل PyObject_Malloc(n) است؛ در غیر این صورت، اگر n برابر صفر باشد، بلوک حافظه تغییر اندازه می‌یابد اما آزاد نمی‌شود و اشاره‌گر بازگردانده‌شده غیر NULL است.

مگر اینکه p برابر NULL باشد، باید قبلاً توسط فراخوانی PyObject_Malloc()، PyObject_Realloc() یا PyObject_Calloc() برگردانده شده باشد.

اگر درخواست شکست بخورد، PyObject_Realloc() مقدار NULL را برمی‌گرداند و p همچنان اشاره‌گر معتبری به ناحیه حافظه قبلی باقی می‌ماند.

void PyObject_Free(void *p)
قسمتی از ABI پایدار.

بلوک حافظه‌ای را که p به آن اشاره می‌کند آزاد می‌کند، که باید توسط یک فراخوانی قبلی به PyObject_Malloc()، PyObject_Realloc() یا PyObject_Calloc() برگردانده شده باشد. در غیر این صورت، یا اگر PyObject_Free(p) پیش‌تر فراخوانی شده باشد، رفتار تعریف‌نشده رخ می‌دهد.

اگر p برابر NULL باشد، هیچ عملیاتی انجام نمی‌شود.

برای آزاد کردن حافظه‌ی یک شیء، این را مستقیماً فراخوانی نکنید؛ در عوض جایگاه tp_free نوع را فراخوانی کنید.

از این برای حافظه‌ای که توسط PyObject_GC_New یا PyObject_GC_NewVar تخصیص‌یافته است استفاده نکنید؛ به جای آن از PyObject_GC_Del() استفاده کنید.

همچنین ملاحظه نمائید

تخصیص‌دهنده‌های حافظه پیش‌فرض

تخصیص‌دهنده‌های پیش‌فرض حافظه:

پیکربندی

نام

PyMem_RawMalloc

PyMem_Malloc

PyObject_Malloc

ساخت‌ انتشار (Release build)

"pymalloc"

malloc

pymalloc

pymalloc

ساخت اشکال‌زدایی

"pymalloc_debug"

malloc + اشکال‌زدایی

pymalloc + دیباگ

pymalloc + دیباگ

ساخت‌ انتشار، بدون pymalloc

"malloc"

malloc

malloc

malloc

ساخت دیباگ، بدون pymalloc

"malloc_debug"

malloc + اشکال‌زدایی

malloc + اشکال‌زدایی

malloc + اشکال‌زدایی

ساخت‌ نخ‌آزاد

"mimalloc"

mimalloc

mimalloc

mimalloc

ساخت اشکال‌زداییِ نخ‌آزاد

"mimalloc_debug"

mimalloc + دیباگ

mimalloc + دیباگ

mimalloc + دیباگ

راهنما:

سفارشی‌سازی تخصیص‌دهنده‌های حافظه

اضافه شده در نسخه‌ی 3.4.

type PyMemAllocatorEx

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

فیلد

معنا

void *ctx

زمینه کاربر که به‌عنوان آرگومان اول ارسال می‌شود

void* malloc(void *ctx, size_t size)

تخصیص دادن یک بلوک حافظه

void* calloc(void *ctx, size_t nelem, size_t elsize)

تخصیص دادن یک بلوک حافظه مقداردهی اولیه‌شده با صفر

void* realloc(void *ctx, void *ptr, size_t new_size)

تخصیص دادن یا تغییر اندازه دادن یک بلوک حافظه

void free(void *ctx, void *ptr)

آزاد کردن یک بلوک حافظه

تغییر یافته در نسخه‌ی 3.5: ساختار PyMemAllocator به PyMemAllocatorEx تغییر نام یافت و یک فیلد جدید calloc افزوده شد.

type PyMemAllocatorDomain

enum برای شناسایی دامنه‌ی تخصیص‌دهنده استفاده می‌شود. دامنه‌ها:

PYMEM_DOMAIN_RAW

توابع:

PYMEM_DOMAIN_MEM

توابع:

PYMEM_DOMAIN_OBJ

توابع:

void PyMem_GetAllocator(PyMemAllocatorDomain domain, PyMemAllocatorEx *allocator)

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

void PyMem_SetAllocator(PyMemAllocatorDomain domain, PyMemAllocatorEx *allocator)

تخصیص‌دهنده بلوک حافظه‌ی دامنه‌ی مشخص‌شده را تنظیم می‌کند.

تخصیص‌دهنده جدید باید هنگام درخواست صفر بایت، یک اشاره‌گر متمایز غیر NULL بازگرداند.

برای دامنه‌ی PYMEM_DOMAIN_RAW، تخصیص‌دهنده باید نخ‌ایمن باشد: هنگامی که تخصیص‌دهنده فراخوانی می‌شود، یک thread state متصل نیست.

برای دامنه‌های باقی‌مانده، تخصیص‌دهنده باید نخ‌ایمن نیز باشد: تخصیص‌دهنده ممکن است در مفسر‌های مختلفی فراخوانی شود که یک GIL را به اشتراک نمی‌گذارند.

اگر تخصیص‌دهنده جدید یک قلاب نباشد (تخصیص‌دهنده قبلی را فراخوانی نکند)، باید تابع PyMem_SetupDebugHooks() فراخوانی شود تا قلاب‌های اشکال‌زدایی مجدداً بر روی تخصیص‌دهنده جدید نصب شوند.

همچنین ببینید PyPreConfig.allocator و پیش‌راه‌اندازی پایتون با PyPreConfig.

هشدار

PyMem_SetAllocator() قرارداد زیر را دارد:

  • برای نصب یک تخصیص‌دهنده حافظه سفارشی، می‌توان آن را پس از Py_PreInitialize() و پیش از Py_InitializeFromConfig() فراخوانی کرد. هیچ محدودیتی بر تخصیص‌دهنده‌ی نصب‌شده وجود ندارد، جز محدودیت‌هایی که توسط دامنه اعمال می‌شوند (برای نمونه، دامنه خام اجازه می‌دهد که تخصیص‌دهنده بدون attached thread state فراخوانی شود). برای اطلاعات بیشتر به بخش دامنه‌های تخصیص‌دهنده مراجعه کنید.

  • اگر پس از پایان مقداردهی اولیه پایتون (پس از فراخوانی Py_InitializeFromConfig()) فراخوانی شود، تخصیص‌دهنده باید تخصیص‌دهنده موجود را دربرگیرد. جایگزین کردن تخصیص‌دهنده فعلی با تخصیص‌دهنده دلخواه دیگری پشتیبانی نمی‌شود.

تغییر یافته در نسخه‌ی 3.12: همه‌ی تخصیص‌دهنده‌ها باید نخ‌ایمن باشند.

void PyMem_SetupDebugHooks(void)

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

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

هنگامی که پایتون در حالت اشکال‌زدایی ساخته می‌شود، تابع PyMem_SetupDebugHooks() در پیش‌مقداردهی پایتون فراخوانی می‌شود تا قلاب‌های اشکال‌زدایی را برای تشخیص خطاهای حافظه روی تخصیص‌دهنده‌های حافظه پایتون برپا کند.

از متغیر محیطی PYTHONMALLOC می‌توان برای نصب قلاب‌های اشکال‌زدایی روی پایتونی که در حالت انتشار کامپایل شده است، استفاده کرد (مثال: PYTHONMALLOC=debug).

از تابع PyMem_SetupDebugHooks() می‌توان برای تنظیم قلاب‌های اشکال‌زدایی پس از فراخوانی PyMem_SetAllocator() استفاده کرد.

این قلاب‌های اشکال‌زدایی بلوک‌های حافظه‌ی تخصیص‌یافته به‌صورت پویا را با الگوهای بیتیِ خاص و قابل تشخیص پر می‌کنند. حافظه‌ی تازه تخصیص‌یافته با بایت 0xCD (PYMEM_CLEANBYTE) پر می‌شود و حافظه‌ی آزادشده با بایت 0xDD (PYMEM_DEADBYTE) پر می‌شود. اطراف بلوک‌های حافظه با «بایت‌های ممنوع» که با بایت 0xFD (PYMEM_FORBIDDENBYTE) پر شده‌اند، احاطه شده است. رشته‌هایی از این بایت‌ها به احتمال زیاد آدرس‌های معتبر، اعداد ممیز شناور یا رشته‌های اسکی معتبر نیستند.

بررسی‌های ران‌تایم:

  • تشخیص نقض‌های API. برای مثال، تشخیص اینکه آیا PyObject_Free() روی بلوک حافظه‌ای که توسط PyMem_Malloc() تخصیص داده شده است، فراخوانی می‌شود یا خیر.

  • تشخیص نوشتن پیش از ابتدای بافر (زیرریز بافر (buffer underflow)).

  • تشخیص نوشتن فراتر از انتهای بافر (سرریز بافر).

  • بررسی کنید که هنگام فراخوانی توابع تخصیص‌دهنده‌ی دامنه‌های PYMEM_DOMAIN_OBJ (مثلاً PyObject_Malloc()) و PYMEM_DOMAIN_MEM (مثلاً PyMem_Malloc())، یک attached thread state وجود داشته باشد.

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

فرض کنید S = sizeof(size_t) باشد. به هر یک از دو انتهای هر بلوک از N بایت درخواست‌شده، 2*S بایت اضافه می‌شود. چیدمان حافظه به این صورت است، که در آن p نشانی بازگردانده‌شده توسط تابعی شبیه به malloc یا realloc را نشان می‌دهد (p[i:j] به معنای اسلایس بایت‌ها از *(p+i) با احتساب آن تا *(p+j) بدون احتساب آن است؛ توجه داشته باشید که نحوه برخورد با اندیس‌های منفی با اسلایس پایتون متفاوت است):

p[-2*S:-S]

تعداد بایت‌هایی که در ابتدا درخواست شده است. این یک size_t بزرگ‌اندیان است (که خواندن آن در برون‌ریزی حافظه آسان‌تر است).

p[-S]

شناسه API (نویسه اسکی):

p[-S+1:0]

رونوشت‌هایی از PYMEM_FORBIDDENBYTE. برای گرفتن نوشتن‌ها و خواندن‌های پیش از ابتدای ناحیه (under-writes and reads) استفاده می‌شوند.

p[0:N]

حافظه‌ی درخواستی با رونوشت‌هایی از PYMEM_CLEANBYTE پر می‌شود تا ارجاع به حافظه‌ی مقداردهی‌نشده رهگیری شود. هنگامی که تابعی شبیه realloc برای درخواست بلوک حافظه‌ی بزرگ‌تر فراخوانی می‌شود، بایت‌های اضافی جدید نیز با PYMEM_CLEANBYTE پر می‌شوند. هنگامی که تابعی شبیه free فراخوانی می‌شود، این بایت‌ها با PYMEM_DEADBYTE بازنویسی می‌شوند تا ارجاع به حافظه‌ی آزادشده رهگیری شود. هنگامی که تابعی شبیه realloc برای درخواست بلوک حافظه‌ی کوچک‌تر فراخوانی می‌شود، بایت‌های اضافی قدیمی نیز با PYMEM_DEADBYTE پر می‌شوند.

p[N:N+S]

کپی‌هایی از PYMEM_FORBIDDENBYTE. برای رهگیری نوشتن‌ها و خواندن‌های خارج از محدوده استفاده می‌شوند.

p[N+S:N+2*S]

تنها زمانی استفاده می‌شود که ماکروی PYMEM_DEBUG_SERIALNO تعریف شده باشد (به‌طور پیش‌فرض تعریف نشده است).

شماره سریالی که در هر فراخوانی تابعی شبیه malloc یا realloc، ۱ واحد افزایش می‌یابد. size_t بزرگ‌اندیان. اگر بعدها «حافظه‌ی خراب» تشخیص داده شود، شماره سریال راهی عالی فراهم می‌کند تا در اجرای بعدی نقطه‌وقفی (breakpoint) تعیین کنید و لحظه‌ی تحویل داده شدن این بلوک را به‌دست آورید. تابع ایستای bumpserialno() در obmalloc.c تنها جایی است که شماره سریال در آن افزایش می‌یابد، و وجود دارد تا شما بتوانید چنین نقطه‌وقفی را به‌سادگی تعیین کنید.

تابعی شبیه realloc یا free ابتدا بررسی می‌کند که بایت‌های PYMEM_FORBIDDENBYTE در هر دو انتها سالم باشند. اگر تغییر کرده باشند، خروجی تشخیصی به stderr نوشته می‌شود و برنامه از طریق Py_FatalError() خاتمه می‌یابد. حالت شکست اصلی دیگر، ایجاد خطای حافظه هنگامی است که برنامه یکی از الگوهای بیتی خاص را می‌خواند و تلاش می‌کند از آن به‌عنوان آدرس استفاده کند. اگر در چنین موقعیتی وارد اشکال‌زدا شوید و به شیء نگاه کنید، به احتمال زیاد می‌بینید که کاملاً با PYMEM_DEADBYTE (به این معنا که حافظه‌ی آزادشده در حال استفاده است) یا PYMEM_CLEANBYTE (به این معنا که حافظه‌ی مقداردهی‌نشده در حال استفاده است) پر شده است.

تغییر یافته در نسخه‌ی 3.6: تابع PyMem_SetupDebugHooks() اکنون روی پایتونی که در حالت انتشار کامپایل شده است نیز کار می‌کند. در صورت خطا، قلاب‌های اشکال‌زدایی اکنون از tracemalloc برای به‌دست‌آوردن ردگیری محل تخصیص یک بلوک حافظه استفاده می‌کنند. قلاب‌های اشکال‌زدایی اکنون همچنین بررسی می‌کنند که آیا هنگام فراخوانی توابع دامنه‌های PYMEM_DOMAIN_OBJ و PYMEM_DOMAIN_MEM، یک attached thread state وجود دارد یا خیر.

تغییر یافته در نسخه‌ی 3.8: الگو‌های بایتی 0xCB (PYMEM_CLEANBYTE0xDB (PYMEM_DEADBYTE) و 0xFB (PYMEM_FORBIDDENBYTE) با 0xCD، 0xDD و 0xFD جایگزین شده‌اند تا از همان مقادیر malloc() و free() اشکال‌زدایی CRT ویندوز استفاده شود.

تخصیص‌دهنده‌ی pymalloc

پایتون یک تخصیص‌دهنده pymalloc دارد که برای اشیاء کوچک (کوچک‌تر یا برابر با ۵۱۲ بایت) با عمر کوتاه بهینه‌سازی شده است. این تخصیص‌دهنده از نگاشت‌های حافظه به نام «آرنا» با اندازه ثابت ۲۵۶ KiB در سکوهای ۳۲ بیتی یا ۱ MiB در سکوهای ۶۴ بیتی استفاده می‌کند. برای تخصیص‌های بزرگ‌تر از ۵۱۲ بایت، به PyMem_RawMalloc() و PyMem_RawRealloc() بازمی‌گردد.

pymalloc تخصیص‌دهنده پیش‌فرض دامنه‌های PYMEM_DOMAIN_MEM (مثلاً PyMem_Malloc()) و PYMEM_DOMAIN_OBJ (مثلاً PyObject_Malloc()) است.

تخصیص‌دهنده‌ی آرنا (arena allocator) از توابع زیر استفاده می‌کند:

  • VirtualAlloc() و VirtualFree() در ویندوز،

  • mmap() و munmap() در صورت دسترسی‌پذیری،

  • malloc() و free() در غیر این صورت.

اگر پایتون با گزینه‌ی --without-pymalloc پیکربندی شده باشد، این تخصیص‌دهنده غیرفعال می‌شود. همچنین می‌توان آن را در زمان اجرا با استفاده از متغیر محیطی PYTHONMALLOC غیرفعال کرد (مثلاً PYTHONMALLOC=malloc).

معمولاً منطقی است که هنگام ساخت پایتون با AddressSanitizer (--with-address-sanitizer)، که به کشف اشکال‌های سطح پایین درون کد C کمک می‌کند، تخصیص‌دهنده pymalloc را غیرفعال کنید.

سفارشی‌سازی تخصیص‌دهنده آرنا (Arena Allocator) در pymalloc

اضافه شده در نسخه‌ی 3.4.

type PyObjectArenaAllocator

ساختاری که برای توصیف یک تخصیص‌دهنده‌ی آرنا (arena allocator) استفاده می‌شود. این ساختار سه فیلد دارد:

فیلد

معنا

void *ctx

زمینه کاربر که به‌عنوان آرگومان اول ارسال می‌شود

void* alloc(void *ctx, size_t size)

تخصیص دادن یک آرنا به اندازه‌ی size بایت

void free(void *ctx, void *ptr, size_t size)

آزاد کردن یک آرنا

void PyObject_GetArenaAllocator(PyObjectArenaAllocator *allocator)

بهدست آوردن تخصیص‌دهنده‌ی آرنا .

void PyObject_SetArenaAllocator(PyObjectArenaAllocator *allocator)

تخصیص‌دهنده‌ی آرنا را تنظیم می‌کند.

تخصیص‌دهنده mimalloc

اضافه شده در نسخه‌ی 3.13.

پایتون از تخصیص‌دهنده mimalloc پشتیبانی می‌کند، زمانی که پشتیبانی پلتفرم زیرین در دسترس باشد. mimalloc یک تخصیص‌دهنده همه‌منظوره با ویژگی‌های عملکردی عالی است که در ابتدا توسط Daan Leijen برای سیستم‌های ران‌تایم زبان‌های Koka و Lean توسعه‌یافته است.

برخلاف pymalloc که برای اشیاء کوچک (۵۱۲ بایت یا کمتر) بهینه‌سازی‌شده است، mimalloc تخصیص‌هایی با هر اندازه را مدیریت می‌کند.

در ساخت‌ نخ‌آزاد، mimalloc تخصیص‌دهنده‌ی پیش‌فرض و الزامی برای دامنه‌های PYMEM_DOMAIN_MEM و PYMEM_DOMAIN_OBJ است. در ساخت‌های نخ‌آزاد نمی‌توان آن را غیرفعال کرد. ساخت‌ نخ‌آزاد از هیپ‌های mimalloc به ازای هر نخ استفاده می‌کند که این امر اجازه می‌دهد تخصیص و آزادسازی در بیشتر موارد بدون قفل‌کردن انجام شوند.

در ساخت‌ پیش‌فرض (غیر نخ‌آزاد)، mimalloc دسترس‌پذیر است، اما تخصیص‌دهنده‌ی پیش‌فرض نیست. می‌توان آن را در زمان اجرا با استفاده از PYTHONMALLOC=mimalloc انتخاب کرد (یا mimalloc_debug برای گنجاندن قلاب‌های اشکال‌زدایی). می‌توان آن را در زمان ساخت‌ با استفاده از گزینه‌ی پیکربندی --without-mimalloc غیرفعال کرد، اما این گزینه را نمی‌توان با --disable-gil ترکیب کرد.

API زبان C برای tracemalloc

اضافه شده در نسخه‌ی 3.7.

int PyTraceMalloc_Track(unsigned int domain, uintptr_t ptr, size_t size)

پیگیری یک بلوک حافظه‌ی تخصیص‌یافته در ماژول tracemalloc.

در صورت موفقیت 0 را برمی‌گرداند، در صورت خطا -1 را برمی‌گرداند (تخصیص حافظه برای ذخیره‌ی ردگیری ناموفق بود). اگر tracemalloc غیرفعال باشد، -2 را برمی‌گرداند.

اگر بلوک حافظه از قبل پیگیری شده باشد، ردگیری موجود به‌روزرسانی می‌شود.

int PyTraceMalloc_Untrack(unsigned int domain, uintptr_t ptr)

پیگیری یک بلوک حافظه‌ی تخصیص‌یافته را در ماژول tracemalloc لغو می‌کند. اگر بلوک پیگیری نشده باشد، هیچ کاری انجام نمی‌دهد.

اگر tracemalloc غیرفعال باشد، -2 را برمی‌گرداند، در غیر این صورت 0 را برمی‌گرداند.

مثال‌ها

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

PyObject *res;
char *buf = (char *) PyMem_Malloc(BUFSIZ); /* for I/O */

if (buf == NULL)
    return PyErr_NoMemory();
/* ...Do some I/O operation involving buf... */
res = PyBytes_FromString(buf);
PyMem_Free(buf); /* allocated with PyMem_Malloc */
return res;

همان کد با استفاده از مجموعه توابع نوع‌محور:

PyObject *res;
char *buf = PyMem_New(char, BUFSIZ); /* for I/O */

if (buf == NULL)
    return PyErr_NoMemory();
/* ...Do some I/O operation involving buf... */
res = PyBytes_FromString(buf);
PyMem_Free(buf); /* allocated with PyMem_New */
return res;

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

char *buf1 = PyMem_New(char, BUFSIZ);
char *buf2 = (char *) malloc(BUFSIZ);
char *buf3 = (char *) PyMem_Malloc(BUFSIZ);
...
PyMem_Del(buf3);  /* Wrong -- should be PyMem_Free() */
free(buf2);       /* Right -- allocated via malloc() */
free(buf1);       /* Fatal -- should be PyMem_Free()  */

علاوه بر توابعی که برای مدیریت بلوک‌های حافظه خام از هیپ پایتون در نظر گرفته شده‌اند، اشیاء در پایتون با PyObject_New، PyObject_NewVar و PyObject_Free() تخصیص می‌یابند و آزاد می‌شوند.

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