内存分配

注意

此页面使用两种不同的语法变体

  • Cython 特定的 cdef 语法,旨在使类型声明简洁明了,并易于从 C/C++ 的角度阅读。

  • 纯 Python 语法,允许在 纯 Python 代码 中进行静态 Cython 类型声明,遵循 PEP-484 类型提示和 PEP 526 变量注释。

    要在 Python 语法中使用 C 数据类型,您需要在要编译的 Python 模块中导入特殊的 cython 模块,例如

    import cython
    

    如果您使用纯 Python 语法,我们强烈建议您使用最新的 Cython 3 版本,因为与 0.29.x 版本相比,这里已经进行了重大改进。

在 Python 中,动态内存分配通常不是问题。一切都是对象,引用计数系统和垃圾收集器会在不再使用内存时自动将其返回给系统。

当涉及到更低级的内存缓冲区时,Cython 通过 NumPy、内存视图或 Python 的标准库数组类型为(多维)简单类型数组提供了特殊支持。它们功能齐全,经过垃圾收集,并且比 C 中的裸指针更容易使用,同时仍然保留了速度和静态类型的好处。请参阅 使用 Python 数组类型化内存视图

然而,在某些情况下,这些对象仍然会产生不可接受的开销,这可能会导致在 C 中进行手动内存管理的必要性。

简单的 C 值和结构体(例如局部变量 cdef double x / x: cython.double)通常 在栈上分配 并按值传递,但对于更大更复杂的对象(例如双精度数的动态大小列表),内存必须 手动请求和释放。C 提供了 malloc()realloc()free() 函数用于此目的,这些函数可以在 cython 中从 clibc.stdlib 导入。它们的签名是

void* malloc(size_t size)
void* realloc(void* ptr, size_t size)
void free(void* ptr)

一个非常简单的 malloc 使用示例如下

import random
from cython.cimports.libc.stdlib import malloc, free

def random_noise(number: cython.int = 1):
    i: cython.int
    # allocate number * sizeof(double) bytes of memory
    my_array: cython.p_double = cython.cast(cython.p_double, malloc(
        number * cython.sizeof(cython.double)))
    if not my_array:
        raise MemoryError()

    try:
        ran = random.normalvariate
        for i in range(number):
            my_array[i] = ran(0, 1)

        # ... let's just assume we do some more heavy C calculations here to make up
        # for the work that it takes to pack the C double values into Python float
        # objects below, right after throwing away the existing objects above.

        return [x for x in my_array[:number]]
    finally:
        # return the previously allocated memory to the system
        free(my_array)

请注意,用于在 Python 堆上分配内存的 C-API 函数通常比上面提到的低级 C 函数更受欢迎,因为它们提供的内存实际上是在 Python 的内部内存管理系统中进行的。它们还对较小的内存块进行了特殊优化,通过避免代价高昂的操作系统调用来加快分配速度。

C-API 函数可以在 cpython.mem 标准声明文件中找到

from cython.cimports.cpython.mem import PyMem_Malloc, PyMem_Realloc, PyMem_Free

它们的接口和用法与相应的低级 C 函数相同。

要记住的一件重要的事情是,使用 malloc()PyMem_Malloc() 获得的内存块必须使用相应的 free()PyMem_Free() 调用手动释放,当它们不再使用时(并且必须始终使用匹配类型的 free 函数)。否则,它们将不会在 python 进程退出之前被回收。这被称为内存泄漏。

如果一块内存需要比 try..finally 块所能管理的更长的生命周期,另一个有用的习惯用法是将它的生命周期绑定到一个 Python 对象,以利用 Python 运行时的内存管理,例如:

from cython.cimports.cpython.mem import PyMem_Malloc, PyMem_Realloc, PyMem_Free

@cython.cclass
class SomeMemory:
    data: cython.p_double

    def __cinit__(self, number: cython.size_t):
        # allocate some memory (uninitialised, may contain arbitrary data)
        self.data = cython.cast(cython.p_double, PyMem_Malloc(
            number * cython.sizeof(cython.double)))
        if not self.data:
            raise MemoryError()

    def resize(self, new_number: cython.size_t):
        # Allocates new_number * sizeof(double) bytes,
        # preserving the current content and making a best-effort to
        # reuse the original data location.
        mem = cython.cast(cython.p_double, PyMem_Realloc(
            self.data, new_number * cython.sizeof(cython.double)))
        if not mem:
            raise MemoryError()
        # Only overwrite the pointer if the memory was really reallocated.
        # On error (mem is NULL), the originally memory has not been freed.
        self.data = mem

    def __dealloc__(self):
        PyMem_Free(self.data)  # no-op if self.data is NULL