Перевод статьи подготовлен специально для студентов курса «Разработчик Python».
Внимание: код в этой статье лицензирован под GNU AGPLv3.
Я написал это руководство, поскольку не смог найти такого, которое будет объединять в себе все полезное о ctypes. Надеюсь, эта статья сделает чью-то жизнь намного легче.
Содержание:
- Базовые оптимизации
- сtypes
- Компиляция под Python
- Структуры в Python
- Вызов вашего кода на С
- PyPy
Базовые оптимизации
Перед переписыванием исходного кода Python на С, рассмотрим базовые методы оптимизации в Python.
Встроенные структуры данных
Встроенные структуры данных в Python, такие как set и dict, написаны на С. Они работают гораздо быстрее, чем ваши собственные структуры данных, написанные как классы Python. Другие структуры данных, помимо стандартных set, dict, list и tuple описаны в документации модуля collections.
Списочные выражения
Вместо того, чтобы добавлять элементы к списку стандартным методом, воспользуйтесь списочными выражениями.
# Slow
mapped = []
for value in originallist:
mapped.append(myfunc(value))
# Faster
mapped = [myfunc(value) in originallist]
ctypes
Модуль ctypes позволяет вам взаимодействовать с кодом на С из Python без использования модуля subprocess
или другого подобного модуля для запуска других процессов из CLI.
Тут всего две части: компиляция кода на С для загрузки в качестве shared object
и настройка структур данных в коде на Python для сопоставления их с типами С.
В этой статье я буду соединять свой код на 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']
Одна из проблем заключается в том, что эта конкретная функция на С — это сигнатура функции, которая принимает списки строк в качестве типов аргументов, и возвращает тип, у которого нет фиксированной длины. Я решаю эту задачу с помощью структуры последовательности, содержащей указатели и длину.
Компиляция кода на С под Python
Сначала, исходный код на С (lcs.c) компилируется в lcs.so
, чтобы загружаться в Python.
gcc -c -Wall -Werror -fpic -O3 lcs.c -o lcs.o
gcc -shared -o lcs.so lcs.o
- -Wall отобразит все предупреждения;
- -Werrow обернет все предупреждения в ошибки;
- -fpic сгенерирует независимые от положения инструкции, которые понадобятся, если вы захотите использовать эту библиотеку в Python;
- -O3 максимизирует оптимизацию;
А теперь мы начнем писать код на Python, используя полученный файл shared object.
Структуры в Python
Ниже представлены две структуры данных, которые используются в моем коде на С.
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>
). - В
ctypes
есть похожие типы c_char (char) и c_char_p (*char). - В
ctypes
также естьPOINTER()
, который создает указатель типа из каждого типа ему переданного. - Если у вас есть рекурсивное определение, как в
CELL
, вы должны передать начальное объявление, а затем добавить поля_fields_
, чтобы позже получить ссылку на себя же. - Поскольку я не использовал
CELL
в своем коде на Python, мне не нужно было писать эту структуру, но у нее есть интересное свойство в рекурсивном поле.
Вызов вашего кода на С
Кроме того, мне нужен был какой-нибудь код для преобразования типов Python в новые структуры на С. Теперь вы можете использовать свою новую функцию на С, чтобы ускорить работу кода на 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
– это список байт, они могут быть декодированы для полученияret
в виде спискаstr
.- lcsmodule.freeSequence(common) просто освобождает память, ассоциированную с common. Это важно, поскольку сборчик мусора (AFAIK) его не соберет автоматически.
Оптимизированный код на Python – это код, который вы написали на С и обернули в Python.
Кое-что еще: PyPy
Внимание: Сам я никогда PyPy не пользовался.
Одна из самых простых оптимизаций заключается в запуске ваших программ в среде выполнения PyPy, которая содержит в себе JIT-компилятор (just-in-time), который ускоряет работу циклов, компилируя их в машинный код при многократном выполнении.
Если у вас появятся комментарии или вы захотите что-то обсудить, напишите мне (samuel.robert.stevens@gmail.com).
На этом все. До встречи на курсе!
Автор: MaxRokatansky