¿Hay alguna razón para usar malloc sobre PyMem_Malloc?

Estoy leyendo la documentación de Gestión de memoria en las extensiones de Python C , y por lo que puedo decir, no parece haber muchas razones para usar malloc lugar de PyMem_Malloc . Digamos que quiero asignar una matriz que no se exponga al código fuente de Python y se almacene en un objeto que se recolectará como basura. ¿Hay alguna razón para usar malloc ?

EDITAR : PyMem_Malloc mixtas de PyMem_Malloc y PyObject_Malloc ; Son dos llamadas diferentes.

Sin la macro PYMALLOC_DEBUG activada, PyMem_Malloc es un alias de malloc() de libc, que tiene un caso especial: llamar a PyMem_Malloc para asignar cero bytes devolverá un puntero que no sea NULL, mientras que malloc (cero byte) puede devolver un valor NULO o generar un error de sistema ( referencia al código fuente ):

/ * malloc. Tenga en cuenta que nbytes == 0 intenta devolver un puntero no NULL, distinto * de todos los otros punteros actualmente activos. Esto puede no ser posible. * /

Además, hay una nota de advertencia en el archivo de encabezado pymem.h :

Nunca mezcle llamadas a PyMem_ con llamadas a la plataforma malloc / realloc / calloc / free. Por ejemplo, en Windows, diferentes DLL pueden terminar usando diferentes montones, y si usa PyMem_Malloc obtendrá la memoria del montón usado por la DLL de Python; podría ser un desastre si lo liberas () directamente en tu propia extensión. El uso de PyMem_Free en su lugar garantiza que Python pueda devolver la memoria al montón apropiado. Como otro ejemplo, en el modo PYMALLOC_DEBUG, Python ajusta todas las llamadas a todas las funciones de memoria PyMem_ y PyObject_ en envoltorios especiales de depuración que agregan información adicional de depuración a los bloques de memoria dinámica. Las rutinas del sistema no tienen idea de qué hacer con esas cosas, y las envolturas de Python no tienen idea de qué hacer con los bloques en bruto obtenidos directamente por las rutinas del sistema.

Luego, hay algunos ajustes específicos de Python dentro de PyMem_Malloc PyObject_Malloc , una función que se usa no solo para las extensiones C, sino también para todas las asignaciones dinámicas al ejecutar un progtwig Python, como 100*234 , str(100) o 10 + 4j :

 >>> id(10 + 4j) 139721697591440 >>> id(10 + 4j) 139721697591504 >>> id(10 + 4j) 139721697591440 

Las instancias de complex() anteriores son pequeños objetos asignados en un grupo dedicado.

La asignación de objetos pequeños (<256 bytes) con PyMem_Malloc PyObject_Malloc es bastante eficiente ya que se realiza a partir de una agrupación de 8 bytes alineados en bloques, existiendo una agrupación para cada tamaño de bloque. También hay bloques de Pages y Arenas para asignaciones más grandes.

Este comentario sobre el código fuente explica cómo se optimiza la llamada PyObject_Malloc :

 /* * The basic blocks are ordered by decreasing execution frequency, * which minimizes the number of jumps in the most common cases, * improves branching prediction and instruction scheduling (small * block allocations typically result in a couple of instructions). * Unless the optimizer reorders everything, being too smart... */ 

Las agrupaciones, las páginas y las arenas son optimizaciones destinadas a reducir la fragmentación de la memoria externa de los progtwigs de Python de larga ejecución.

Echa un vistazo al código fuente para obtener la documentación detallada completa de los internos de memoria de Python.

Está perfectamente bien que las extensiones asignen memoria con malloc u otros asignadores del sistema. Esto es normal e inevitable para muchos tipos de módulos; la mayoría de los módulos que envuelven otras bibliotecas, que no saben nada acerca de Python, causarán asignaciones nativas cuando se realicen dentro de esa biblioteca. (Algunas bibliotecas le permiten controlar la asignación lo suficiente para evitar esto; la mayoría no lo hace).

El uso de PyMem_Malloc tiene un serio inconveniente: es necesario mantener la GIL al usarla. Las bibliotecas nativas a menudo desean liberar la GIL cuando realizan cálculos con un uso intensivo de la CPU o realizan cualquier llamada que pueda bloquear, como I / O. La necesidad de bloquear el GIL antes de las asignaciones puede estar en algún lugar entre un inconveniente y un problema de rendimiento.

El uso de envoltorios de Python para la asignación de memoria permite utilizar el código de depuración de memoria de Python. Sin embargo, con herramientas como Valgrind, dudo del valor real de eso.

Necesitarás usar estas funciones si una API lo requiere; por ejemplo, si a una API se le pasa un puntero que debe asignarse con estas funciones, para que se pueda liberar con ellas. Salvo una razón explícita como esa para usarlos, me quedo con la asignación normal.

Desde mi experiencia escribiendo las funciones de MATLAB .mex, creo que el factor determinante más importante en si usas malloc o no es la portabilidad. Supongamos que tiene un archivo de encabezado que realiza una carga de funciones útiles utilizando solo los tipos de datos c internos (no es necesaria la interacción del objeto Python, por lo que no hay problema al usar malloc), y de repente se da cuenta de que desea trasladar ese archivo de encabezado a un código base diferente Nada que ver con Python (tal vez sea un proyecto escrito únicamente en C), usar malloc obviamente sería una solución mucho más portátil.

Pero para su código que es puramente una extensión de Python, mi reacción inicial sería esperar que la función c nativa se desempeñe más rápido. No tengo evidencia para respaldar esto 🙂