Русский
Русский
English
Статистика
Реклама

Cython

Любовь. Python. C. Доклад Яндекса

29.01.2021 10:18:54 | Автор: admin
Что связывает языки Python и C++? Как извлечь из этого выгоду лично для себя? На большой конференции Pytup Александр Букин показал способы, благодаря которым можно оптимизировать свой код, а также выбирать и эффективно использовать сторонние библиотеки.

Всем привет, меня зовут Александр Букин, я разрабатываю Яндекс.Погоду. Вы еще можете знать меня как сооснователя Pytup. Также я состою в программных комитетах таких классных конференций, как PyCon.ru и YaTalks.

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

Дисклеймер: название доклада это отсылка к прекрасному сериалу Любовь. Смерть. Роботы на Netflix. Всем очень советую.

Чтобы разобраться в хитром сплетении двух языков, нам придется вернуться в прошлое. Давным-давно в одной не очень для нас далекой галактике родился герой.



Этот герой был силен, он не по годам хорошо осваивал силу. У него было много мидихлориан. А еще все знали, что этот парень очень быстрый.



Он любил скорость. И, конечно, он рос, развивался, и с годами у него стало появляться больше возможностей, способностей. Он обрастал библиотеками. Его мощь росла. Но в какой-то момент, не очень далекий, он понял, что ему хочется большего, что он стоит на месте, что нужно двигаться. И для этого он решил познать другую сторону, познать мощь ООП и улучшенные стандарты библиотеки. Познав эту мощь, он переродился и стал сильнее, и все мы его узнали как C++.



Но незадолго до этого он успел дать жизнь новому прекрасному герою, нашему любимому Python.



Как мы знаем, их родство C и Python прямое. И Python какое-то время был разделен с отцом. Он развивался немного отдельно, хотя отец, конечно, оказывал на него большое влияние. И не все у них хорошо складывалось. Иногда ему даже было сложно принять, что C++ его папа. Он не всегда с этим хорошо мирился. Шло время, и ему все-таки удалось найти общий язык с отцом. Объединив усилия, найдя эту любовь, Python и C++ вместе одолели зло во вселенной. Их общая мощь оказалась так велика, что никто не мог удержать их.



Я считаю, это прекрасно. Давайте поймем, почему для нас, для разработчиков, это может быть полезно, а не только интересно. Как я и упомянул, C++ всегда был очень быстр, а Python не всегда. Скорее он даже прославился тем, что его производительность в важных задачах иногда страдает. Это как раз то, чем C++ может помочь ускорить ваш код, некоторые части, которые вы в своих сервисах вызываете действительно часто. Давайте выясним, как это сделать.

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



Так как Python написан на C, у него есть довольно большое количество способов интеграции с ним. Сегодня мы рассмотрим два из них Cython и Native Extension на C/C++. Подробнее остановимся именно на нативных расширениях. Да, там есть еще подмножество. Но, во-первых, эти два мои любимые. Во-вторых, целого доклада не хватит рассказывать про каждый из них.

Небольшое введение про Cython. Это довольно популярная технология: например, gevent написан с помощью него. Он легко интегрируется в сборку проекта, именно поэтому он мне нравится. У Cython достаточно хорошая документация. Еще для разработчика есть полезная вещь cythonize. Это оборачивание вашей функции вызов этой функции, который в среднем на синтетических тестах даже дает ускорение в выполнении кода до двух раз.


Ссылка со слайда

Понятно, что есть и минусы. Во-первых, хотя Cython, в отличие от других способов, предлагает вам писать на близком к CPython синтаксисе, все равно в мелочах они отличаются. И иногда это мешает. Во-вторых, когда вам всё-таки приходится переключаться на C, его необходимо знать. Но здесь, кстати, необязательно знать его всегда. Конечно же, присутствует наш любимый Segmentation Fault, который можно словить, если плохо поработать с памятью уже в сыром C. Из этого же растут ноги сложностей в дебаггинге. Но если вам не хочется очень глубоко погружаться в C, а хочется попробовать ускорить свою технологию прямо сейчас, Cython хороший выбор.

Но мы, как говорится, верим в абсолют, поэтому абсолютная сила это абсолютная скорость. И мы хотим сильно ускорить выполнение, поэтому полезем внутрь.


Ссылка со слайда

Полезть внутрь это как раз использовать нативный Extension. Несмотря на то, что это не самая простая технология, она родная. Ее документация есть прямо в доке Python. Она тоже легко интегрируется в сборку проекта и позволяет создавать свои типы данных. Здесь вам уже никак не обойти необходимость знания C и написания на нем кода. Здесь придется, если вы будете делать что-то сложное, поработать с памятью и поотлаживаться.



Маленький пример. Вот минимальный файл spammodule.c, в котором описывается, как ни странно, модуль спама. Как мы видим, мы подключаем заголовочный файл include Python.h, который понадобится нам для любого модуля. И описываем наш модуль. Говорим, какое у него будет имя, и описываем функцию его инициализации PyInit_spam. Дальше вызываем PyModule_Create, который либо возвращает null, либо возвращает модуль.



Чтобы все это заработало, необходимо всё-таки сбилдить наш модуль. Для этого можно воспользоваться setuptools. Мы пишем небольшой setup.py, в котором указываем, что нам нужен Extension. Говорим, как его называть и откуда брать исходники. Запускаем setup.py build, setup.py install. Можно импортить, можно использовать.

Это пример для C. Пример для С++ выглядит очень похоже.



Просто пишем исходник на плюсах, добавляем sources spammodule.cpp, и указываем, что язык у нас тоже С++. Поздравляю, вы прекрасны. Всего лишь нужно в разделе вашего файла .c или .cpp написать валидный, хорошо работающий, правильно интерпретирующий работу с памятью код на C и на плюсах. Возможно, вы к этому не очень готовы. Может быть, вам просто лень это делать, или вы думаете: я же разработчик, а разработчик не делает своих велосипедов. Наверное, раз это такой родной и давно используемый механизм, уже есть кто-то, кто это сделал. И да, уже есть.

Давайте посмотрим парочку примеров. Например, есть ujson.



Что мы делаем часто, как все разработчики? Перекладываем джейсончики.

Эта библиотека используется довольно легко. У нее есть стандартные функции dumps и loads. И внутри она реализована как раз с помощью Extension. Там парочка C-файлов и оберточка сверху.

Давайте не будем верить мне на слово, что простое добавление C-кода ускоряет файлики. Давайте это побенчмаркаем, посериализуем, десериализуем их. Все измерения, которые я дальше буду показывать, в миллисекундах.

Для начала возьмем не очень большой файл, который возвращает API Twitter, JSON, размером 600 килобайт.


Ссылка со слайда

Я взял за основу две очень популярные Python-библиотеки: json и simplejson. Мы получаем, что сериализация и десериализация в районе 3,5 миллисекунд у json и 3,15 у simplejson. Выглядит довольно быстро. Как вы думаете, за сколько это сделает ujson? Допустим, он сделает это за 3 секунды ровно. Может быть, за 2,9. Но на самом деле прирост будет больше. Я добавил ссылку на бенчмарк, и сериализация заняла практически 2 миллисекунды. Как мы видим, прирост в полтора раза, довольно неплохо. Но конечно, хотелось бы большего.


Ссылка со слайда

Возьмем файлик побольше canada.json. Это файл с геоточками на 2 мегабайта. Видим, что тот же simplejson уже работает не так однозначно, ему потребовалось целых 80 миллисекунд на сериализацию. json немножко получше. Но ujson здесь вырывается вперед гораздо сильнее на большом количестве данных, и мы уже получаем прирост в четыре с половиной раза относительно simplejson с сериализацией и в три раза относительно json. Отличный результат.

Я дам вам насладиться этой скоростью. Но есть и ложка дегтя. Понятно, что библиотека ujson не во всем хороша с точки зрения совместимости. Она не поддерживает все типы данных, которые можно сериализовать. Если вы найдете на ее GitHub, то увидите, что там довольно много issues, иногда есть утечки памяти, иногда ошибки. Надо помнить, что ничто не идеально, и смотреть, подходит ли данная библиотека для вашего конкретного варианта.

Но если у вас где-то сериализуется json и ограничения этой библиотеки вам подходят, попробуйте. Возможно, вы будете приятно удивлены.



Посмотрим на задачу, которая часто встречается и которая в Python исторически не очень быстро работает, парсинг дат. Есть такая библиотека ciso8601. Она тоже написана с помощью C Extension. Вот так выглядит ее использование. Довольно просто. Есть функция parse_datetime, в которой вы передаете строчку в одном из поддерживаемых форматов. Это парсится в стандартный datetime object. Даже поддерживает тайм-зоны.

Давайте тоже побенчмаркаем. Парсить мы будем вот такую строчку: 2014-01-09T21:48:00. Все измерения, которые получим, будут в микросекундах.



Здесь добавилась еще версия Python. Будет интересно посмотреть на разных версиях, как оно работает. Я взял за основу python-dateutil, который фактически является расширенной стандартной библиотекой datetime, и популярный, но написанный на Python str2date.

python-dateutil на версии Python 3.8 делает это за 122 микросекунды видите, необычно, что он чуть замедлился относительно 3.7. Гораздо быстрее, на порядок, делает это str2date. Что же может нам предложить ciso? Наверное, будет одна микросекунда.


Ссылка со слайда

На самом деле будет меньше одной микросекунды, очень быстро. И даже в одном из худших случаев с версией 3.8 это опережает оригинальный datetime парсер в 600 раз. Если мы возьмем версию 2.7, это будет практически в 1000 раз быстрее.

Это уже производит гораздо более сильное впечатление, чем наш предыдущий бенчмарк с json. И мы задаемся вопросом: наверное, что-то не так, что-то в datetime работает нехорошо. На самом деле нет. Просто у этой библиотеки тоже есть свои минусы с точки зрения поддержки форматов и дебаггинга, если что-то пошло не так.

Но давайте взглянем на нее чуть подробнее как на хороший пример C Extension. Вот небольшой проектик. Тут много файликов, но самый важный module.c. В этом файлике находится весь код этой библиотеки. Это всего один файлик, и он всего на 586 строчек. Второй важный файлик setup.py. Помните, мы вначале рассматривали spammodule.c. Это точно такая же схема. Есть один C-файлик и один setup.py. Как мы видим, внутри он, конечно, поразвернутее, но вот эта строчка Дай мне, пожалуйста, Extension, обзови его вот так, возьми у него исходники присутствует и здесь.



Ребята большие молодцы, что не стали усложнять. Это помогает. Так мы затронули то, что, как говорится, можно сделать одной левой. Вы уже сейчас можете просто взять, поменять пару строчек в своем коде, там, где работаете с датами с json, и получить прирост. Я считаю, это замечательно.

Если же ваша задача не настолько простая, ваша функциональность, которая вызывается часто и дает большой прирост, не настолько большая возможно, стоит попробовать написать самому. Но так как интеграция двух языков усложняет жизнь, я все-таки советую не забывать делать это максимально атомарными вещами и понемножку, чтобы это была маленькая функция.

Немножко о том, где еще можно узнать, что такое расширение для Python, почему они хороши и как его ускорить. В 2018 году у нас на Pytup выступал Костя Гуков, который рассказывал про расширение на Rust. Там он показывал расширения, которые тоже парсят даты. Забегая вперед, скажу, что они медленнее, чем написанные на C, но тоже очень быстрые. Антон Патрушев на PyCon тоже рассказывал про расширение на Rust:

Смотреть видео

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

Давайте подведем небольшие итоги:

  • Не забывайте оптимизировать свой код на Python. Это первое, что нужно делать всегда, когда кажется, что что-то может происходить быстрее.
  • Если вы провели оптимизацию, попробуйте сторонние библиотеки, которые содержат написанное с помощью C и C++ Extension или с помощью Rust. Возвращаясь немного назад, ujson не самая быстрая библиотека, есть быстрее: orjson, njson. Попробуйте их.
  • И если ваша задача довольно узкая или вам хочется сделать что-то свое, пишите свои расширения, изучайте новые языки для этих расширений. Развивайтесь.

May the Force be with you, друзья.

Подробнее..

Python amp оптимизация времени и памяти

07.04.2021 00:23:14 | Автор: admin

Введение

Зачастую скорость выполнения python оставляет желать лучшего. Некоторые отказываются от использования python именно по этой причине, но существует несколько способов оптимизировать код python как по времени, так и по используемой памяти.

Хотелось бы поделиться несколькими методами, которые помогают в реальных задачах. Я пользуюсь win10 x64.

Экономим память силами Python

В качестве примера рассмотрим вполне реальный пример. Пусть у нас имеется некоторый магазин в котором есть список товаров. Вот нам понадобилось поработать с этими товарами. Самый хороший вариант, когда все товары хранятся в БД, но вдруг что-то пошло не так, и мы решили выгрузить все товары в память, дабы обработать их. И тут встает резонный вопрос, а хватит ли нам памяти для работы с таким количеством товаров?

Давайте первым делом создадим некий класс, отвечающий за наш магазин. У него будет лишь 2 поля: name и listGoods, которые отвечают за название магазина и список товаров соответственно.

class ShopClass:    def __init__(self, name=""):        self.name = name        self.listGoods = []

Теперь мы хотим наполнить магазин товарами (а именно заполнить поле listGoods). Для этого создадим класс, отвечающий за информацию об одном товаре (я использую dataclassы для таких примеров).

# если ругается на dataclass, то делайте # pip install dataclasses# затем в коде вызывайте импорт# from dataclasses import dataclass @dataclassclass DataGoods:    name:str    price:int    unit:str

Далее необходимо заполнить наш магазин товарами. Для чистоты эксперимента я создам по 200 одинаковых товаров в 3х категориях:

shop = ShopClass("MyShop")for _ in range(200):    shop.listGoods.extend([        DataGoods("телефон", 20000, "RUB"),        DataGoods("телевизор", 45000, "RUB"),        DataGoods("тостер", 2000, "RUB")    ])

Теперь пришло время измерить память, которую занимает наш магазин в оперативке (для измерения памяти я использую модуль pympler):

from pympler import asizeofprint("Размер магазина:", asizeof.asizeof(shop))>>> Размер магазина: 106648

Получается, что наш магазин в оперативке занял почти 106Кб. Да, это не так много, но если учесть, что я сохранил лишь 600 товаров, заполнив в них только информацию о наименовании, цене и валюте, в реальной задаче придется хранить в несколько раз больше полей. Например, можно хранить артикуль, производителя, количество товара на складе, страну производителя, цвет модели, вес и много других параметров. Все эти данные могут раздуть ваш магазин с нескольких килобайт до нескольких сотен мегабайт (и это при условии, что данные еще даже не начинали обрабатываться).

Теперь перейдем к решению данной проблемы. Python создает новый объект таким образом, что под него выделяется очень много информации, о которой мы даже не догадываемся. Надо понимать, что python создает объект __dict__ внутри класса для того, чтобы можно было добавлять новые атрибуты и удалять уже имеющиеся без особых усилий и последствий. Посмотрим, как можно динамически добавлять новые атрибуты в класс.

shop = ShopClass("MyShop")print(shop.__dict__)  >>> {'name': 'MyShop', 'listGoods': []}shop.city = "Москва"print(shop.__dict__) >>> {'name': 'MyShop', 'listGoods': [], 'city': 'Москва'}

Однако в нашем примере это абсолютно не играет никакой роли. Мы уже заранее знаем, какие атрибуты должны быть у нас. В pythone есть магический атрибут __slots__, который позволяет отказаться от __dict__. Отказ от __dict__ приведет к тому, что для новых классов не будет создаваться словарь со всеми атрибутами и хранимым в них данными, по итогу объем занимаемой памяти должен будет уменьшиться. Изменим немного наши классы:

class ShopClass:    __slots__ = ("name", "listGoods")    def __init__(self, name=""):        self.name = name        self.listGoods = []@dataclassclass DataGoods:    __slots__ = ("name", "price", "unit")    name:str    price:int    unit:str

И протестируем по памяти наш магазин.

from pympler import asizeofprint("Размер магазина:", asizeof.asizeof(shop))>>> Размер магазина: 43904

Как видно, объем, занимаемый магазином, уменьшился почти в 2.4 раза (значение может варьироваться в зависимости от операционной системы,версии Python, значений и других факторов). У нас получилось оптимизировать занимаемый объем памяти, добавив всего пару строчек кода. Но у такого подхода есть и минусы, например, если вы захотите добавить новый атрибут, вы получите ошибку:

shop = ShopClass("MyShop")shop.city = "Москва">>> AttributeError: 'ShopClass' object has no attribute 'city'

На этом преимущества использования слотов не заканчиваются, из-за того, что мы избавились от атрибута __dict__ теперь ptyhon'у нет необходимости заполнять словарь каждого класса, что влияет и на скорость работы алгоритма. Протестируем наш код при помощи модуля timeit, первый раз протестируем наш код на отключенных __slots__ (включенном__dict__):

import timeitcode = """class ShopClass:    #__slots__ = ("name", "listGoods")    def __init__(self, name=""):        self.name = name        self.listGoods = []@dataclassclass DataGoods:    #__slots__ = ("name", "price", "unit")    name:str    price:int    unit:strshop = ShopClass("MyShop")for _ in range(200):    shop.listGoods.extend([        DataGoods("телефон", 20000, "RUB"),        DataGoods("телевизор", 45000, "RUB"),        DataGoods("тостер", 2000, "RUB")])"""print(timeit.timeit(code, number=60000))>>> 33.4812513

Теперь включим __slots__ (#__slots__ = ("name", "price", "unit") -> __slots__ = ("name", "price", "unit") и # __slots__ = ("name", "listGoods") -> __slots__ = ("name", "listGoods")):

# включили __slots__ в коде вышеprint(timeit.timeit(code, number=60000))>>> 28.535005599999998

Результат оказался более чем удовлетворительным, получилось ускорить код примерно на 15% (тестирование проводилось несколько раз, результат был всегда примерно одинаковый).

Таким образом, у нас получилось не только уменьшить объем памяти, занимаемой программой, но и ускорить наш код.

Пытаемся ускорить код

Способов ускорить python существует несколько, начиная от использования встроенных фишек язык (например, описанных в прошлой главе), заканчивая написанием расширений на C/C++ и других языках.

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

Cython

На мой взгляд Cython является отличным решением, если вы хотите писать код на Python, но при этом вам важна скорость выполнения кода. Реализуем код для подсчета сумм стоимости всех телевизоров, телефонов и тостеров на чистом Python и рассчитаем время, которое было затрачено (будем создавать 20.000.000 товаров):

import timeclass ShopClass:   __slots__ = ("name", "listGoods")   def __init__(self, name=""):      self.name = name      self.listGoods = []@dataclassclass DataGoods:   __slots__ = ("name", "price", "unit")   name: str   price: int   unit: strshop = ShopClass("MyShop")t = time.time()for _ in range(200*100000):   shop.listGoods.extend([      DataGoods("телефон", 20000, "RUB"),      DataGoods("телевизор", 45000, "RUB"),      DataGoods("тостер", 2000, "RUB")   ])print("СОЗДАЕМ ТОВАР НА PYTHON:", time.time()-t)>>> СОЗДАЕМ ТОВАР НА PYTHON: 44.49887752532959telephoneSum, televizorSum, tosterSum = 0, 0, 0t = time.time()for goods in shop.listGoods:   if goods.name == "телефон":      telephoneSum += goods.price   elif goods.name == "телевизор":      televizorSum += goods.price   elif goods.name == "тостер":      tosterSum += goods.priceprint("ВРЕМЯ НА ПОДСЧЁТ СУММ PYTHON:", time.time() - t)>>> ВРЕМЯ НА ПОДСЧЁТ СУММ PYTHON: 13.135360717773438

Как мы видим, время обработки весьма неутешительно. Теперь приступим к использованию cython. Для начала ставим библиотеку cython_npm (см. официальный гитхаб): pip install cython-npm. Теперь создадим новую папку в нашем проекте, назовем её cython_code и в ней создадим файл cython_data.pyx (программы cython пишутся с расширением .pyx).

Перепишем класс магазина под cython:

cdef class CythonShopClass:   cdef str name   cdef list listGoods   def __init__(self, str name):       self.name = name       self.listGoods = []

В cython необходимо строго типизировать каждую переменную, которую вы используете в коде (это не обязательно, но если этого не делать, то уменьшения по времени не будет). Для этого необходимо писать cdef <тип данных> <название переменной> в каждом классе или методе. Реализуем остальной код на cython. Функцию my_def() реализуем без cdef, а с привычным нам def, так как её мы будем вызывать из основного python файла. Также в начале нашего файла .pyx необходимо прописать версию языка (# cython: language_level=3).

# cython: language_level=3# на забывает вставить код класса магазинаcdef class CythonDataGoods:   cdef str name   cdef int price   cdef str unit   def __init__(self, str name, int price, str unit):       self.name = name       self.price = price       self.unit = unitcdef int c_testFunc():    cdef CythonShopClass shop    cdef CythonDataGoods goods    cdef int i, t, telephoneSum, televizorSum, tosterSum    size, i, telephoneSum, televizorSum, tosterSum = 0, 0, 0, 0, 0    shop = CythonShopClass("MyShop")    t = time.time()    for i in range(200*100000):       shop.listGoods.extend([           CythonDataGoods("телефон", 20000, "RUB"),           CythonDataGoods("телевизор", 45000, "RUB"),           CythonDataGoods("тостер", 2000, "RUB")       ])    print("СОЗДАЕМ ТОВАР НА CYTHON:", time.time()-t)    t = time.time()    for goods in shop.listGoods:        if goods.name == "телефон":            telephoneSum += goods.price        elif goods.name == "телевизор":            televizorSum += goods.price        elif goods.name == "тостер":            tosterSum += goods.price    print("ВРЕМЯ НА ПОДСЧЁТ СУММ CYTHON:", time.time() - t)    return 0def my_def():    data = c_testFunc()    return data

Теперь в main.py нашего проекта сделаем вызов cython кода. Для этого делаем сначала импорт всех установленных библиотек:

from cython_npm.cythoncompile import exportfrom cython_npm.cythoncompile import installimport time

И делаем сразу же компиляцию нашего cython и его импорт в основной python код

export('cython_code/cython_data.pyx')import cython_code.cython_data as cython_data

Теперь необходимо вызвать код cython

if __name__ == "__main__":   a = cython_data.my_def()

Запускаем. Обратим внимание, что было выведено в консоли. В cython, где мы делали вывод времени на создание товаров, мы получили:

>>> СОЗДАЕМ ТОВАР НА CYTHON: 4.082242012023926

А там где был вывод после подсчета сумм получили:

>>> ВРЕМЯ НА ПОДСЧЁТ СУММ CYTHON: 1.0513946056365967

Как мы видим, скорость создания товаров сократилась с 44 до 4 секунд, то есть мы ускорили данную часть кода почти в 11 раз. При подсчете сумм время сократилось с 13 секунд до 1 секунды, примерно в 13 раз.

Таким образом, cython - это один самых простых способов для того, чтобы ускорить свою программу в несколько раз, он также подойдет для тех, кто придерживается типизации данных в коде. Стоит также отметить, что время прироста скорости зависит от задачи, при решении некоторых задач cython может ускорить ваш код до 100 раз.

Магия Python

Конечно, использование сторонних надстроек или модулей для ускорения - это хорошо, но также стоит оптимизировать свои алгоритмы. Например, ускорим часть кода, где идет добавление новых товаров в список магазина. Для этого напишем лямбда функцию, которая будет возвращать список параметров, которые нужны для нового товара. Также будем пользоваться генератором списков:

shop = ShopClass("MyShop")t = time.time()getGoods = lambda index: {0: ("телефон", 20000, "RUB"),                           1: ("телевизор", 45000, "RUB"),                           2:("тостер", 2000, "RUB")}.get(index) shop.listGoods = [DataGoods(*getGoods(i%3)) for i in range(200*100000)]print("СОЗДАЕМ ТОВАР НА PYTHON:", time.time()-t)>>>  СОЗДАЕМ ТОВАР НА PYTHON: 19.719463109970093

Скорость увеличилась примерно в 2 раза, при этом мы пользовались силами самого python. Генераторы в python - очень удобная вещь, они позволяют не только ускорить ваш код, но и оптимизировать его по используемой памяти.

PyPy

Бывает так, что нет возможности переписать код на cython или другой язык, потому что уже имеется достаточно большая кодовая база (или по другой причине), а скорость выполнения программы хочется увеличить. Рассмотрим код из прошлого примера, где мы использовали лямба функции и генератор списков. Тут на помощь может прийти PyPy, это интерпретатор языка python, использующий JIT компилятор. Однако PyPy поддерживает не все сторонние библиотеки, если вы используете в коде таковые, то изучите подробнее документацию. Выполнить python код при помощи PyPy очень легко.

Для начала качаем PyPy с официального сайта. Распаковываем в любую папку, открываем cmd и заходим в папку, где теперь лежит файл pypy3.exe, в эту же папку положим наш код с программой. Теперь в cmd пропишем следующую команду:

Таким образом, 19 секунд pythonовского кода из прошлого примера у нас получилось сократить до 4.5 секунд вообще без переписывания кода, то есть почти в 4 раза.

Вывод

Мы рассмотрели несколько вариантов оптимизации кода по времени и памяти. На зло всем хейтерам, которые говорят, что python медленный, мы смогли достичь ускорения кода в десятки раз.

Были рассмотрели не все возможные варианты ускорения кода. В некоторых случаях можно использовать Numba, NumPy, Nim или multiprocessing. Все зависит от того, какую задачу вы решаете. Некоторые задачи будет проще решать на других языках, так как python не способен решить всё на этом свете.

Прежде чем приступить к выбору функционала для оптимизации кода необходимо провести внутреннюю оптимизацию кода на чистом python, по максимуму избавиться от циклов в циклах в циклах в цикле, очищать руками память и удалять ненужные элементы по ходу выполнения кода. Не стоит ожидать, что переписав ваш код на другой язык - это решит все ваши проблемы, учитесь искать узкие места в коде и оптимизировать их алгоритмически или при помощи фишек самого языка.

Подробнее..

Немного Сythonа

04.09.2020 00:21:28 | Автор: admin


Дошли руки до Cythona, спасибо самоизоляции. Проблема прозаична как ускориться на python с минимальными потерями в синтаксисе. Один из подходов использование Сython (сместь С и python).
Не давала покоя публикация с громким названием отсюда habr.com/ru/company/ruvds/blog/462487
Но из содержания публикации мало что можно вынести, так как формулы и результирующая таблица неверны. Попробуем дополнить картину, начатую авторами поста и расставим точки над и.

*Тесты проводились на odroid xu4, ubuntu mate, python 2.7.17.
Cython ставится просто (pip install cython).

Будем мучить все те же числа Фибоначчи.
Создадим файлы для тестов прироста производительности.
Для языка python (test.py):
def test(n):   a, b = 0.0, 1.0   for i in range(n):      a, b = a + b, a   print (a)


Для языка cython(test2.pyx):
def test2(int n):   cdef int i   cdef double a=0.0, b=1.0   for i in range(n):      a, b = a + b, a   print (a)


Файл cython требует предварительной сборки.
Для него создадим setup.py c содержимым:
from distutils.core import setupfrom Cython.Build import cythonizesetup(ext_modules=cythonize('test2.pyx'))


И соберем:
python setup.py build_ext --inplace


Теперь возьмем файл из упомянутого поста с тестами и немного его поправим, добавив возможность вводить собственное число на старте (tests.py):
import testimport test2import timenumber = input('enter number: ')start = time.time()test.test(number)end =  time.time()py_time = end - startprint("Python time = {}".format(py_time))start = time.time()test2.test(number)end =  time.time()cy_time = end - startprint("Cython time = {}".format(cy_time))print("Speedup = {}".format(py_time / cy_time))


Посмотрим, что получилось:
python tests.py


Результаты:

Числа в вычислениях совпадают.
Прирост скорости ощутим, хотя и не такой заоблачный, как ожидалось.

*Можно обойтись без сборки test2.pyx с использованием setup.py, для этого необходимо просто в файл tests.py добавить строки:
import pyximportpyximport.install()

Теперь test2.pyx будет собираться на лету при каждом запуске tests.py, а файлов в папке будет меньше.
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru