Python-Code-Optimierung mit ctypes

Eine Übersetzung des Artikels wurde speziell für Studenten des Python Developer- Kurses erstellt .




Hinweis : Der Code in diesem Artikel ist unter GNU AGPLv3 lizenziert .

Ich habe diesen Leitfaden geschrieben, weil ich keinen finden konnte, der alle nützlichen Dinge über ctypes kombiniert. Ich hoffe, dieser Artikel erleichtert jemandem das Leben erheblich.

Inhalt:

  1. Grundlegende Optimierungen
  2. Stile
  3. Python-Kompilierung
  4. Strukturen in Python
  5. Rufen Sie Ihren Code in C auf
  6. Pypy

Grundlegende Optimierungen


Berücksichtigen Sie vor dem Umschreiben des Python-Quellcodes in C die grundlegenden Optimierungsmethoden in Python.

Integrierte Datenstrukturen


Die in Python integrierten Datenstrukturen wie set und dict sind in C geschrieben. Sie funktionieren viel schneller als Ihre eigenen Datenstrukturen, die als Python-Klassen geschrieben wurden. Weitere Datenstrukturen neben dem Standardsatz, dem Diktat, der Liste und dem Tupel sind in der Dokumentation des Sammlungsmoduls beschrieben .

Listen Sie Ausdrücke auf


Verwenden Sie Listenausdrücke, anstatt Elemente mit der Standardmethode zur Liste hinzuzufügen.

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

ctypes


Mit dem Modul ctypes können Sie mit C-Code aus Python interagieren, ohne ein Modul subprocessoder ein ähnliches Modul zum Starten anderer Prozesse über die CLI zu verwenden.

Es gibt nur zwei Teile: Kompilieren von C-Code zum Laden in Qualität shared objectund Einrichten von Datenstrukturen in Python-Code, um sie den Typen C zuzuordnen.

In diesem Artikel werde ich meinen Python-Code mit lcs.c verbinden , das die längste Teilsequenz in zwei findet Listen von Zeichenfolgen. Ich möchte, dass Folgendes in Python funktioniert:

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

Ein Problem besteht darin, dass diese bestimmte C-Funktion die Signatur einer Funktion ist, die Listen von Zeichenfolgen als Argumenttypen verwendet und einen Typ zurückgibt, der keine feste Länge hat. Ich löse dieses Problem mit einer Sequenzstruktur, die Zeiger und Länge enthält.

Kompilieren von C-Code in Python


Zunächst wird C-Quellcode ( lcs.c ) lcs.sozum Laden in Python kompiliert .

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

  • - Die Wand zeigt alle Warnungen an.
  • - Werror wird alle Warnungen in Fehler einschließen ;
  • - fpic generiert positionsunabhängige Anweisungen, die Sie benötigen, wenn Sie diese Bibliothek in Python verwenden möchten.
  • - O3 maximiert die Optimierung;

Und jetzt werden wir beginnen, Python-Code unter Verwendung der resultierenden gemeinsam genutzten Objektdatei zu schreiben .

Strukturen in Python


Unten sind zwei Datenstrukturen aufgeführt, die in meinem C-Code verwendet werden.

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

Und hier ist die Übersetzung dieser Strukturen in 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))]

Ein paar Anmerkungen:

  • Alle Strukturen sind Klassen, die von erben ctypes.Structure.
  • Das einzige Feld _fields_ist eine Liste von Tupeln. Jedes Tupel ist ( <variable-name>, <ctypes.TYPE>).
  • Es ctypesgibt ähnliche Typen in c_char (char) und c_char_p (* char) .
  • Es gibt ctypesauch einen POINTER(), der aus jedem übergebenen Typ einen Typzeiger erstellt.
  • Wenn Sie eine rekursive Definition wie in haben CELL, müssen Sie die ursprüngliche Deklaration übergeben und dann die Felder hinzufügen _fields_, um später einen Link zu sich selbst zu erhalten.
  • Da ich CELLPython nicht in meinem Code verwendet habe, musste ich diese Struktur nicht schreiben, aber sie hat eine interessante Eigenschaft im rekursiven Feld.

Rufen Sie Ihren Code in C auf


Außerdem brauchte ich Code, um Python-Typen in neue Strukturen in C zu konvertieren. Jetzt können Sie Ihre neue C-Funktion verwenden, um Python-Code zu beschleunigen.

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']

Ein paar Anmerkungen:

  • **char (Liste der Zeichenfolgen) stimmt direkt mit einer Liste der Bytes in Python überein.
  • Es lcs.cgibt eine Funktion lcs()mit der Signatur struct Sequence * lcs (struct Sequence * s1, struct Sequence * s2) . Um den Rückgabetyp einzurichten, verwende ich lcsmodule.lcs.restype = ctypes.POINTER(SEQUENCE).
  • Um einen Aufruf mit Bezug auf die Sequenzstruktur zu tätigen , verwende ich ctypes.byref()einen, der einen „Lichtzeiger“ auf Ihr Objekt zurückgibt (schneller als ctypes.POINTER()).
  • common.items- Dies ist eine Liste von Bytes, die dekodiert werden können, um retin Form einer Liste zu erhalten str.
  • lcsmodule.freeSequence (common) gibt nur den mit common verknüpften Speicher frei. Dies ist wichtig, da der Garbage Collector (AFAIK) ihn nicht automatisch sammelt.

Optimierter Python-Code ist Code, den Sie in C geschrieben und in Python eingeschlossen haben.

Etwas mehr: PyPy


Achtung: Ich selbst habe PyPy noch nie benutzt.
Eine der einfachsten Optimierungen besteht darin, Ihre Programme in der PyPy- Laufzeit auszuführen , die einen JIT-Compiler (Just-in-Time) enthält, der die Arbeit von Schleifen beschleunigt und sie zur wiederholten Ausführung in Maschinencode kompiliert.

Wenn Sie Kommentare haben oder etwas besprechen möchten, schreiben Sie mir (samuel.robert.stevens@gmail.com).

Das ist alles. Wir sehen uns auf dem Kurs !

All Articles