Python способы оптимизации памяти

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

Введение

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

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

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

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

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

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

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

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

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

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

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

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

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

Читайте также:  Виды способа контроля по сварке

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

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

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

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

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

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

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

Cython

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

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

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

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

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

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

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

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

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

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

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

Магия Python

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

Читайте также:  Все способы восстановления аккаунта гугл

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

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

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

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

Вывод

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

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

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

Источник

Оптимизация по времени и памяти кода на Python3

Есть задача в условии которой есть ограничения Время: 1 сек. Память: 16 Мб .
Ссылка на задачу

Условие:

Задана последовательность целых чисел. Числа нумеруются по порядку следования, начиная с единицы.

Требуется написать программу, которая найдет сумму максимума из чисел с четными номерами и минимума из чисел с нечетными номерами – max+min.

Есть 2 варианта кода для решения этой задачи (на самом деле их больше но эти ближе остальных к выполнению требований):

Код раз:

Время: 0,093 — Память: 17 Мб

Код два:

Время: 1,218 — Память: 8,3 Мб

Вопрос:

Есть-ли какие-нибудь способы оптимизации по использованию ресурсов?

2 ответа 2

Вот мое решение:

Время: 0,343

Память: 5,8 Мб

Оптимизация через открытие файла в бинарном режиме чтения:

Время: 0,296

Память: 4178 Кб

Оптимизация через использование модуля mmap :

Время: 0,296

Память: 1934 Кб

Мне все-время нехватало памяти, когда делал split по строчке с числами ( num_set = f.read().split() ), поэтому решил извернуться и сделать итератор чисел. Сразу что пришло в голову — finditer из модуля регулярок.

А т.к. по заданию четные и нечетные числа идут друг за другом, то посчитал, что используя next можно будет запрашивать следующие друг за другом значения пока они не закончатся ( StopIteration )

Читайте также:  Premium keratin change ampoule floland способ применения

Открытие ‘INPUT.TXT’ в бинарном режиме уменьшает итоговое время и потребление памяти: 0,343 -> 0,296 и 5,8 Мб -> 4178 Кб.

Результаты по времени и памяти могут немного отличаться от запуска к запуску в той проверяющей системе.

Этот же самый код совместим с PyPy и на PyPy будет быстрее выполняться: 0,296 -> 0,203 и 1934 Кб -> 1753 Кб

Источник

Как уменьшить использование памяти и ускорить работу кода на Python с помощью генераторов

Всем привет. Сегодня хотим поделиться одним полезным переводом, подготовленным в преддверии запуска курса «Web-разработчик на Python». Писать код эффективный по времени и по памяти на Python особенно важно, когда занимаешься созданием Web-приложения, модели машинного обучения или занимаешься тестированием.

Когда я начал изучать генераторы в Python, я понятия не имел насколько они важны. Однако они постоянно помогали мне при написании функций на протяжении всего моего путешествия по машинному обучению.

Функции-генераторы позволяют объявить функцию, которая будет вести себя как итератор. Они позволяют программистам создавать быстрые, простые и чистые итераторы. Итератор – это объект, который может быть повторен (зациклен). Он используется для того, чтобы абстрагировать контейнер данных и заставить его вести себя как итерируемый объект. Например, примером итерируемого объекта могут быть строки, списки и словари.

Генератор выглядит как функция, но использует вместо return ключевое слово yield. Давайте рассмотрим пример, чтобы стало понятнее.

Это функция-генератор. Когда вы ее вызываете, она возвращает объект-генератор.

Важно обратить внимание на то, как состояние инкапсулируется в теле функции генератора. Вы можете итерироваться по одному, используя встроенную функцию next():

Что произойдет, если вы вызовете next() после окончания выполнения?

StopIteration – это встроенный тип исключения, которое возникает автоматически, как только генератор перестает возвращать результат. Это сигнал остановки для цикла for.

Оператор yield

Его основная задача – управлять потоком функции генератора так, чтобы это было похоже на оператор return. При вызове функции генератора или использовании выражения генератора он возвращает специальный итератор, который называется генератором. Чтобы использовать генератор, присвойте его какой-либо переменной. При вызове специальных методов в генераторе, таких как next(), код функции будет выполняться до yield.

При попадании в инструкцию yield, программа приостанавливает выполнение функции и возвращает полученное значение объекту, который инициировал выполнение. (Тогда как return прекращает выполнение функции полностью.) Когда работа функции приостанавливается, ее состояние сохраняется.

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

Постановка проблемы

Предположим, нам нужно пройтись по большому списку чисел (например, 100000000) и сохранить квадраты всех чисел, которые нужно хранить отдельно в другом списке.

Обычный подход

После запуска кода выше, мы получим следующее:

С использованием генераторов

После запуска кода выше, мы получим следующее:

Как мы видим, время выполнения и затраченная память значительно сократились. Генераторы работают по принципу, известному как «ленивые вычисления». Это значит, что они могут экономить ресурсы процессора, памяти и других вычислительных ресурсов.

Заключение

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

Источник

Оцените статью
Разные способы