Python3 + C, есть ли смысл?

от автора

Всем привет! Недавно работал над своим пет проектом, где нужно было много много считать, а программа не успевала проводить расчеты вовремя. Имею ввиду, что программа работала чуть ли не 15 минут, хотя должна была бы делать это пошустрее, ведь пользователь — человек искушенный.

Итак, предположим, что я пишу программу, по сортировке массивов, банальная история, ничего сложного, давай реализуем ее на Python3 и на языке C в качестве простых функций, на вход которых идут только массивчики(ну в С не совсем массив на вход, ну да ладно, мы сейчас не об этом). Реализация на Python3 будет выглядеть так:

def bubble_sort(arr):   # метод который реализует свап переменных     def swap(i, j):         arr[i], arr[j] = arr[j], arr[i]      n = len(arr)     swapped = True          x = -1     # реализация сортировки     while swapped:         swapped = False         x = x + 1         for i in range(1, n-x):             if arr[i - 1] > arr[i]:                 swap(i - 1, i)                 swapped = True

А на языке С так:

#include <stdio.h>  // количество элементов массива #define len 1000  //инициализация обрабатываемого массива int a[len];  // Метод по добавлению элементов, которые будем сртировать void addItemInArray(int id, int val) {     a[id] = val; }  // метод сортировки void sort(void) {     for(int i = 0 ; i < len - 1; i++) {         for(int j = 0 ; j < len - i - 1 ; j++) {             if(a[j] > a[j+1]) {                 int tmp = a[j];                 a[j] = a[j+1] ;                 a[j+1] = tmp;             }         }     } }  // тестовый метод, для проверки подключения к проекту на Python void test(int a) {   printf("%d", a); }  

Я использовал не самый быстрый метод, однако для наглядности, этого будет достаточно.

И по той причине что мы будет запускать все из Питона напрямую — припишем простой декоратор на проверку — сколько времени заняло выполнение функции.

# название декоратора, на вход идет функция (автоматически подставляется функция указанная под объявлением декоратора) def timein(func):     # функция "обертка", на вход идут аргументы функций, которые будут использовать этот декоратор     def wrapper(val):         start = datetime.now()         # вызов метода, который положили в декоратор         func(val)         end = datetime.now()         print(f"было потрачено времени - {end - start}")     return wrapper

Что дальше нам нужно — нам нужно скомпилировать C файл для возможности его использования в Python файле, для этого понадобится библиотека ctypes.

# Для Linux $ gcc -shared -Wl,-soname,sort -o sort.so -fPIC sort.c ​ # Для Mac $ gcc -shared -Wl,-install_name,sort.so -o sort.so -fPIC sort.c

В результате мы будем использовать файл sort.so и обращаясь к нему — будем вызывать функции. Ну а теперь к самому интересному, подключим библиотеку ctypes и с ее помощью попробуем вызвать тестовый метод.

from ctypes import *  my_lib = CDLL("./sort.so") # Подключаем C-файл  some_val = 100 # значение для тестового метода  # my_lib.test(some_val)  сработает, однако не стоит так делать # почему не стоит - далее в статье my_lib.test(c_int(some_val))

Результат работы программы такой:

Отработало корректно(так как тестовый метод должен был просто напечатать переданное в него число типа int), а теперь к тому — почему лучше писать таким образом(c_int(some_val)).
Из-за того, что разные типы данных, имеют разный размер (занимают разное количество ячеек памяти), могут быть проблемы при «смеси» этих значений. Вот табличка с типами:

тип ctypes

тип C

тип Python

c_bool

_Bool

bool (1)

c_char

char

1-символьный байтовый объект

c_wchar

wchar_t

1-символьная строка

c_byte

char

int

c_ubyte

unsigned char

int

c_short

short

int

c_ushort

unsigned short

int

c_int

int

int

c_uint

unsigned int

int

c_long

long

int

c_ulong

unsigned long

int

c_longlong

__int64 or long long

int

c_ulonglong

unsigned __int64 or unsigned long long

int

c_size_t

size_t

int

c_ssize_t

ssize_t or Py_ssize_t

int

c_float

float

float

c_double

double

float

c_longdouble

long double

float

c_char_p

char * (оканчивающийся на NUL)

байтовый объект или None

c_wchar_p

wchar_t * (оканчивающийся на NUL)

string или None

c_void_p

void *

int или None

Теперь подготовим массив значений, который будем сортировать.

for _ in range(1000):     my_array.append(randint(1, 500))

Теперь подготовим массив в нашем модуле в С.

for i in range(len(my_array)):     # в качестве первого аргумента передаем номер элемента в массиве, а второе - значение     my_lib.addItemInArray(i, my_array[i]) 

Ну и последний шаг — запуск сортировки в С и Python.

# реализация метода для вызова функции из С @timein def sort_C(a):     my_lib.sort(a)  sort_C(a) # вызов из С sort(my_array) # вызов питоновской функции

В итоге код на Python будет выглядеть так:

from datetime import datetime from ctypes import * from random import randint  my_lib = CDLL('./sort.so')  def timein(func):     def wrapper(val):         start = datetime.now()         func(val)         end = datetime.now()         print(f"было потрачено времени - {end - start}")     return wrapper  @timein def sort(array):     for i in range(len(array) - 1):         for j in range(len(array) - 2):             if array[i] > array[j]:                  array[i], array[j] = array[j], array[i]  my_array = []  for _ in range(1000):     my_array.append(randint(1, 500))  sort(my_array)  # заполняю массив в С for i in range(len(my_array)):     my_lib.addItemInArray(i, my_array[i])  a = 1  @timein def sort_C(a):     my_lib.sort(a)  sort_C(a) 

И соответственно на С, он будет выглядеть таким образом:

include <stdio.h>  #define len 1000  void test(int a) { printf("%d", a); }  // инициализирую обраюатываемый массив int a[len];  // Метод по добавлению элементов, которые будем сртировать void addItemInArray(int id, int val) {     a[id] = val; }  // метод сортировки void sort(int asd) {     for(int i = 0 ; i < len - 1; i++) {         for(int j = 0 ; j < len - i - 1 ; j++) {             if(a[j] > a[j+1]) {                 int tmp = a[j];                 a[j] = a[j+1] ;                 a[j+1] = tmp;             }         }     } } 

И финал — предварительная компиляция и запуск с проверкой — сколько времени потребуется разным языкам справиться с одним и тем же действием:

Первый результат — на питоне, а второй — на С, разница по времени кажется колоссальной, а с увеличением количества элементов — разница во времени будет еще существеннее. И вот мы пришли к закономерному результату — выгодно использовать расчеты на C, в своих Python проектах. В общем и целом, на мой взгляд написать на С пару тройку методов для расчетов — не так сложно, а в итоге это может сыграть важную роль в вашем проекте.

Спасибо за уделенное время.


ссылка на оригинал статьи https://habr.com/ru/articles/741724/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *