Всем привет! Недавно работал над своим пет проектом, где нужно было много много считать, а программа не успевала проводить расчеты вовремя. Имею ввиду, что программа работала чуть ли не 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)
).
Из-за того, что разные типы данных, имеют разный размер (занимают разное количество ячеек памяти), могут быть проблемы при "смеси" этих значений. Вот табличка с типами:
|
---|
Теперь подготовим массив значений, который будем сортировать.
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 проектах. В общем и целом, на мой взгляд написать на С пару тройку методов для расчетов — не так сложно, а в итоге это может сыграть важную роль в вашем проекте.
Спасибо за уделенное время.