Как оптимизировать программу на python

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

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

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

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

Вот Почему Твой Код — Говно | Python PEP-8

Как видно из приведенного выше рисунка, в разных случаях python в несколько или несколько десятков раз медленнее, чем C ++.

Вычислительная эффективность Python низкая. Каковы конкретные причины? Вот некоторые

Тип объекта, на который указывает переменная, определяется во время выполнения, и компилятор не может делать никаких прогнозов, поэтому его нельзя оптимизировать. Приведите простой пример: r = a + b. A и b складываются вместе, но типы a и b известны только во время выполнения. Для операций сложения разные типы имеют разную обработку, поэтому каждый раз при запуске он будет оценивать типы a и b, а затем выполнять соответствующую операцию . В статических языках, таких как C ++, код времени выполнения определяется при компиляции.

Другой пример — поиск по атрибутам. Конкретный порядок поиска подробно описан в разделе «Поиск по атрибутам Python». Короче говоря, доступ к определенному свойству объекта — очень сложный процесс, и объекты Python, доступ к которым осуществляется через одну и ту же переменную, могут быть разными (см. Пример свойства Lazy). В языке C для доступа к атрибуту можно использовать адрес объекта плюс смещение атрибута.

  • Python интерпретируется исполнение

Но он не поддерживает JIT (как раз вовремя компилятор). Хотя знаменитый гугл однажды попробовал проект Unladen Swallow, в конце концов он сломался.

Каждый объект должен поддерживать счетчик ссылок, что добавляет дополнительную работу.

GIL — наиболее критикуемый пункт Python, потому что GIL, многопоточность в Python не может быть действительно параллельной. Если это связано с бизнес-сценарием с привязкой к вводу-выводу, проблема невелика, но в сценарии с привязкой к ЦП она фатальна. Поэтому я не использую многопоточность на Python в своей работе. Обычно я использую многопроцессорность (предварительная вилка) или добавляю сопрограммы. Даже в одном потоке GIL будет иметь большое влияние на производительность, потому что python будет пытаться переключать потоки каждый раз, когда он выполняет 100 кодов операций (по умолчанию, может быть установлен sys.setcheckinterval ()), конкретный исходный код находится в ceval.c :: PyEval_EvalFrameEx.

⚡ УСКОРЯЕМ PYTHON в 20 РАЗ! | Новый способ :3

Это может быть общей проблемой для всех языков программирования со сборкой мусора. Python использует стратегии маркировки и генерации сборки мусора, каждый раз, когда сборка мусора прерывает выполнение программы (останавливает мир), вызывая так называемое зависание. В infoq есть статья о том, что после отключения механизма сборщика мусора Python производительность Instagram увеличилась на 10%. Заинтересованные читатели могут его внимательно прочитать.

Код Pythonic

Все мы знаем, что преждевременная оптимизация — это источник зла, и все оптимизации должны основываться на профиле.

Однако, как разработчик python, вы должны быть на языке Pythonic, а код на языке Python часто более эффективен, чем код без кода Python, например:

  • Используйте итератор, например:
    iteritems dict вместо элементов (так же, как itervalues, iterkeys)
    Используйте генератор, особенно в случае преждевременного прерывания цикла
  • Определите, является ли это тот же объект, используя is вместо ==
  • Чтобы определить, находится ли объект в коллекции, используйте набор вместо списка
  • Используя функцию оценки короткого замыкания, напишите логическое выражение вероятности «короткого замыкания» спереди. Возможны и другие ленивые идеи
  • Для накопления большого количества строк используйте операцию соединения
  • Используйте синтаксис for else (while else)
  • Для обмена значениями двух переменных используйте: a, b = b, a
Читайте также:
Как русифицировать программу hds

Оптимизация по профилю

Даже если наш код уже очень питонический, он может быть не таким эффективным, как ожидалось. Мы также знаем закон 80/20. Большую часть времени мы проводим в небольшом количестве фрагментов кода. Ключ к оптимизации — найти эти узкие места. Есть много способов: добавить журнал повсюду, чтобы распечатать временную метку, или использовать timeit для проверки подозреваемой функции отдельно, но наиболее эффективным является использование инструмента профиля.

▍ 1. python profilers

Для программ на Python есть три хорошо известных инструмента профилирования: profile, cprofile и hotshot.

Среди них профиль реализован на чистом языке Python, Cprofile реализует нативную часть профиля, а hotshot также реализован на языке C. Разница между hotshot и Cprofile заключается в том, что hotshot меньше влияет на работу целевого кода, а затраты на постобработку больше. И хотшот прекратил техническое обслуживание.

Следует отметить, что профиль (Cprofile hotshot) подходит только для однопоточных программ на Python. Для многопоточности можно использовать yappi, yappi не только поддерживает многопоточность, но и с точностью до процессорного времени

Для гринлета вы можете использовать greenletprofiler, изменять на основе yappi и перехватывать контекст потока с контекстом greenlet.

Ниже приводится фрагмент сфабрикованного «неэффективного» кода и используется Cprofile для иллюстрации конкретных методов профиля и узких мест производительности, с которыми мы можем столкнуться.

from cProfile import Profile
import math
def foo():
return foo1()

def foo1():
return foo2()

def foo2():
return foo3()

def foo3():
return foo4()

def foo4():
return «this call tree seems ugly, but it always happen»

def bar():
ret = 0
for i in xrange(10000):
ret += i * i + math.sqrt(i)
return ret

def main():
for i in range(100000):
if i % 10000 == 0:
bar()
else:
foo()

if __name__ == ‘__main__’:
prof = Profile()
prof.runcall(main)
prof.print_stats()
#prof.dump_stats(‘test.prof’) # dump profile result to test.prof

Результаты приведены ниже:

Для приведенного выше вывода значение каждого поля следующее:

ncalls Общее количество вызовов функций
tottime Время работы внутри функции (исключая подфункции)
percall (первый) tottime / ncalls
cumtime Функция включает время, затраченное подфункцией
percall (второй) cumtime / ncalls
имя_файла: белье (функция) Файл: номер строки (функция)

Вывод в коде очень прост. Фактически, вы можете использовать pstat для разнообразия вывода результатов профиля. Подробнее см. В официальном документе: https://docs.python.org/2/library/profile.html

▍2. profile GUI tools

Хотя вывод Cprofile относительно интуитивно понятен, мы по-прежнему склонны сохранять результаты профиля, а затем использовать графические инструменты для анализа из разных измерений или сравнения кода до и после оптимизации.

Существует также множество инструментов для просмотра результатов профиля, таких как visualpytune, qcachegrind и runnakerun. В этой статье для анализа используется visualpytune. Для приведенного выше кода повторно запустите файл test.prof после создания модификации в соответствии с комментариями и напрямую откройте его с помощью visualpytune, как показано ниже:

Значение поля в основном такое же, как и вывод текста, но удобство можно отсортировать, щелкнув имя поля. Вызывающий (вызывающий) текущей функции указан в нижнем левом углу, а занятость текущей функции и подфункций — в правом нижнем углу. Вышеупомянутое является результатом сортировки в соответствии с cumtime (то есть суммой времени, занятого функцией и ее подфункциями).

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

python-profiling-tools знакомит с тем, как использовать qcachegrind и runnakerun, эти два красочных инструмента намного мощнее visualpytune. Для конкретного использования обратитесь к исходному тексту. На следующем рисунке показан результат test.prof, открытого с помощью qcachegrind.

qcachegrind действительно более мощный, чем visualpytune. Как видно из рисунка выше, он примерно разделен на три части: Первая часть похожа на visualpytune, это время, затрачиваемое каждой функцией, где Incl эквивалентно cumtime, а Self эквивалентно tottime. Во второй и третьей частях есть много меток. Различные метки обозначают результаты с разных точек зрения.

Читайте также:
1с торговля и склад описание программы

Как показано на рисунке, «граф вызовов» в третьей части показывает дерево вызовов функции и содержит дерево вызовов каждой подфункции. Процент времени понятен с первого взгляда.

▍3. Оптимизация профиля

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

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

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

Первое: снизить уровень вызова функций

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

Для кода настроенного ранее профиля дерево вызовов foo очень простое, но частота высокая. Измените код и добавьте функцию plain_foo () для непосредственного возврата окончательного результата.Ключевой вывод будет следующим:

По сравнению с предыдущими результатами:

Видно, что оптимизация почти в 3 раза.

Во-вторых: оптимизировать поиск атрибутов

Как упоминалось выше, эффективность поиска атрибутов Python очень низкая.Если вы часто обращаетесь к атрибуту в фрагменте кода (например, в цикле for), вы можете рассмотреть возможность использования локальных переменных вместо атрибутов объекта.

В-третьих: выключить сборщик мусора

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

# case 1
a, b = SomeClass(), SomeClass()
a.b, b.a = b, a

# case 2
lst = []
lst.append(lst)

# case 3
self.handler = self.some_func

Конечно, каждый может сказать, кто был бы настолько глуп, чтобы писать такой код, да, приведенный выше код слишком очевиден, когда есть несколько уровней посередине, будут «косвенные» циклические приложения. OrderedDict в коллекциях стандартной библиотеки Python — case2:

Для разрешения циклических ссылок первый метод — использовать слабые ссылки (weakref), а второй — вручную исключить из обращения ссылки.

Четвертый: setcheckinterval

Если программа определена как однопоточная, измените интервал проверки на большее значение, как описано здесь.

Пятое: используйте __slots__

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

class BaseSlots(object):
__slots__ = [‘e’, ‘f’, ‘g’]

class Slots(BaseSlots):
__slots__ = [‘a’, ‘b’, ‘c’, ‘d’]
def __init__(self):
self.a = self.b = self.c = self.d = self.e = self.f = self.g = 0

class BaseNoSlots(object):
pass

class NoSlots(BaseNoSlots):
def __init__(self):
super(NoSlots,self).__init__()
self.a = self.b = self.c = self.d = self.e = self.f = self.g = 0

def log_time(s):
begin = time.time()
for i in xrange(10000000):
s.a,s.b,s.c,s.d, s.e, s.f, s.g
return time.time() — begin

if __name__ == ‘__main__’:
print ‘Slots cost’, log_time(Slots())
print ‘NoSlots cost’, log_time(NoSlots())

Читайте также:
Телекоммуникация это компьютерная программа для поддержки электронной почты устройство

Результат выглядит следующим образом

Slots cost 3.12999987602
NoSlots cost 3.48100018501

Источник: russianblogs.com

Ускорение кода на Python средствами самого языка

Каким бы хорошим не был Python, есть у него проблема известная все разработчикам — скорость. На эту тему было написано множество статей, в том числе и на Хабре.

  • Использовать Psyco
  • Переписать часть программы на С используя Python C Extensions
  • Сменить мозгиалгоритм
  1. «Порог вхождения» у C и Python/C API все же выше, чем у «голого» Python’a, что отсекает эту возможность для разработчиков, не знакомых с C
  2. Одной из ключевых особенностей Python является скорость разработки. Написание части программы на Си снижает ее, пропорционально части переписанного в Си кода к всей программе
Так что же делать?

Тогда, если для вашего проекта выше перечисленные методы не подошли, что делать? Менять Python на другой язык? Нет, сдаваться нельзя. Будем оптимизировать сам код. Примеры будут взяты из программы, строящей множество Мандельброта заданного размера с заданным числом итераций.
Время работы исходной версии при параметрах 600*600 пикселей, 100 итераций составляло 3.07 сек, эту величину мы возьмем за 100%

Скажу заранее, часть оптимизаций приведет к тому, что код станет менее pythonic, да простят меня адепты python-way.

Шаг 0. Вынос основного кода программы в отдельную

Данный шаг помогает интерпретатору python лучше проводить внутренние оптимизации про запуске, да и при использовании psyco данный шаг может сильно помочь, т.к. psyco оптимизирует лишь функции, не затрагивая основное тело программы.
Если раньше рассчетная часть исходной программы выглядела так:

for Y in xrange(height): for X in xrange(width): #проверка вхождения точки (X,Y) в множество Мандельброта, itt итераций

То, изменив её на:
def mandelbrot(height, itt, width): for Y in xrange(height): for X in xrange(width): #проверка вхождения точки (X,Y) в множество Мандельброта, itt итераций mandelbrot(height, itt, width)

Однострочная многократная оптимизация кода на Python с использованием Numba

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

3796 просмотров

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

Так как же повысить производительность кода, внеся только несколько изменений? Ответом в данной ситуации послужит Numba!

Используя лишь несколько простейших декораторов, библиотека Numba генерирует оптимизированный машинный код с использованием компилятора LLVM таким образом, что он становится сравним по скорости с тем же кодом, написанным на C/C++ или Fortran.

Вот как компилирует код Numba:

Numba была задумана для работы с числовыми значениями и массивами Numpy. Функции и даже классы могут быть оптимизированы в двух режимах — ‘nopython’и ‘object’. В первом случае — получается достигнуть наибольшего выигрыша в производительности за счёт компиляции функции напрямую в машинный код. Во втором случае — мы имеем ситуацию, когда Numba имеет дело с оптимизацией циклов и прочих конструкций, а остальной код продолжает исполняться интерпритатором. Режим ‘object’необходим, так как, к сожалению, с некоторыми объектами Python, например словарём (dict), Numba не способен провести оптимизацию.

Пример оптимизации кода с использованием Numba:

В приведённом примере использование Numba дало двухкратный выигрыш в производительности.

В ряде случаев возможен намного более серьёзный прирост в скорости:

В данном примере уже был получен выигрыш в 290 раз!

Numba в большинстве случаев самостоятельно выводит тип входных параметров и выходных значений, однако для лучшего контроля за целостностью кода возможно явно указать типы всех параметров:

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

Numba представляет намного больше возможностей для улучшения кода, чем описаны в данной статье, в том числе компиляция кода по вычислениям на GPU, что является крайне большим подспорьем для внедрения её не только в малые, но и крупные проекты.

Источник: vc.ru

Рейтинг
( Пока оценок нет )
Загрузка ...
EFT-Soft.ru