Optimasi kode python dengan ctypes

Terjemahan artikel disiapkan khusus untuk siswa kursus Pengembang Python .




Catatan : kode dalam artikel ini dilisensikan di bawah GNU AGPLv3 .

Saya menulis panduan ini karena saya tidak dapat menemukan satu yang menggabungkan semua hal berguna tentang ctypes. Saya harap artikel ini membuat hidup seseorang jauh lebih mudah.

Kandungan:

  1. Optimalisasi dasar
  2. gaya
  3. Kompilasi python
  4. Struktur dalam Python
  5. Panggil kode Anda dalam C
  6. Pypy

Optimalisasi dasar


Sebelum menulis ulang kode sumber Python di C, pertimbangkan metode optimasi dasar di Python.

Struktur Data Built-in


Struktur data built-in Python, seperti set dan dict, ditulis dalam C. Mereka bekerja jauh lebih cepat daripada struktur data Anda sendiri yang ditulis sebagai kelas Python. Struktur data lain selain set standar, dict, daftar, dan tuple dijelaskan dalam dokumentasi modul koleksi .

Daftar ekspresi


Alih-alih menambahkan item ke daftar menggunakan metode standar, gunakan ekspresi daftar.

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

ctypes


Modul ctypes memungkinkan Anda untuk berinteraksi dengan kode C dari Python tanpa menggunakan modul subprocessatau modul serupa lainnya untuk memulai proses lain dari CLI.

Hanya ada dua bagian: mengkompilasi kode C untuk memuat dalam kualitas shared objectdan mengatur struktur data dalam kode Python untuk memetakannya ke tipe C.

Dalam artikel ini saya akan menghubungkan kode Python saya dengan lcs.c , yang menemukan urutan terpanjang dalam dua daftar string. Saya ingin yang berikut ini berfungsi di Python:

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

Satu masalah adalah bahwa fungsi C khusus ini adalah tanda tangan dari fungsi yang mengambil daftar string sebagai tipe argumen dan mengembalikan tipe yang tidak memiliki panjang tetap. Saya mengatasi masalah ini dengan struktur urutan yang berisi pointer dan panjang.

Kompilasi kode C dengan Python


Pertama, kode sumber C ( lcs.c ) dikompilasi lcs.sountuk memuat dengan Python.

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

  • - Dinding akan menampilkan semua peringatan;
  • - Werror akan membungkus semua peringatan dalam kesalahan;
  • - fpic akan menghasilkan instruksi posisi-independen yang akan Anda perlukan jika Anda ingin menggunakan pustaka ini dengan Python;
  • - O3 memaksimalkan optimasi;

Dan sekarang kita akan mulai menulis kode Python menggunakan file objek bersama yang dihasilkan .

Struktur dalam Python


Di bawah ini adalah dua struktur data yang digunakan dalam kode C saya.

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

Dan ini adalah terjemahan dari struktur-struktur ini ke dalam 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))]

Beberapa catatan:

  • Semua struktur adalah kelas yang diwarisi dari ctypes.Structure.
  • Satu-satunya bidang _fields_adalah daftar tupel. Setiap tuple adalah ( <variable-name>, <ctypes.TYPE>).
  • Ada ctypesbeberapa tipe yang mirip di c_char (char) dan c_char_p (* char) .
  • Ada ctypesjuga POINTER()yang membuat pointer tipe dari setiap tipe yang diteruskan ke sana.
  • Jika Anda memiliki definisi rekursif seperti dalam CELL, Anda harus melewati deklarasi awal, dan kemudian menambahkan bidang _fields_untuk mendapatkan tautan ke diri Anda nanti.
  • Karena saya tidak menggunakan CELLPython dalam kode saya, saya tidak perlu menulis struktur ini, tetapi memiliki properti yang menarik di bidang rekursif.

Panggil kode Anda dalam C


Selain itu, saya memerlukan beberapa kode untuk mengubah tipe Python ke struktur baru dalam C. Sekarang Anda dapat menggunakan fungsi C baru Anda untuk mempercepat kode 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']

Beberapa catatan:

  • **char (daftar string) cocok langsung dengan daftar byte dalam Python.
  • Ada lcs.cfungsi lcs()dengan struct urutan Sequence * lcs (struct Sequence * s1, struct Sequence * s2) . Untuk mengatur tipe pengembalian, saya menggunakan lcsmodule.lcs.restype = ctypes.POINTER(SEQUENCE).
  • Untuk melakukan panggilan dengan referensi ke struktur Sequence , saya menggunakan ctypes.byref()yang mengembalikan "light pointer" ke objek Anda (lebih cepat dari ctypes.POINTER()).
  • common.items- Ini adalah daftar byte, mereka dapat diterjemahkan untuk mendapatkan retdalam bentuk daftar str.
  • lcsmodule.freeSequence (common) hanya membebaskan memori yang terkait dengan common. Ini penting karena pengumpul sampah (AFAIK) tidak akan secara otomatis mengumpulkannya.

Kode Python yang dioptimalkan adalah kode yang Anda tulis dalam C dan dibungkus dengan Python.

Sesuatu Lebih: PyPy


Perhatian: Saya sendiri belum pernah menggunakan PyPy.
Salah satu optimasi paling sederhana adalah menjalankan program Anda di runtime PyPy , yang berisi kompiler JIT (just-in-time) yang mempercepat kerja loop, mengkompilasinya ke dalam kode mesin untuk eksekusi berulang.

Jika Anda memiliki komentar atau ingin membahas sesuatu, tulis kepada saya (samuel.robert.stevens@gmail.com).

Itu saja. Sampai jumpa di lapangan !

All Articles