Quản lý bộ nhớ

Tổng quan

Quản lý bộ nhớ trong Python bao gồm một vùng riêng chứa tất cả các đối tượng và cấu trúc dữ liệu Python. Việc quản lý vùng nhớ riêng tư này được đảm bảo nội bộ bởi Python memory manager. Trình quản lý bộ nhớ Python có các thành phần khác nhau xử lý các khía cạnh quản lý lưu trữ động khác nhau, như chia sẻ, phân đoạn, phân bổ trước hoặc bộ nhớ đệm.

Ở mức thấp nhất, bộ cấp phát bộ nhớ thô đảm bảo có đủ chỗ trong vùng riêng để lưu trữ tất cả dữ liệu liên quan đến Python bằng cách tương tác với trình quản lý bộ nhớ của hệ điều hành. Ngoài bộ cấp phát bộ nhớ thô, một số bộ cấp phát dành riêng cho đối tượng hoạt động trên cùng một vùng nhớ và thực hiện các chính sách quản lý bộ nhớ riêng biệt phù hợp với đặc thù của từng loại đối tượng. Ví dụ: các đối tượng số nguyên được quản lý khác nhau trong vùng heap so với các chuỗi, bộ dữ liệu hoặc từ điển vì số nguyên ngụ ý các yêu cầu lưu trữ khác nhau và sự cân bằng giữa tốc độ/không gian. Do đó, trình quản lý bộ nhớ Python ủy quyền một số công việc cho các bộ cấp phát dành riêng cho đối tượng, nhưng đảm bảo rằng bộ cấp phát sau hoạt động trong giới hạn của vùng nhớ riêng.

Điều quan trọng là phải hiểu rằng việc quản lý vùng heap Python được thực hiện bởi chính trình thông dịch và người dùng không có quyền kiểm soát nó, ngay cả khi họ thường xuyên thao tác các con trỏ đối tượng tới các khối bộ nhớ bên trong vùng nhớ heap đó. Việc phân bổ vùng nhớ heap cho các đối tượng Python và các bộ đệm bên trong khác được trình quản lý bộ nhớ Python thực hiện theo yêu cầu thông qua các hàm Python/C API được liệt kê trong tài liệu này.

Để tránh hỏng bộ nhớ, người viết tiện ích mở rộng không bao giờ nên thử thao tác trên các đối tượng Python có các hàm được xuất bởi thư viện C: malloc(), calloc(), realloc()free(). Điều này sẽ dẫn đến các cuộc gọi hỗn hợp giữa bộ cấp phát C và trình quản lý bộ nhớ Python với những hậu quả nghiêm trọng vì chúng triển khai các thuật toán khác nhau và hoạt động trên các vùng nhớ khác nhau. Tuy nhiên, người ta có thể phân bổ và giải phóng các khối bộ nhớ một cách an toàn bằng bộ cấp phát thư viện C cho các mục đích riêng lẻ, như trong ví dụ sau:

PyObject *res;
char *buf = (char *) malloc(BUFSIZ); /* cho I/O */

nếu (buf == NULL)
    trả về PyErr_NoMemory();
...Thực hiện một số thao tác I/O liên quan đến buf...
res = PyBytes_FromString(buf);
miễn phí(buf); /* bị hỏng */
trả lại độ phân giải;

Trong ví dụ này, yêu cầu bộ nhớ cho bộ đệm I/O được bộ cấp phát thư viện C xử lý. Kết quả là trình quản lý bộ nhớ Python chỉ liên quan đến việc phân bổ đối tượng byte được trả về.

Tuy nhiên, trong hầu hết các trường hợp, bạn nên phân bổ bộ nhớ từ vùng heap Python một cách cụ thể vì vùng sau nằm dưới sự kiểm soát của trình quản lý bộ nhớ Python. Ví dụ: điều này là bắt buộc khi trình thông dịch được mở rộng với các kiểu đối tượng mới được viết bằng C. Một lý do khác để sử dụng vùng heap Python là mong muốn thông báo cho trình quản lý bộ nhớ Python về nhu cầu bộ nhớ của mô-đun mở rộng. Ngay cả khi bộ nhớ được yêu cầu chỉ được sử dụng cho các mục đích nội bộ, có tính đặc thù cao, việc ủy ​​quyền tất cả các yêu cầu bộ nhớ cho trình quản lý bộ nhớ Python sẽ khiến trình thông dịch có được hình ảnh chính xác hơn về toàn bộ dấu chân bộ nhớ của nó. Do đó, trong một số trường hợp nhất định, trình quản lý bộ nhớ Python có thể kích hoạt hoặc không kích hoạt các hành động thích hợp, như thu gom rác, nén bộ nhớ hoặc các quy trình phòng ngừa khác. Lưu ý rằng bằng cách sử dụng bộ cấp phát thư viện C như trong ví dụ trước, bộ nhớ được cấp phát cho bộ đệm I/O sẽ thoát hoàn toàn khỏi trình quản lý bộ nhớ Python.

Xem thêm

Biến môi trường PYTHONMALLOC có thể được sử dụng để định cấu hình bộ cấp phát bộ nhớ được Python sử dụng.

Biến môi trường PYTHONMALLOCSTATS có thể được sử dụng để in số liệu thống kê của pymalloc memory allocator mỗi khi một trường đối tượng pymalloc mới được tạo và khi tắt máy.

Miền cấp phát

Tất cả các chức năng phân bổ thuộc về một trong ba "miền" khác nhau (xem thêm PyMemAllocatorDomain). Các miền này thể hiện các chiến lược phân bổ khác nhau và được tối ưu hóa cho các mục đích khác nhau. Chi tiết cụ thể về cách mỗi miền phân bổ bộ nhớ hoặc chức năng nội bộ mà mỗi miền gọi được coi là chi tiết triển khai, nhưng vì mục đích gỡ lỗi, bạn có thể tìm thấy bảng đơn giản hóa tại Bộ cấp phát bộ nhớ mặc định. Các API được sử dụng để phân bổ và giải phóng một khối bộ nhớ phải thuộc cùng một miền. Ví dụ: PyMem_Free() phải được sử dụng để giải phóng bộ nhớ được phân bổ bằng PyMem_Malloc().

Ba miền phân bổ là:

  • Miền thô: dành cho việc phân bổ bộ nhớ cho các bộ đệm bộ nhớ có mục đích chung trong đó việc phân bổ must được chuyển đến bộ cấp phát hệ thống hoặc nơi bộ cấp phát có thể hoạt động mà không cần attached thread state. Bộ nhớ được yêu cầu trực tiếp từ hệ thống. Xem Raw Memory Interface.

  • Miền "Mem": dành cho việc phân bổ bộ nhớ cho bộ đệm Python và bộ đệm bộ nhớ đa năng trong đó việc phân bổ phải được thực hiện bằng attached thread state. Bộ nhớ được lấy từ vùng riêng của Python. Xem Memory Interface.

  • Miền đối tượng: dùng để phân bổ bộ nhớ cho các đối tượng Python. Bộ nhớ được lấy từ vùng riêng của Python. Xem Object allocators.

Ghi chú

Bản dựng free-threaded yêu cầu chỉ các đối tượng Python được phân bổ bằng miền "đối tượng" và tất cả các đối tượng Python được phân bổ bằng miền đó. Điều này khác với các phiên bản Python trước đó, đây chỉ là cách thực hành tốt nhất chứ không phải là một yêu cầu khó.

Ví dụ: bộ đệm (đối tượng không phải Python) phải được phân bổ bằng PyMem_Malloc(), PyMem_RawMalloc() hoặc malloc() chứ không phải PyObject_Malloc().

Xem Memory Allocation APIs.

Giao diện bộ nhớ thô

Các bộ chức năng sau đây là các hàm bao cho bộ cấp phát hệ thống. Các chức năng này an toàn theo luồng, vì vậy thread state không cần phải là attached.

default raw memory allocator sử dụng các chức năng sau: malloc(), calloc(), realloc()free(); gọi malloc(1) (hoặc calloc(1, 1)) khi yêu cầu byte 0.

Added in version 3.4.

void *PyMem_RawMalloc(size_t n)
Một phần của ABI ổn định kể từ phiên bản 3.13.

Phân bổ byte n và trả về một con trỏ loại void* cho bộ nhớ được phân bổ hoặc NULL nếu yêu cầu không thành công.

Yêu cầu byte 0 sẽ trả về một con trỏ không phải NULL riêng biệt nếu có thể, như thể PyMem_RawMalloc(1) đã được gọi thay thế. Bộ nhớ sẽ không được khởi tạo theo bất kỳ cách nào.

void *PyMem_RawCalloc(size_t nelem, size_t elsize)
Một phần của ABI ổn định kể từ phiên bản 3.13.

Phân bổ các phần tử nelem, mỗi phần tử có kích thước tính bằng byte là elsize và trả về một con trỏ loại void* cho bộ nhớ được phân bổ hoặc NULL nếu yêu cầu không thành công. Bộ nhớ được khởi tạo về số không.

Yêu cầu các phần tử bằng 0 hoặc các phần tử có kích thước 0 byte sẽ trả về một con trỏ không phải NULL riêng biệt nếu có thể, như thể PyMem_RawCalloc(1, 1) đã được gọi thay thế.

Added in version 3.5.

void *PyMem_RawRealloc(void *p, size_t n)
Một phần của ABI ổn định kể từ phiên bản 3.13.

Thay đổi kích thước khối bộ nhớ được trỏ bởi p thành n byte. Nội dung sẽ không thay đổi ở mức tối thiểu của kích thước cũ và mới.

Nếu pNULL, lệnh gọi tương đương với PyMem_RawMalloc(n); ngược lại, nếu n bằng 0, khối bộ nhớ sẽ được thay đổi kích thước nhưng không được giải phóng và con trỏ trả về không phải là NULL.

Trừ khi pNULL, nó phải được trả về bằng lệnh gọi trước đó tới PyMem_RawMalloc(), PyMem_RawRealloc() hoặc PyMem_RawCalloc().

Nếu yêu cầu không thành công, PyMem_RawRealloc() trả về NULLp vẫn là một con trỏ hợp lệ tới vùng bộ nhớ trước đó.

void PyMem_RawFree(void *p)
Một phần của ABI ổn định kể từ phiên bản 3.13.

Giải phóng khối bộ nhớ được trỏ tới bởi p, khối này phải được trả về bằng lệnh gọi trước đó tới PyMem_RawMalloc(), PyMem_RawRealloc() hoặc PyMem_RawCalloc(). Ngược lại, hoặc nếu PyMem_RawFree(p) đã được gọi trước đó, hành vi không xác định sẽ xảy ra.

Nếu pNULL thì không có thao tác nào được thực hiện.

Giao diện bộ nhớ

Các bộ hàm sau, được mô hình hóa theo tiêu chuẩn ANSI C, nhưng chỉ định hành vi khi yêu cầu byte 0, có sẵn để phân bổ và giải phóng bộ nhớ khỏi vùng heap Python.

Trong bản dựng hỗ trợ GIL (bản dựng mặc định), default memory allocator sử dụng pymalloc memory allocator, trong khi ở free-threaded build, mặc định là mimalloc memory allocator.

Cảnh báo

Phải có attached thread state khi sử dụng các chức năng này.

Thay đổi trong phiên bản 3.6: Bộ cấp phát mặc định bây giờ là pymalloc thay vì hệ thống malloc().

Thay đổi trong phiên bản 3.13: Trong bản dựng free-threaded, bộ cấp phát mặc định hiện là mimalloc.

void *PyMem_Malloc(size_t n)
Một phần của ABI ổn định.

Phân bổ byte n và trả về một con trỏ loại void* cho bộ nhớ được phân bổ hoặc NULL nếu yêu cầu không thành công.

Yêu cầu byte 0 sẽ trả về một con trỏ không phải NULL riêng biệt nếu có thể, như thể PyMem_Malloc(1) đã được gọi thay thế. Bộ nhớ sẽ không được khởi tạo theo bất kỳ cách nào.

void *PyMem_Calloc(size_t nelem, size_t elsize)
Một phần của ABI ổn định kể từ phiên bản 3.7.

Phân bổ các phần tử nelem, mỗi phần tử có kích thước tính bằng byte là elsize và trả về một con trỏ loại void* cho bộ nhớ được phân bổ hoặc NULL nếu yêu cầu không thành công. Bộ nhớ được khởi tạo về số không.

Yêu cầu các phần tử bằng 0 hoặc các phần tử có kích thước bằng 0 byte sẽ trả về một con trỏ không phải NULL riêng biệt nếu có thể, như thể PyMem_Calloc(1, 1) đã được gọi thay thế.

Added in version 3.5.

void *PyMem_Realloc(void *p, size_t n)
Một phần của ABI ổn định.

Thay đổi kích thước khối bộ nhớ được trỏ bởi p thành n byte. Nội dung sẽ không thay đổi ở mức tối thiểu của kích thước cũ và mới.

Nếu pNULL, lệnh gọi tương đương với PyMem_Malloc(n); ngược lại, nếu n bằng 0, khối bộ nhớ sẽ được thay đổi kích thước nhưng không được giải phóng và con trỏ trả về không phải là NULL.

Trừ khi pNULL, nó phải được trả về bằng lệnh gọi trước đó tới PyMem_Malloc(), PyMem_Realloc() hoặc PyMem_Calloc().

Nếu yêu cầu không thành công, PyMem_Realloc() trả về NULLp vẫn là một con trỏ hợp lệ tới vùng bộ nhớ trước đó.

void PyMem_Free(void *p)
Một phần của ABI ổn định.

Giải phóng khối bộ nhớ được trỏ tới bởi p, khối này phải được trả về bằng lệnh gọi trước đó tới PyMem_Malloc(), PyMem_Realloc() hoặc PyMem_Calloc(). Ngược lại, hoặc nếu PyMem_Free(p) đã được gọi trước đó, hành vi không xác định sẽ xảy ra.

Nếu pNULL thì không có thao tác nào được thực hiện.

Các macro định hướng kiểu sau đây được cung cấp để thuận tiện. Lưu ý rằng TYPE đề cập đến bất kỳ loại C nào.

PyMem_New(TYPE, n)

Tương tự như PyMem_Malloc(), nhưng phân bổ byte bộ nhớ (n * sizeof(TYPE)). Trả về một con trỏ được truyền tới TYPE*. Bộ nhớ sẽ không được khởi tạo theo bất kỳ cách nào.

PyMem_Resize(p, TYPE, n)

Tương tự như PyMem_Realloc(), nhưng khối bộ nhớ được thay đổi kích thước thành byte (n * sizeof(TYPE)). Trả về một con trỏ được truyền tới TYPE*. Khi quay lại, p sẽ là con trỏ tới vùng bộ nhớ mới hoặc NULL trong trường hợp bị lỗi.

Đây là macro tiền xử lý C; p luôn được chỉ định lại. Lưu lại giá trị ban đầu của p để tránh bị mất bộ nhớ khi xử lý lỗi.

void PyMem_Del(void *p)

Tương tự với PyMem_Free()

Bí danh không được dùng nữa

Đây là các bí danh soft deprecated cho các hàm và macro hiện có. Chúng tồn tại chỉ để tương thích ngược.

Bí danh không được dùng nữa

Chức năng hoặc macro tương ứng

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()

Thay đổi trong phiên bản 3.4: Các macro bây giờ là bí danh của các hàm và macro tương ứng. Trước đây, hành vi của chúng giống nhau, nhưng việc sử dụng chúng không nhất thiết đảm bảo khả năng tương thích nhị phân giữa các phiên bản Python.

Sắp loại bỏ từ phiên bản 2.0.

Bộ cấp phát đối tượng

Các bộ hàm sau, được mô hình hóa theo tiêu chuẩn ANSI C, nhưng chỉ định hành vi khi yêu cầu byte 0, có sẵn để phân bổ và giải phóng bộ nhớ khỏi vùng heap Python.

Ghi chú

Không có gì đảm bảo rằng bộ nhớ được trả về bởi các bộ cấp phát này có thể được chuyển thành công sang đối tượng Python khi chặn các hàm cấp phát trong miền này bằng các phương thức được mô tả trong phần Customize Memory Allocators.

Zz000zz sử dụng pymalloc memory allocator. Trong bản dựng free-threaded, mặc định là mimalloc memory allocator.

Cảnh báo

Phải có attached thread state khi sử dụng các chức năng này.

void *PyObject_Malloc(size_t n)
Một phần của ABI ổn định.

Phân bổ byte n và trả về một con trỏ loại void* cho bộ nhớ được phân bổ hoặc NULL nếu yêu cầu không thành công.

Yêu cầu byte 0 sẽ trả về một con trỏ không phải NULL riêng biệt nếu có thể, như thể PyObject_Malloc(1) đã được gọi thay thế. Bộ nhớ sẽ không được khởi tạo theo bất kỳ cách nào.

void *PyObject_Calloc(size_t nelem, size_t elsize)
Một phần của ABI ổn định kể từ phiên bản 3.7.

Phân bổ các phần tử nelem, mỗi phần tử có kích thước tính bằng byte là elsize và trả về một con trỏ loại void* cho bộ nhớ được phân bổ hoặc NULL nếu yêu cầu không thành công. Bộ nhớ được khởi tạo về số không.

Yêu cầu các phần tử bằng 0 hoặc các phần tử có kích thước 0 byte sẽ trả về một con trỏ không phải NULL riêng biệt nếu có thể, như thể PyObject_Calloc(1, 1) đã được gọi thay thế.

Added in version 3.5.

void *PyObject_Realloc(void *p, size_t n)
Một phần của ABI ổn định.

Thay đổi kích thước khối bộ nhớ được trỏ bởi p thành n byte. Nội dung sẽ không thay đổi ở mức tối thiểu của kích thước cũ và mới.

Nếu pNULL, lệnh gọi tương đương với PyObject_Malloc(n); ngược lại, nếu n bằng 0, khối bộ nhớ sẽ được thay đổi kích thước nhưng không được giải phóng và con trỏ trả về không phải là NULL.

Trừ khi pNULL, nó phải được trả về bằng lệnh gọi trước đó tới PyObject_Malloc(), PyObject_Realloc() hoặc PyObject_Calloc().

Nếu yêu cầu không thành công, PyObject_Realloc() trả về NULLp vẫn là một con trỏ hợp lệ tới vùng bộ nhớ trước đó.

void PyObject_Free(void *p)
Một phần của ABI ổn định.

Giải phóng khối bộ nhớ được trỏ tới bởi p, khối này phải được trả về bằng lệnh gọi trước đó tới PyObject_Malloc(), PyObject_Realloc() hoặc PyObject_Calloc(). Ngược lại, hoặc nếu PyObject_Free(p) đã được gọi trước đó, hành vi không xác định sẽ xảy ra.

Nếu pNULL thì không có thao tác nào được thực hiện.

Đừng gọi trực tiếp điều này để giải phóng bộ nhớ của đối tượng; thay vào đó hãy gọi loại tp_free slot.

Không sử dụng điều này cho bộ nhớ được phân bổ bởi PyObject_GC_New hoặc PyObject_GC_NewVar; thay vào đó hãy sử dụng PyObject_GC_Del().

Xem thêm

Bộ cấp phát bộ nhớ mặc định

Cấp phát bộ nhớ mặc định:

Cấu hình

Tên

PyMem_RawMalloc

PyMem_Malloc

PyObject_Malloc

Bản phát hành

"pymalloc"

malloc

pymalloc

pymalloc

Bản dựng gỡ lỗi

"pymalloc_debug"

malloc + gỡ lỗi

pymalloc + gỡ lỗi

pymalloc + gỡ lỗi

Phát hành bản dựng, không có pymalloc

"malloc"

malloc

malloc

malloc

Bản dựng gỡ lỗi, không có pymalloc

"malloc_debug"

malloc + gỡ lỗi

malloc + gỡ lỗi

malloc + gỡ lỗi

Xây dựng theo luồng miễn phí

"mimalloc"

mimalloc

mimalloc

mimalloc

Xây dựng gỡ lỗi theo luồng miễn phí

"mimalloc_debug"

mimalloc + gỡ lỗi

mimalloc + gỡ lỗi

mimalloc + gỡ lỗi

Huyền thoại:

Tùy chỉnh bộ cấp phát bộ nhớ

Added in version 3.4.

type PyMemAllocatorEx

Cấu trúc được sử dụng để mô tả bộ cấp phát khối bộ nhớ. Cấu trúc có các trường sau:

Cánh đồng

Nghĩa

void *ctx

bối cảnh người dùng được chuyển làm đối số đầu tiên

void* malloc(void *ctx, size_t size)

cấp phát khối bộ nhớ

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

phân bổ một khối bộ nhớ được khởi tạo bằng số 0

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

phân bổ hoặc thay đổi kích thước khối bộ nhớ

void free(void *ctx, void *ptr)

giải phóng một khối bộ nhớ

Thay đổi trong phiên bản 3.5: Cấu trúc PyMemAllocator được đổi tên thành PyMemAllocatorEx và trường calloc mới đã được thêm vào.

type PyMemAllocatorDomain

Enum được sử dụng để xác định miền cấp phát. Tên miền:

PYMEM_DOMAIN_RAW

Chức năng:

PYMEM_DOMAIN_MEM

Chức năng:

PYMEM_DOMAIN_OBJ

Chức năng:

void PyMem_GetAllocator(PyMemAllocatorDomain domain, PyMemAllocatorEx *allocator)

Lấy bộ cấp phát khối bộ nhớ của miền được chỉ định.

void PyMem_SetAllocator(PyMemAllocatorDomain domain, PyMemAllocatorEx *allocator)

Đặt bộ cấp phát khối bộ nhớ của miền được chỉ định.

Bộ cấp phát mới phải trả về một con trỏ không phải NULL riêng biệt khi yêu cầu byte 0.

Đối với miền PYMEM_DOMAIN_RAW, bộ cấp phát phải an toàn theo luồng: thread state không phải là attached khi bộ cấp phát được gọi.

Đối với các miền còn lại, bộ cấp phát cũng phải an toàn theo luồng: bộ cấp phát có thể được gọi trong các trình thông dịch khác nhau không dùng chung GIL.

Nếu bộ cấp phát mới không phải là một hook (không gọi bộ cấp phát trước đó), hàm PyMem_SetupDebugHooks() phải được gọi để cài đặt lại các móc gỡ lỗi ở trên cùng của bộ cấp phát mới.

Xem thêm PyPreConfig.allocatorPreinitialize Python with PyPreConfig.

Cảnh báo

PyMem_SetAllocator() có hợp đồng sau:

  • Nó có thể được gọi sau Py_PreInitialize() và trước Py_InitializeFromConfig() để cài đặt bộ cấp phát bộ nhớ tùy chỉnh. Không có hạn chế nào đối với bộ cấp phát đã cài đặt ngoài những hạn chế do miền áp đặt (ví dụ: Miền thô cho phép gọi bộ cấp phát mà không cần attached thread state). Xem the section on allocator domains để biết thêm thông tin.

  • Nếu được gọi sau khi Python khởi tạo xong (sau khi Py_InitializeFromConfig() được gọi), bộ cấp phát must sẽ bao bọc bộ cấp phát hiện có. Thay thế bộ cấp phát hiện tại bằng một số tùy ý khác là not supported.

Thay đổi trong phiên bản 3.12: Tất cả các bộ cấp phát phải an toàn theo luồng.

void PyMem_SetupDebugHooks(void)

Thiết lập debug hooks in the Python memory allocators để phát hiện lỗi bộ nhớ.

Móc gỡ lỗi trên bộ cấp phát bộ nhớ Python

Khi Python is built in debug mode, hàm PyMem_SetupDebugHooks() được gọi tại Python preinitialization để thiết lập móc gỡ lỗi trên bộ cấp phát bộ nhớ Python nhằm phát hiện lỗi bộ nhớ.

Biến môi trường PYTHONMALLOC có thể được sử dụng để cài đặt móc gỡ lỗi trên Python được biên dịch ở chế độ phát hành (ví dụ: PYTHONMALLOC=debug).

Hàm PyMem_SetupDebugHooks() có thể được sử dụng để đặt móc gỡ lỗi sau khi gọi PyMem_SetAllocator().

Các móc gỡ lỗi này lấp đầy các khối bộ nhớ được phân bổ động bằng các mẫu bit đặc biệt, dễ nhận biết. Bộ nhớ được cấp phát mới chứa byte 0xCD (PYMEM_CLEANBYTE), bộ nhớ giải phóng chứa byte 0xDD (PYMEM_DEADBYTE). Các khối bộ nhớ được bao quanh bởi các "byte bị cấm" chứa đầy byte 0xFD (PYMEM_FORBIDDENBYTE). Các chuỗi byte này không chắc là địa chỉ hợp lệ, số float hoặc chuỗi ASCII.

Kiểm tra thời gian chạy:

Nếu có lỗi, móc gỡ lỗi sử dụng mô-đun tracemalloc để lấy lại dấu vết nơi khối bộ nhớ được phân bổ. Truy nguyên chỉ được hiển thị nếu tracemalloc đang theo dõi việc phân bổ bộ nhớ Python và khối bộ nhớ đã được theo dõi.

Đặt S = sizeof(size_t). Các byte 2*S được thêm vào mỗi đầu của mỗi khối byte N được yêu cầu. Bố cục bộ nhớ là như vậy, trong đó p đại diện cho địa chỉ được trả về bởi hàm giống malloc hoặc giống realloc (p[i:j] có nghĩa là lát byte từ *(p+i) bao gồm tối đa *(p+j) độc quyền; lưu ý rằng việc xử lý các chỉ số âm khác với một lát Python):

p[-2*S:-S]

Số byte được yêu cầu ban đầu. Đây là size_t, big endian (dễ đọc hơn trong kết xuất bộ nhớ).

p[-S]

mã định danh API (ký tự ASCII):

p[-S+1:0]

Bản sao của PYMEM_FORBIDDENBYTE. Được sử dụng để bắt lỗi ghi và đọc.

p[0:N]

Bộ nhớ được yêu cầu chứa đầy các bản sao của PYMEM_CLEANBYTE, được sử dụng để bắt tham chiếu đến bộ nhớ chưa được khởi tạo. Khi một hàm giống realloc được gọi yêu cầu khối bộ nhớ lớn hơn, các byte dư thừa mới cũng được lấp đầy bằng PYMEM_CLEANBYTE. Khi một hàm giống tự do được gọi, chúng sẽ được ghi đè bằng PYMEM_DEADBYTE để bắt tham chiếu đến bộ nhớ đã giải phóng. Khi một hàm giống realloc được gọi yêu cầu khối bộ nhớ nhỏ hơn, các byte cũ dư thừa cũng được lấp đầy bằng PYMEM_DEADBYTE.

p[N:N+S]

Bản sao của PYMEM_FORBIDDENBYTE. Được sử dụng để bắt ghi đè và đọc.

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

Chỉ được sử dụng nếu macro PYMEM_DEBUG_SERIALNO được xác định (không được xác định theo mặc định).

Một số sê-ri, tăng thêm 1 trên mỗi lệnh gọi tới hàm giống malloc hoặc giống realloc. Zz000zz lớn. Nếu "bộ nhớ kém" được phát hiện sau đó, số sê-ri sẽ là một cách tuyệt vời để đặt điểm dừng trong lần chạy tiếp theo, để ghi lại thời điểm khối này được chuyển đi. Hàm tĩnhbumserialno() trong obmalloc.c là nơi duy nhất số sê-ri được tăng lên và tồn tại để bạn có thể dễ dàng đặt điểm dừng như vậy.

Trước tiên, một hàm giống realloc hoặc giống tự do sẽ kiểm tra xem các byte PYMEM_FORBIDDENBYTE ở mỗi đầu có còn nguyên vẹn hay không. Nếu chúng đã bị thay đổi, đầu ra chẩn đoán sẽ được ghi vào stderr và chương trình sẽ bị hủy qua Py_FatalError(). Kiểu lỗi chính khác là gây ra lỗi bộ nhớ khi một chương trình đọc một trong các mẫu bit đặc biệt và cố gắng sử dụng nó làm địa chỉ. Nếu bạn vào trình gỡ lỗi và nhìn vào đối tượng, bạn có thể thấy rằng nó hoàn toàn chứa đầy PYMEM_DEADBYTE (có nghĩa là bộ nhớ giải phóng đang được sử dụng) hoặc PYMEM_CLEANBYTE (có nghĩa là bộ nhớ chưa được khởi tạo đang được sử dụng).

Thay đổi trong phiên bản 3.6: Hàm PyMem_SetupDebugHooks() hiện cũng hoạt động trên Python được biên dịch ở chế độ phát hành. Do lỗi, các móc gỡ lỗi hiện sử dụng tracemalloc để lấy lại dấu vết nơi khối bộ nhớ được phân bổ. Các móc gỡ lỗi hiện cũng kiểm tra xem có attached thread state hay không khi các hàm của miền PYMEM_DOMAIN_OBJPYMEM_DOMAIN_MEM được gọi.

Thay đổi trong phiên bản 3.8: Các mẫu byte 0xCB (PYMEM_CLEANBYTE), 0xDB (PYMEM_DEADBYTE) và 0xFB (PYMEM_FORBIDDENBYTE) đã được thay thế bằng 0xCD, 0xDD0xFD để sử dụng cùng các giá trị so với Windows CRT debug malloc()free().

Bộ cấp phát pymalloc

Python có bộ cấp phát pymalloc được tối ưu hóa cho các đối tượng nhỏ (nhỏ hơn hoặc bằng 512 byte) với thời gian tồn tại ngắn. Nó sử dụng ánh xạ bộ nhớ được gọi là "đấu trường" với kích thước cố định là 256 KiB trên nền tảng 32 bit hoặc 1 MiB trên nền tảng 64 bit. Nó quay trở lại PyMem_RawMalloc()PyMem_RawRealloc() đối với các phân bổ lớn hơn 512 byte.

pymallocdefault allocator của các miền PYMEM_DOMAIN_MEM (ví dụ: PyMem_Malloc()) và PYMEM_DOMAIN_OBJ (ví dụ: PyObject_Malloc()).

Bộ cấp phát đấu trường sử dụng các chức năng sau:

  • VirtualAlloc()VirtualFree() trên Windows,

  • mmap()munmap() nếu có,

  • malloc()free() nếu không.

Bộ cấp phát này bị vô hiệu hóa nếu Python được định cấu hình với tùy chọn --without-pymalloc. Nó cũng có thể bị vô hiệu hóa trong thời gian chạy bằng cách sử dụng biến môi trường PYTHONMALLOC (ví dụ: PYTHONMALLOC=malloc).

Thông thường, nên tắt bộ cấp phát pymalloc khi xây dựng Python bằng addressSanitizer (--with-address-sanitizer) để giúp phát hiện các lỗi cấp thấp trong mã C.

Tùy chỉnh Bộ phân bổ Arena pymalloc

Added in version 3.4.

type PyObjectArenaAllocator

Cấu trúc được sử dụng để mô tả một bộ cấp phát đấu trường. Cấu trúc có ba trường:

Cánh đồng

Nghĩa

void *ctx

bối cảnh người dùng được chuyển làm đối số đầu tiên

void* alloc(void *ctx, size_t size)

phân bổ một trường có kích thước byte

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

giải phóng một đấu trường

void PyObject_GetArenaAllocator(PyObjectArenaAllocator *allocator)

Nhận công cụ phân bổ đấu trường.

void PyObject_SetArenaAllocator(PyObjectArenaAllocator *allocator)

Đặt bộ phân bổ đấu trường.

Bộ cấp phát mimalloc

Added in version 3.13.

Python hỗ trợ bộ cấp phát mimalloc khi có hỗ trợ nền tảng cơ bản. mimalloc là một công cụ cấp phát có mục đích chung với các đặc tính hiệu suất tuyệt vời, ban đầu được Daan Leijen phát triển cho hệ thống thời gian chạy của ngôn ngữ Koka và Lean.

Không giống như pymalloc, được tối ưu hóa cho các đối tượng nhỏ (512 byte trở xuống), mimalloc xử lý việc phân bổ ở mọi kích thước.

Trong bản dựng free-threaded, mimalloc là bộ cấp phát mặc định và required cho các miền PYMEM_DOMAIN_MEMPYMEM_DOMAIN_OBJ. Nó không thể bị vô hiệu hóa trong các bản dựng có luồng tự do. Bản dựng luồng tự do sử dụng các vùng nhớ mimalloc trên mỗi luồng, cho phép tiến hành phân bổ và giải phóng mà không cần khóa trong hầu hết các trường hợp.

Trong bản dựng mặc định (không có luồng tự do), mimalloc có sẵn nhưng không có bộ cấp phát mặc định. Nó có thể được chọn trong thời gian chạy bằng cách sử dụng PYTHONMALLOC=mimalloc (hoặc mimalloc_debug để bao gồm debug hooks). Nó có thể bị vô hiệu hóa tại thời điểm xây dựng bằng tùy chọn cấu hình --without-mimalloc, nhưng không thể kết hợp tùy chọn này với --disable-gil.

tracemalloc C API

Added in version 3.7.

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

Theo dõi khối bộ nhớ được phân bổ trong mô-đun tracemalloc.

Trả về 0 nếu thành công, trả về -1 nếu có lỗi (không phân bổ được bộ nhớ để lưu dấu vết). Trả về -2 nếu tracemalloc bị tắt.

Nếu khối bộ nhớ đã được theo dõi, hãy cập nhật dấu vết hiện có.

int PyTraceMalloc_Untrack(unsigned int domain, uintptr_t ptr)

Bỏ theo dõi khối bộ nhớ được phân bổ trong mô-đun tracemalloc. Không làm gì nếu khối không được theo dõi.

Trả về -2 nếu tracemalloc bị tắt, nếu không thì trả về 0.

Ví dụ

Đây là ví dụ từ phần Tổng quan, được viết lại để bộ đệm I/O được phân bổ từ vùng heap Python bằng cách sử dụng bộ hàm đầu tiên:

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

nếu (buf == NULL)
    trả về PyErr_NoMemory();
/* ...Thực hiện một số thao tác I/O liên quan đến buf... */
res = PyBytes_FromString(buf);
PyMem_Free(buf); /* được phân bổ bằng PyMem_Malloc */
trả lại độ phân giải;

Mã tương tự sử dụng bộ hàm định hướng loại

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

nếu (buf == NULL)
    trả về PyErr_NoMemory();
/* ...Thực hiện một số thao tác I/O liên quan đến buf... */
res = PyBytes_FromString(buf);
PyMem_Free(buf); /* được phân bổ bằng PyMem_New */
trả lại độ phân giải;

Lưu ý rằng trong hai ví dụ trên, bộ đệm luôn được thao tác thông qua các hàm thuộc cùng một tập hợp. Thật vậy, cần phải sử dụng cùng một họ bộ nhớ API cho một khối bộ nhớ nhất định để giảm thiểu nguy cơ trộn lẫn các bộ cấp phát khác nhau. Chuỗi mã sau đây chứa hai lỗi, một trong số đó được gắn nhãn là fatal vì nó trộn lẫn hai bộ cấp phát khác nhau hoạt động trên các vùng nhớ khác nhau.

char *buf1 = PyMem_New(char, BUFSIZ);
char *buf2 = (char *) malloc(BUFSIZ);
char *buf3 = (char *) PyMem_Malloc(BUFSIZ);
...
PyMem_Del(buf3);  /* Sai -- phải là PyMem_Free() */
miễn phí(buf2);       /* Phải -- được phân bổ qua malloc() */
miễn phí(buf1);       /* Gây tử vong -- phải là PyMem_Free() */

Ngoài các chức năng nhằm xử lý các khối bộ nhớ thô từ vùng heap Python, các đối tượng trong Python còn được phân bổ và giải phóng với PyObject_New, PyObject_NewVarPyObject_Free().

Những điều này sẽ được giải thích trong chương tiếp theo về việc xác định và triển khai các loại đối tượng mới trong C.