Домашнее задание#
Задание
Название работы: "Разработка Модуля Python на С++"
- По вариантам реализовать модуль на С++ и произвести подключение в Python проект (дать формулировку задания, номер варианта, привести какой метод компиляции был выбран (скрины консольных команд или настройки проекта))
- Произвести замеры по скорости работы модуля по времени! (измерения времени производить внутри файла C++)
- Реализовать аналогичную функцию на чистом Python
- Произвести замеры по скорости работы чисто питоновской функции
- Замеры ввести в таблицу. Округление времени до 2 знаков после запятой.
№ Теста | Количество итераций | C++ (с) | Python (с) |
---|---|---|---|
1 | 10000 | 5.23 | 12.45 |
2 | 50000 | 18.67 | 45.92 |
3 | 100000 | 36.89 | 90.75 |
- Построить график по вашим данным, пользуясь любым удобным способом (рекомендуется Excel)
- Сделать вывод во сколько раз и кто быстрее
- Приложить в виде Листинга 1 и Листинга 2 коды программ
- Дать ссылку на гитхаб
Задание повышенной сложности.
Методичские указания#
CTypes + Visual studio CODE#
В Windows библиотека динамической компоновки (DLL) является исполняемым файлом, который выступает в качестве общей библиотеки функций и ресурсов. Динамическая компоновка — это возможность операционной системы. Она позволяет исполняемому файлу вызывать функции или использовать ресурсы, хранящиеся в отдельном файле. Эти функции и ресурсы можно компилировать и развертывать отдельно от использующих их исполняемых файлов.
Библиотека DLL не является отдельным исполняемым файлом. Библиотеки DLL выполняются в контексте приложений, которые их вызывают. Операционная система загружает библиотеку DLL в область памяти приложения. Это делается либо при загрузке приложения (неявная компоновка), либо по запросу во время выполнения (явная компоновка). Библиотеки DLL также упрощают совместное использование функций и ресурсов различными исполняемыми файлами. Несколько приложений могут осуществлять одновременный доступ к содержимому одной копии библиотеки DLL в памяти.
Создайте новый проект в Visual studio CODE
Из списка вариантов выберите пункт Библиотека динамической компоновки DLL
. Выберите место для проекта.
Автоматически сгененрируются несколько файлов pch.h
и dllmain
Question
// dllmain.cpp : Определяет точку входа для приложения DLL.
#include "pch.h"
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
Для дополнения кода необходимо добавить в проект (создать) файл хедера (например, Header.h
) и соотвествующий ему файл cpp (например, Source.cpp
)
Рассмотрим следующуюзадачу: пусть мне надо создать библиотеку из 3 функций (Функция для сложения целых чисел, Функция для сложения чисел с плавающей точкой, Функция вычисления синуса для N случайных чисел)
Приведем пример Header.h
для данной задачи:
#pragma once
#ifdef MYLIBRARY_EXPORTS
#define MYLIBRARY_API __declspec(dllexport) // Для компиляции библиотеки
#else
#define MYLIBRARY_API __declspec(dllimport) // Для использования библиотеки
#endif
extern "C" {
MYLIBRARY_API int add_int(int num1, int num2); // Функция для сложения целых чисел
MYLIBRARY_API float add_float(float num1, float num2); // Функция для сложения чисел с плавающей точкой
MYLIBRARY_API float compute_sin(int N); // Функция вычисления синуса для N случайных чисел
}
MYLIBRARY_AP
I — это макрос, который используется для управления экспортом и импортом функций из DLL. Когда библиотека компилируется, этот макрос подставляет правильную директиву компилятора:-
__declspec(dllexport)
используется для указания, что символ (функция или переменная) должен быть экспортирован из DLL.-
__declspec(dllimport)
используется для указания, что символ будет импортирован из DLL.
Приведем пример соотвествующего cpp
кода, в котором не будет ничего интересного, за исключением необходимости включения в код #include "pch.h"
#include "pch.h"
#include "Header.h"
#include <cmath>
#include <cstdlib>
#include <ctime>
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
int add_int(int num1, int num2) {
return num1 + num2;
}
float add_float(float num1, float num2) {
return num1 + num2;
};
float compute_sin(int N) {
clock_t start_time = clock(); // Начало замера времени
float result = 0.0f;
for (int i = 0; i < N; ++i) {
float angle = static_cast<float>(rand()) / RAND_MAX * 2 * M_PI; // Случайный угол в радианах
result += sin(angle); // Вычисление синуса
}
clock_t end_time = clock(); // Конец замера времени
float elapsed_time = float(end_time - start_time) / CLOCKS_PER_SEC; // Время в секундах
return elapsed_time; // Возвращаем время выполнения
}
После добавления файлов можно собрать решение
Затем найдите dll файл (мой проект назывался Dll1, поэтому название файла Dll1.dll)
Создайте проект на python (например в Visual studio Code)
Скопируйте *.dll файл в дирректорию, где будем писать python код
В Python-коде мы сначала импортируем модуль ctypes . Затем функция CDLL из того же модуля используется для загрузки C-библиотеки. Теперь функции из C-кода доступны для нас через переменную adder . Когда мы вызываем adder.add_int() , то автоматически вызывается C-функция add_int.
from ctypes import *
# Загружаем библиотеку
adder = CDLL('./adder.dll')
# Находим сумму целых чисел
res_int = adder.add_int(4,5)
print("Сумма 4 и 5 = " + str(res_int))
# Находим сумму действительных чисел
a = c_float(5.5)
b = c_float(4.1)
add_float = adder.add_float
add_float.restype = c_float
print("Сумма 5.5 и 4.1 = " + str(add_float(a, b)))
Интерфейс модуля ctypes позволяет использовать питоновские целые числа и строки при вызове C-функций.
Для других типов, например логического или действительных чисел, мы должны использовать корректные ctypes .
Python/C API#
C/Python API это, вероятно, наиболее широко применяемый метод - не благодаря своей простоте, а потому что он позволяет оперировать Python объектами из C кода.
Этот метод подразумевает написание C-кода специально для работы с Python. Все объекты Python
представляются как структуры PyObject
и заголовочный файл Python.h
предоставляет различные функции для работы с объектами.
Например, если PyObject
одновременно PyListType (список)
, то мы можем использовать функцию PyList_Size()
, чтобы получить длину списка.
Это эквивалентно коду len(some_list)
в Python
. Большинство основных функций/операторов для стандартных Python объектов доступны в C через Python.h .
Для работы вам может понадобиться Техническая документация Python/C API
Давайте напишем С-библиотеку для суммирования всех элементов списка Python (все элементы являются числами).
Создайте новый проект в Visual studio CODE
Создайте консольное приложение.
Найдите где располагается Python.
Настройте среду VS code(вкладка проект->свойства) согласно таблице:
Вкладка и раздел | Свойство | Значение |
---|---|---|
Общие свойства > конфигурации | Имя цели | Укажите имя модуля, который будет ссылаться на него из Python в from...import инструкциях, таких как суперфасткод. Это же имя будет использоваться в C++ при определении модуля для Python. Чтобы применить имя проекта в качестве имени модуля, сохраните значение по умолчанию $<(ProjectName)> . Для python_d.exe добавьте _d в конец файла. |
Тип конфигурации | Динамическая библиотека (.dll) | |
C/C++ > Общие | Дополнительные каталоги включаемых файлов | Добавьте папку "Включить Python" в соответствии с вашей установкой (например, c:\Python310\include ). |
C/C++ > Препроцессор | Определения препроцессора | Если он присутствует, измените значение _DEBUG на NDEBUG , чтобы оно соответствовало версии CPython. При использовании python_d.exe оставьте это значение неизменным. |
C/C++ > Создание кода | Библиотека времени выполнения | Многопоточные библиотеки DLL (/MD) для соответствия версии выпуска (nondebug) CPython. При использовании python_d.exe оставьте это значение как многопоточная библиотека DLL отладки (/MDd). |
Базовые проверки среды выполнения | По умолчанию | |
Компоновщик > Общие | Дополнительные каталоги библиотек | Добавьте подходящую для вашей установки папку Python libs с файлами .lib (например, c:\Python310\libs ). Убедитесь, что указали папку libs, содержащую файлы .lib, но не папку Lib, содержащую файлы .py. |
Пример настройки C/C++ > Общие| Дополнительные каталоги включаемых файлов
Пример настройки Компоновщик > Общие | Дополнительные каталоги библиотек
Tip
Не перепутайте директорию libs и Lib!
Напишем код в файле Sourse.cpp
#include <Python.h>
// Эта функция вызывается из Python кода
static PyObject* addList_add(PyObject* self, PyObject* args) {
PyObject* listObj;
// Входящие аргументы находятся в кортеже
// В нашем случае есть только один аргумент - список, на который мы будем
// ссылаться как listObj
if (!PyArg_ParseTuple(args, "O", &listObj))
return NULL;
// Длина списка
long length = PyList_Size(listObj);
// Проходимся по всем элементам
int i, sum = 0;
for (i = 0; i < length; i++) {
// Получаем элемент из списка - он также Python-объект
PyObject* temp = PyList_GetItem(listObj, i);
// Мы знаем, что элемент это целое число - приводим его к типу C long
long elem = PyLong_AsLong(temp); // Используем PyLong_AsLong вместо PyInt_AsLong
sum += elem;
}
// Возвращаемое в Python-код значение также Python-объект
// Приводим C long к Python integer
return Py_BuildValue("i", sum);
}
// Немного документации для 'add'
static char addList_docs[] =
"add( ): add all elements of the list\n";
/*
Эта таблица содержит необходимую информацию о функциях модуля
<имя функции в модуле Python>, <фактическая функция>,
<ожидаемые типы аргументов функции>, <документация функции>
*/
static PyMethodDef addList_funcs[] = {
{"add", (PyCFunction)addList_add, METH_VARARGS, addList_docs},
{NULL, NULL, 0, NULL}
};
/*
addList имя модуля и это блок его инициализации.
<желаемое имя модуля>, <таблица информации>, <документация модуля>
*/
static struct PyModuleDef addList_module = {
PyModuleDef_HEAD_INIT,
"addList", // Имя модуля
"Add all the lists", // Документация модуля
-1, // Размер состояния модуля
addList_funcs // Таблица функций
};
PyMODINIT_FUNC PyInit_addList(void) { // Для Python 3 используем PyInit_<module>
return PyModule_Create(&addList_module); // Используем PyModule_Create вместо Py_InitModule3
}
Tip
Комментарии к коду
- Заголовочный файл
представления типов объектов в Python) и определения функций (для работы с
Python-объектами).
- Дальше мы пишем функцию, которую собираемся вызывать из Python. По
соглашению, имя функции принимается {module-name}_{function-name}, которое в
нашем случае - addList_add . Подробнее об этой функции будет дальше.
- Затем заполняем таблицу, которая содержит всю необходимую информацию о
функциях, которые мы хотим иметь в модуле. Каждая строка относится к функции,
последняя - контрольное значение (строка из null элементов).
- Затем идёт блок инициализации модуля - PyMODINIT_FUNC init{module-name} .
Функция addList_add принимает аргументы типа PyObject (args также является
кортежем, но поскольку в Python всё является объектами, мы используем
унифицированный тип PyObject ). Мы парсим входные аргументы (фактически,
разбиваем кортеж на отдельные элементы) при помощи PyArg_ParseTuple() . Первый
параметр является аргументом для парсинга. Второй аргумент - строка,
регламентирующая процесс парсинга элементов кортежа args. Знак на N-ой позиции
строки сообщает нам тип N-ого элемента кортежа args, например - 'i' значит integer, 's' -
строка и 'O' - Python-объект. Затем следует несколько аргументов, где мы хотели бы
хранить выходные элементы PyArg_ParseTuple() . Число этих аргументов равно числу
аргументов, которые планируется передавать в функцию модуля и их позиционность
должна соблюдаться
Например, если мы ожидаем строку, целое число и список в
таком порядке, сигнатура функции будет следующего вида:
int n;
char *s;
PyObject* list;
PyArg_ParseTuple(args, "siO", &n, &s, &list);
В данном случае, нам нужно извлечь только объект списка и сохранить его в
переменной listObj . Затем мы используем функцию PyList_Size() чтобы получить
длину списка. Логика совпадает с len(some_list) в Python.
Теперь мы итерируем по списку, получая элементы при помощи функции
PyLint_GetItem(list, index) . Так мы получаем PyObject*. Однако, поскольку мы знаем,
что Python-объекты еще и PyIntType , то используем функцию PyInt_AsLong(PyObj *)
для получения значения. Выполняем процедуру для каждого элемента и получаем
сумму.
Затем переместим наш файл .cpp в директорию, где будет находится файл python.
Создаем файл setup.py
Tip
setup.py
— это скрипт, используемый в Python для создания и установки пакетов и модулей. Он описывает, как собрать и установить пакеты Python, а также конфигурацию, необходимую для компиляции расширений на C или C++
Пример кода setup.py
для Source.cpp
( Имя пакета млэете использовать любое, которое вам захочется импортировать в последствии, в моем случае addList
)
from distutils.core import setup, Extension
setup(
name='addList', # Имя пакета
version='1.0', # Версия пакета
ext_modules=[Extension('addList', ['Source.cpp'])] # Указание расширения на C/C++, которое будет собрано из файла Source.cpp
)
Теперь необхоимо открыть консоль и прописать команду (пути к файлу не учитываются, считается, что у вас открыта текущая дирректория проекта)
python setup.py build_ext --inplace
build_ext
: Этот параметр указывает на сборку расширений, то есть компиляцию C/C++ кода, указанных в файле setup.py, в динамическую библиотеку (например, .pyd или .so).--inplace
: Этот параметр заставляет установку происходить непосредственно в текущую директорию, а не в стандартную директорию установки для Python. --force
: Пересобирает расширение, даже если оно уже было собрано ранее.
Если команда не работает, убедитесь, что у вас установлен следующий пакет:
pip install setuptools
Теперь проект собран, осталось импортировать модуль cpp, как обычную библиотеку в py
import addList
l = [1,2,3,4,5, 9]
print("Сумма элементов списка - " + str(l) + " = " + str(addList.add(l)))
MinGW (для тех, у кого не получается)#
Чтобы проверить есть ли MinGW сделайте команду в CMD:
g++ --help
Пример, когда он не установлен:
Установочный файл GCC 11.4.0 + MinGW-w64 11.0.0 (UCRT) release 1 можно найти на этой странице.
Вы выбираете версию Win32 и Win64 в зависимости от своей системы.
Вы скачаете архив winlibs-x86_64-posix-seh-gcc-11.4.0-mingw-w64ucrt-11.0.0-r1.7z
Данные этого архива вытаскиваем. Внутри единственная папка minGW64
Её необходимо переместить в корень диска С (C:/minGW64)
Откройте меню “Система” в “Панели управления”:
Из меню “Система” перейдите в “Дополнительные параметры системы”:
Выберите “Переменные среды”:
Выберите переменную Path и нажмите кнопку “Изменить...“
Добавьте в новую строку полный путь до директории mingw32\bin и нажмите кнопку OK.
Чтобы проверить, что настройка выполнена успешно, откройте консоль (не в директории mingw32\bin) и выполните команду g++ --help:.
Теперь скомпилируем C-файл в .dll
-файл (.so
под Linux). Так мы получим файл
adder.dll
.
Скомпелируем файл на C++ в динамическую библиотеку (.dll) в операционной системе Windows, воспользовавшись MinGW. Для этого в консоли нужно прописать команду:
g++ -shared -o ИМЯ_ВАШЕГО_ФАЙЛА.dll МЯ_ВАШЕГО_ФАЙЛА.cpp
Пример кода для компиляции adder.сpp
// Простой C-файл - суммируем целые и действительные числа
#include <stdio.h>
int add_int(int, int);
float add_float(float, float);
int add_int(int num1, int num2){
return num1 + num2;
}
float add_float(float num1, float num2){
return num1 + num2;
}
После копмиляции C-файл в .dll
-файл (.so
под Linux). Так мы получим файл adder.dll
.
Теперь мы можем легко подключить наш файл и вызвать функцию (при условии, что мы поместили его в файлы проекта!!!)
from ctypes import *
# Загружаем библиотеку
adder = CDLL('./adder.dll')
# Находим сумму целых чисел
res_int = adder.add_int(4,5)
print("Сумма 4 и 5 = " + str(res_int))
# Находим сумму действительных чисел
a = c_float(5.5)
b = c_float(4.1)
add_float = adder.add_float
add_float.restype = c_float
print("Сумма 5.5 и 4.1 = " + str(add_float(a, b)))
В примере выше, C-файл содержит простой код - две функции: одна для нахождения
суммы двух целых чисел, другая - действительных.
В Python-коде мы сначала импортируем модуль ctypes . Затем функция CDLL из того
же модуля используется для загрузки C-библиотеки. Теперь функции из C-кода
доступны для нас через переменную adder . Когда мы вызываем adder.add_int() , то
автоматически вызывается C-функция add_int . Интерфейс модуля ctypes позволяет
использовать питоновские целые числа и строки при вызове C-функций.
Для других типов, например логического или действительных чисел, мы должны
использовать корректные ctypes .
Мы делаем это при передаче параметров в
adder.add_float() . Сначала мы создаём требуемый тип c_float из float , затем
используем в качестве аргумента для C-кода. Этот метод простой и аккуратный, но
ограниченный. Мы, к примеру, не можем оперировать объектами на стороне C-кода.
Пример замера времени#
Вариант начала файла Сpp:
#include <Python.h>
#include <iostream>
#include <cmath>
#include <ctime>
// Функция для вычисления синуса N раз и измерения времени выполнения
double calculateSinusNTimes(int N) {
clock_t start_time = clock(); // Запуск таймера
// Вычисление синуса N раз
for (int i = 0; i < N; ++i) {
double result = std::sin(0.5); // Вычисление синуса числа
// Здесь можно использовать другие значения для вычисления синуса
}
clock_t end_time = clock(); // Завершение таймера
double time_taken = double(end_time - start_time) / CLOCKS_PER_SEC; // Вычисление времени выполнения в секундах
return time_taken;
Вариант функции для Python
import time
import math
def calculate_sin_n_times(N):
start_time = time.time() # Запуск таймера
# Вычисление синуса N раз
for i in range(N):
result = math.sin(0.5) # Вычисление синуса числа
# Здесь можно использовать другие значения для вычисления синуса
end_time = time.time() # Завершение таймера
time_taken = end_time - start_time # Вычисление времени выполнения в секундах
return time_taken
Варианты#
Вариант | Метод | Задача |
---|---|---|
1 | CTypes | Сложение двух массивов |
2 | Python/C API | Вычитание двух массивов |
3 | CTypes | Умножение вектора на число |
4 | Python/C API | Деление вектора на число |
5 | CTypes | Поиск максимума в массиве |
6 | Python/C API | Поиск минимума в массиве |
7 | CTypes | Скалярное произведение двух векторов |
8 | Python/C API | Вычисление среднего арифметического по массиву |
9 | CTypes | Вычисление медианного значения массива |
10 | Python/C API | Вычисление математического ожидания |
11 | CTypes | Вычисление стандартного отклонения |
12 | Python/C API | Вычисление суммы элементов массива |
13 | CTypes | Реализовать функцию Distinct по заданному массиву, возвращающую список только различных элементов массива |
14 | Python/C API | Умножение вектора на число |
15 | CTypes | Вычисление среднего арифметического по массиву |
16 | CTypes | Вычисление математического ожидания |
17 | Python/C API | Вычисление медианного значения массива |
18 | CTypes | Вычисление суммы элементов массива |
19 | Python/C API | Вычисление стандартного отклонения |
20 | CTypes | Деление вектора на число |
21 | Python/C API | Вычисление длины вектора |
22 | CTypes | Создать нулевой массив заданного размера |
23 | Python/C API | Вычисление расстояния между векторами |
24 | CTypes | Вычисление длины вектора |
25 | Python/C API | Создать нулевой массив заданного размера |
26 | CTypes | Вычисление расстояния между векторами |
27 | Python/C API | Создать единичный массив заданного размера |
28 | CTypes | Подсчет количества четных значений в массиве |
29 | Python/C API | Реализовать функцию Distinct, по заданному массиву, возвращающую список только различных элементов массива |
30 | CTypes | Подсчет количества совпадающих элементов между двумя массивами |