Производительность языка программирования Python
Секция: Физико-математические науки
лауреатов
участников
лауреатов
участников
XXVII Студенческая международная научно-практическая конференция «Технические и математические науки. Студенческий научный форум»
Производительность языка программирования Python
Python – универсальный язык программирования, на котором можно создавать проекты под любые платформы. Создавайте веб-приложения, занимайтесь анализом данных, автоматизируйте задачи управления системами и многое другое. В данной статье рассматривается реализация интерпретатора CPython [1]. По мнению многих разработчиков, проекты Python работают во много раз медленнее, чем аналогичные программные решения на других языках. Попытаемся разобраться в этом. Рассмотрим основные особенности языка программирования Python, которые могут повлиять на скорость работы программы:
• Работа блокировки потоков GIL (Global Interpreter Lock, глобальная блокировка интерпретатора) [1].
• Python - интерпретирующий, а не компилируемый язык [3].
• Python - это язык с динамической типизацией [2].
Сначала поговорим о том, что такое GIL (Global Interpreter Lock). GIL тесно связан с функционированием потока. Современные компьютеры имеют многоядерные процессоры, а иногда даже являются многопроцессорными системами. Чтобы использовать все эти вычислительные мощности, операционная система использует низкоуровневые структуры, называемые потоками и процессами (процесс программы MATLAB), которые могут запускать несколько потоков и использовать их соответствующим образом. В результате, например, если конкретному процессу требуется много ресурсов процессора, его выполнение может быть распределено на несколько ядер. Благодаря этому методу у большинства приложений возрастает производительность при выполнении программы. Блокировки используются для многопоточных приложений. Ключевым является то, что они позволяют такой системе защищать память при одновременном обращении двух потоков (чтение или запись) к одной ячейке. В Python именно GIL занимается защитой памяти интерпретатора от разрушений. Реализует данную задачу он при помощи деления всех операций с памятью на атомарные.
Python - это интерпретируемый язык программирования. Часто приходится слышать, что низкая производительность Python связана с особенностью сборки проектов. Подобные утверждения основаны на грубом упрощении того, как, на самом деле, работает CPython. Если запустить любую программу с расширение .py, CPython начнёт длительную последовательность действий, которая заключается в чтении, лексическом анализе, парсинге, компиляции, интерпретации и выполнении кода скрипта. В качестве примера рассмотрим простейшую функцию умножения, которая будет принимать в качестве параметров 2 числа и возвращать их произведение. Функция, как и всё в Python, является объектом, и сделав её импорт, мы можем перечислить её методы с помощью функции dir(). Один из атрибутов функции - это __code__, в котором содержится code object - объект, который оборачивает написанный код. У этого объекта есть метод co_code, который возвращает байткод, сгенерированный Python для этой функции (то есть инструкции для интерпретатора Python). Чтобы привести байткод в читаемый вид (т.е. дизассемблировать), используется функция dis.dis() из пакета dis.
Рисунок 1. Выполнение команды dis.dis(multiply)
Это касается не только скриптов, которые мы пишем, но и импортированного кода сторонних модулей. В результате большую часть времени (если вы не пишите код, который выполняется только один раз), Python занимается выполнением готового байт-кода. На рисунке - 1 мы видим, что функция преобразуется в цикл из четырех инструкций. Каждая из этих инструкций содержит определенное действие. Первым является LOAD_FAST, который изменяет содержимое переменной в памяти и помещает ее в стек. Когда переменная ‘a’ помещается в стек, мы переключаемся на аналогичную команду для переменной ‘b’. Следующая инструкция, BINARY_MULTIPLY, говорит интерпретатору Python взять два верхних значения в стеке и умножить их. Обратите внимание, что мы можем передать значение любого типа в функцию: целое число, действительное число или строку, этот байт-код не меняет этого. Типизация объекта начинается, когда Python выполняет инструкцию. В этом случае, когда выполняется оператор BINARY_MULTIPLY, Python просматривает тип объекта и, например, если это два целых числа, умножает два целых числа. Далее результат умножения помещается на вершину стека. И последний оператор RETURN_VALUE, который возвращает переменную из верхней части стека. Это касается не только скриптов, которые мы пишем, но и импортированного кода сторонних модулей. В результате большую часть времени (если вы не напишите код, который выполняется только один раз) Python занимается выполнением готового байт-кода.
Python - это динамически типизированный язык программирования. В отличии от языков со статической типизацией, Python не нуждается в объявлении типа переменной при ее создании. Так же к языкам с динамической типизацией относят Objective-C, Ruby, PHP, Perl, JavaScript. В данных языках программирования понятие типов данных имеет то же значение что и в классическом С, но тип переменной является динамическим. Реализуется данный механизм следующим образом. Типы переменных неизвестны до того момента, когда у них есть конкретные значения при запуске. Проверка типов и их преобразование являются сложными операциями. Каждый раз, когда к переменной обращаются, читают или записывают значение, выполняется проверка типа. Из-за чего существенно страдает скорость работы приложений.
В качестве вывода хочется отметить, что причиной низкой производительности Python является его динамическая составляющая, а также универсальность. Изначально математиком Гвидо ван Россум этоn язык программирования был задуман как простой и выразительный язык. Создатель Python хотел представить всему миру универсальный инструмент, который позволит избежать сложных структур кодирования. Он хотел реализовать язык программирования, в котором код программы будет читаться, как обычный английский язык.