Optimizaci贸n de c贸digo Python con ctypes

Se prepar贸 una traducci贸n del art铆culo espec铆ficamente para los estudiantes del curso Python Developer .




Nota : el c贸digo de este art铆culo est谩 licenciado bajo GNU AGPLv3 .

Escrib铆 esta gu铆a porque no pude encontrar una que combine todas las cosas 煤tiles sobre los ctypes. Espero que este art铆culo haga la vida de alguien mucho m谩s f谩cil.

Contenido:

  1. Optimizaciones b谩sicas
  2. estilos
  3. Compilaci贸n de Python
  4. Estructuras en Python
  5. Llame su c贸digo en C
  6. Pypy

Optimizaciones b谩sicas


Antes de reescribir el c贸digo fuente de Python en C, considere los m茅todos b谩sicos de optimizaci贸n en Python.

Estructuras de datos incorporadas


Las estructuras de datos integradas de Python, como set y dict, est谩n escritas en C. Funcionan mucho m谩s r谩pido que sus propias estructuras de datos escritas como clases de Python. Otras estructuras de datos adem谩s del conjunto est谩ndar, dict, list y tuple se describen en la documentaci贸n del m贸dulo de colecciones .

Lista de expresiones


En lugar de agregar elementos a la lista usando el m茅todo est谩ndar, use expresiones de lista.

# Slow
      mapped = []
      for value in originallist:
          mapped.append(myfunc(value))
      
      # Faster
      mapped = [myfunc(value) in originallist]

tipos


El m贸dulo ctypes le permite interactuar con el c贸digo C de Python sin usar un m贸dulo subprocessu otro m贸dulo similar para iniciar otros procesos desde la CLI.

Solo hay dos partes: compilar el c贸digo C para cargar en calidad shared objecty configurar estructuras de datos en el c贸digo Python para asignarlas a los tipos C.

En este art铆culo conectar茅 mi c贸digo Python con lcs.c , que encuentra la subsecuencia m谩s larga en dos listas de cadenas. Quiero que lo siguiente funcione en Python:

list1 = ['My', 'name', 'is', 'Sam', 'Stevens', '!']
      list2 = ['My', 'name', 'is', 'Alex', 'Stevens', '.']
      
      common = lcs(list1, list2)
      
      print(common)
      # ['My', 'name', 'is', 'Stevens']

Un problema es que esta funci贸n C particular es la firma de una funci贸n que toma listas de cadenas como tipos de argumento y devuelve un tipo que no tiene una longitud fija. Resuelvo este problema con una estructura de secuencia que contiene punteros y longitud.

Compilar c贸digo C en Python


Primero, el c贸digo fuente C ( lcs.c ) se compila lcs.sopara cargar en Python.

gcc -c -Wall -Werror -fpic -O3 lcs.c -o lcs.o
      gcc -shared -o lcs.so lcs.o

  • - Muro mostrar谩 todas las advertencias;
  • - Werror envolver谩 todas las advertencias en errores;
  • - fpic generar谩 instrucciones independientes de la posici贸n que necesitar谩 si desea usar esta biblioteca en Python;
  • - O3 maximiza la optimizaci贸n;

Y ahora comenzaremos a escribir c贸digo Python usando el archivo de objeto compartido resultante .

Estructuras en Python


A continuaci贸n hay dos estructuras de datos que se usan en mi c贸digo C.

struct Sequence
      {
          char **items;
          int length;
      };
      
      struct Cell
      {
          int index;
          int length;
          struct Cell *prev;
      };

Y aqu铆 est谩 la traducci贸n de estas estructuras a Python.

import ctypes
      class SEQUENCE(ctypes.Structure):
          _fields_ = [('items', ctypes.POINTER(ctypes.c_char_p)),
                      ('length', ctypes.c_int)]
      
      class CELL(ctypes.Structure):
          pass
      
      CELL._fields_ = [('index', ctypes.c_int), ('length', ctypes.c_int),
                       ('prev', ctypes.POINTER(CELL))]

Algunas notas

  • Todas las estructuras son clases que heredan de ctypes.Structure.
  • El 煤nico campo _fields_es una lista de tuplas. Cada tupla es ( <variable-name>, <ctypes.TYPE>).
  • Hay ctypestipos similares en c_char (char) y c_char_p (* char) .
  • Tambi茅n ctypeshay uno POINTER()que crea un puntero de tipo a partir de cada tipo que se le pasa.
  • Si tiene una definici贸n recursiva como en CELL, debe pasar la declaraci贸n inicial y luego agregar los campos _fields_para obtener un enlace m谩s adelante.
  • Como no utilic茅 CELLPython en mi c贸digo, no necesit茅 escribir esta estructura, pero tiene una propiedad interesante en el campo recursivo.

Llame su c贸digo en C


Adem谩s, necesitaba un c贸digo para convertir los tipos de Python en nuevas estructuras en C. Ahora puede usar su nueva funci贸n C para acelerar el c贸digo de Python.

def list_to_SEQUENCE(strlist: List[str]) -> SEQUENCE:
          bytelist = [bytes(s, 'utf-8') for s in strlist]
          arr = (ctypes.c_char_p * len(bytelist))()
          arr[:] = bytelist
          return SEQUENCE(arr, len(bytelist))
      
      
      def lcs(s1: List[str], s2: List[str]) -> List[str]:
          seq1 = list_to_SEQUENCE(s1)
          seq2 = list_to_SEQUENCE(s2)
      
          # struct Sequence *lcs(struct Sequence *s1, struct Sequence *s2)
          common = lcsmodule.lcs(ctypes.byref(seq1), ctypes.byref(seq2))[0]
      
          ret = []
      
          for i in range(common.length):
              ret.append(common.items[i].decode('utf-8'))
          lcsmodule.freeSequence(common)
      
          return ret
      
      lcsmodule = ctypes.cdll.LoadLibrary('lcsmodule/lcs.so')
      lcsmodule.lcs.restype = ctypes.POINTER(SEQUENCE)
      
      list1 = ['My', 'name', 'is', 'Sam', 'Stevens', '!']
      list2 = ['My', 'name', 'is', 'Alex', 'Stevens', '.']
      
      common = lcs(list1, list2)
      
      print(common)
      # ['My', 'name', 'is', 'Stevens']

Algunas notas

  • **char (lista de cadenas) coincide directamente con una lista de bytes en Python.
  • Hay lcs.cuna funci贸n lcs()con la firma struct Sequence * lcs (struct Sequence * s1, struct Sequence * s2) . Para configurar el tipo de retorno, yo uso lcsmodule.lcs.restype = ctypes.POINTER(SEQUENCE).
  • Para hacer una llamada con una referencia a la estructura de secuencia , utilizo ctypes.byref()una que devuelve un "puntero de luz" a su objeto (m谩s r谩pido que ctypes.POINTER()).
  • common.items- esta es una lista de bytes, se pueden decodificar para obtener retla forma de una lista str.
  • lcsmodule.freeSequence (com煤n) solo libera la memoria asociada con com煤n. Esto es importante porque el recolector de basura (AFAIK) no lo recoger谩 autom谩ticamente.

El c贸digo optimizado de Python es el c贸digo que usted escribi贸 en C y lo envolvi贸 en Python.

Algo m谩s: PyPy


Atenci贸n: yo mismo nunca he usado PyPy.
Una de las optimizaciones m谩s simples es ejecutar sus programas en el tiempo de ejecuci贸n PyPy , que contiene un compilador JIT (justo a tiempo) que acelera el trabajo de los bucles, compil谩ndolos en c贸digo m谩quina para la ejecuci贸n repetida.

Si tiene comentarios o quiere discutir algo, escr铆bame (samuel.robert.stevens@gmail.com).

Eso es todo. 隆Nos vemos en el curso !

All Articles