Внимание: код в этой статье лицензирован под 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).
На этом все. До встречи на курсе!
Look at the way my buddy Wesley Virgin's biography starts with this SHOCKING and controversial VIDEO.
ОтветитьУдалитьWesley was in the military-and shortly after leaving-he discovered hidden, "self mind control" secrets that the government and others used to get anything they want.
These are the exact same secrets lots of famous people (notably those who "come out of nowhere") and top business people used to become rich and successful.
You've heard that you use less than 10% of your brain.
That's because the majority of your BRAINPOWER is UNCONSCIOUS.
Maybe this thought has even taken place IN YOUR own brain... as it did in my good friend Wesley Virgin's brain 7 years back, while riding a non-registered, garbage bucket of a car without a license and with $3 in his pocket.
"I'm absolutely frustrated with going through life paycheck to paycheck! When will I get my big break?"
You took part in those questions, ain't it right?
Your very own success story is waiting to happen. Go and take a leap of faith in YOURSELF.
UNLOCK YOUR SECRET BRAINPOWER