使用ctypes的Python代码优化

本文的翻译是专门为Python开发人员课程的学生准备的




注意:本文中的代码已获得GNU AGPLv3的许可

我写本指南的原因是找不到与ctypes有关的所有有用内容的指南。我希望这篇文章可以使某人的生活变得更轻松。

内容:

  1. 基本优化
  2. 样式
  3. Python编译
  4. Python中的结构
  5. 用C调用您的代码
  6. y

基本优化


在用C重写Python源代码之前,请考虑Python中的基本优化方法。

内置数据结构


Python的内置数据结构(例如set和dict)是用C编写的。它们的工作速度比您自己编写为Python类的数据结构要快得多。集合模块文档中介绍了除标准集,字典,列表和元组以外的其他数据结构

列出表达式


不要使用标准方法将项目添加到列表中,而要使用列表表达式。

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

类型


ctypes 模块允许您与Python中的C代码进行交互,而无需使用模块subprocess或其他类似模块来从CLI启动其他进程。

只有两部分:编译C代码以提高质量,shared object并在Python代码中设置数据结构以将其映射到类型C。

在本文中,我将把我的Python代码与lcs.c连接,该代码将找到两个子序列中最长的子序列。字符串列表。我希望以下内容可以在Python中工作:

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

一个问题是,这个特定的C函数是一个函数的签名,该函数将字符串列表作为参数类型,并返回一个没有固定长度的类型。我用包含指针和长度的序列结构解决了这个问题。

用Python编译C代码


首先,将C源代码(lcs.c)编译为lcs.so要在Python 加载。

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

  • - 沃尔将显示所有警告;
  • - Werror将包裹在错误的所有警告;
  • - 如果要在Python中使用此库,fpic将生成与位置无关的指令;
  • - O3最大化优化;

现在,我们将开始使用生成的共享库文件来编写Python代码

Python中的结构


下面是我的C代码中使用的两个数据结构。

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

这些是这些结构到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))]

一些注意事项:

  • 所有结构都是从继承的类ctypes.Structure
  • 唯一的字段_fields_是元组列表。每个元组是(<variable-name><ctypes.TYPE>)。
  • c_char(char)和c_char_p(* char)中ctypes相似的类型
  • 还有ctypes一种POINTER()可以根据传递给它的每种类型来创建类型指针。
  • 如果您有类似in的递归定义CELL,则必须传递初始声明,然后添加字段_fields_,以便以后获得指向您自己的链接。
  • 由于我没有CELL在代码中使用Python,因此不需要编写此结构,但是它在递归字段中具有有趣的属性。

用C调用您的代码


另外,我需要一些代码将Python类型转换为C中的新结构。现在,您可以使用新的C函数来加快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']

一些注意事项:

  • **char (字符串列表)直接匹配Python中的字节列表。
  • lcs.c一个lcs()带有签名struct Sequence * lcs(struct Sequence * s1,struct Sequence * s2)的函数要设置返回类型,我使用lcsmodule.lcs.restype = ctypes.POINTER(SEQUENCE)
  • 要使用对Sequence结构的引用进行调用,我使用了 ctypes.byref()一种将“轻型指针”返回到您的对象的方法(比快ctypes.POINTER())。
  • common.items-这是一个字节列表,可以将它们解码为retlist形式str
  • lcsmodule.freeSequence(公共)仅释放与公共关联的内存。这很重要,因为垃圾收集器(AFAIK)不会自动收集它。

优化的Python代码是您用C编写并包装在Python中的代码。

更多:PyPy


注意:我本人从未使用过PyPy。
最简单的优化方法之一是在PyPy运行时中运行程序,该运行时包含JIT编译器(及时),可加快循环的工作,将其编译为机器代码以重复执行。

如果您有意见或要讨论什么,请写信给我(samuel.robert.stevens@gmail.com)。

就这样。在课程中

All Articles