7 простых способов оптимизировать код Python. Оптимизация python


Оптимизация кода Python | System Development

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

Хотя, на первый взгляд кажется, что Python и быстрый код не совместимые понятия, это не совсем правда.

Все тесты проводились на Python 3.3.3 и, само собой, не обошлось без IPython, который ну просто killer-feature этого языка.

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

str1 = 'val1'str2 = 'val2'str3 = 'val3'

Честно говоря, получившиеся замеры скорости конкатенации строк вышли несколько озадачивающими. На мой взгляд, они противоречат не только рекомендациям не использовать + как медленную операцию 1, но и здравому смыслу 2

%timeit res = 'str1: ' + str1 + ' str2: ' + str2 + ' str3: ' + str3 # (1)%timeit res = 'str1: %s str2: %s str3: %s' % (str1, str2, str3)%timeit res = 'str1: {} str2: {} str3: {}'.format(str1, str2, str3)  # (2)%timeit res = 'str1: {0} str2: {1} str3: {2}'.format(str1, str2, str3) # (3)%timeit "".join(['str1: ', str1, 'str2: ', str2, 'str3: ', str3]) # (4)

1000000 loops, best of 3: 446 ns per loop1000000 loops, best of 3: 453 ns per loop1000000 loops, best of 3: 675 ns per loop1000000 loops, best of 3: 416 ns per loop1000000 loops, best of 3: 375 ns per loop

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

Часто рекомендуемый вариант с join4 дает достаточно посредственную скорость, что, отчасти, связанно с необходимостью формировать дополнительный список подстрок для объединения. Если же список подстрок уже имеется, то этот вариант будет лучшим.

l = ['str1: ', str1, 'str2: ', str2, 'str3: ', str3]%timeit "".join(l)

1000000 loops, best of 3: 220 ns per loop

К сожалению, в Python отсутствуют привычные C++ разработчикам массивы. При этом, доступны списки, в CPython реализованные массивами указателей, обеспечивающие константный доступ к элементам. Кроме того, есть массивы из модуля array, массивы из NumPy.

Я ожидал что наилучший результат в произвольном RW доступе покажут массивы из NumPy, но, с учетом того что они были разработаны не для этого, предположение оказалось ложным. Поэтому, приведу тесты в порядке возрастания для массива размером 1000000 элементов. Для массивов данных в 10-100 элементов вариант с преаллокацией оказывается более быстрым, но дальше начинает отставать.

count = 1000000

%%timeitl = [i for i in range(count)]

10 loops, best of 3: 60.9 ms per loop

Кстати, C/C++ разработчики, обратит внимание на следующий момент. Казалось бы более быстрый способ с предварительной аллокацией массива нужного размера оказывается более медленным вариантом по сравнению с использованием List comprehensions. Все дело в том, что Python – интерпретируемый язык и любая встроенная функция дает ощутимый прирост производительности.

%%timeitl = [0]*countfor i in range(count):    l[i] = i

10 loops, best of 3: 76.9 ms per loop

При использовании NumPy массивов важно сразу указать тип размещаемых в нем данных, почему это важно будет понятно из пункта “Сравнение типов”, иначе скорость работы будет еще ниже.

%%timeitimport numpy as np

l = np.array(np.zeros(count), dtype=int) # (1)for i in range(count):    l[i] = i

10 loops, best of 3: 106 ms per loop

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

%%timeitfrom array import array

l = array('i', (i for i in range(count)))

1 loops, best of 3: 275 ms per loop

Мне кажется, результат интересный: самый быстрый способ является самым очевидным способом. После C++ это даже немного удивительно

Умножение и деление – еще один сюрприз от авторов Python для C++ разработчиков. Самый быстрый способ поделить/умножить на степень двойки – сдвиги. Все помнят, верно? Верно-то верно, но не везде.

bit_k = 0x80

%timeit bit_k >> 1%timeit bit_k / 2

10000000 loops, best of 3: 73.3 ns per loop10000000 loops, best of 3: 52.7 ns per loop

%timeit bit_k << 1%timeit bit_k * 2

10000000 loops, best of 3: 74.1 ns per loop10000000 loops, best of 3: 47.4 ns per loop

В Python сдвиги ощутимо медленнее операций умножения/деления.

На этот пункт нужно обратить особо пристальное внимание, т.к. на алгоритмах что-то активно считающих, благодаря невнимательному отношению к типу данных, можно получить падение скорости в 2-4 раза (!!!).

Все дело в том, что операции сравнения между целочисленными типами и типами с плавающей запятой необычайно медленные, что хорошо видно из примера ниже. Поэтому, если в процессе оптимизации какая-то функция активно сравнивает что-либо, желательно удостовериться в том, что типы сравниваемых данных совпадают. Например, тесты на Computer Language Shootout нередко подверженны этой проблеме и путем добавления .0 некоторые из них можно ускорить в те самые 2-4 раза.

%timeit 10 < 10.0%timeit 10.0 < 10.0%timeit 10 < 10

1000000 loops, best of 3: 213 ns per loop10000000 loops, best of 3: 33.2 ns per loop10000000 loops, best of 3: 31.4 ns per loop

Можно предположить, что все дело все в том, что конвертация между типами очень медленная операция:

%timeit float(10)%timeit int(10.0)

1000000 loops, best of 3: 391 ns per loop1000000 loops, best of 3: 380 ns per loop

Но, несмотря на то, что конвертация операция действительно очень медленная, дело не в этом:

def comparation():     1 < 10.0

def conversion():    int(10.0)

import dis

print("Comparation:")dis.dis(comparation)print("Conversion:")dis.dis(conversion)

Comparation:  2           0 LOAD_CONST               1 (1)               3 LOAD_CONST               2 (10.0)               6 COMPARE_OP               0 (<)               9 POP_TOP                           10 LOAD_CONST               0 (None)              13 RETURN_VALUE         Conversion:  5           0 LOAD_GLOBAL              0 (int)               3 LOAD_CONST               1 (10.0)               6 CALL_FUNCTION            1 (1 positional, 0 keyword pair)               9 POP_TOP                           10 LOAD_CONST               0 (None)              13 RETURN_VALUE

А в том, что инструкция VM Python COMPARE_OP неэффективно обрабатывает параметры разных типов.

Все принципы оптимизации в Python схожи, и циклы не исключение. Если что-то можно заменить на встроенные функции – надо менять, т.к. VM довольно медленная.

%%timeitval = 0for i in range(1000):    val += i

10000 loops, best of 3: 60.1 µs per loop

%%timeitfrom functools import reducefrom operator import add

reduce(add, range(1000))

10000 loops, best of 3: 81.8 µs per loop

Встроенная функция из Python может оказаться в ~1.7 раз быстрее самописной:

%%timeitfrom functools import reduce

reduce(lambda res, x: res+x, range(1000))

10000 loops, best of 3: 144 µs per loop

И лямбды тут практически ни при чем.

from functools import reducefrom operator import add

%timeit reduce(lambda res, x: res+x, range(1000))

def my_add(res, x):    return res + x%timeit reduce(my_add, range(1000))

%timeit reduce(add, range(1000))

10000 loops, best of 3: 144 µs per loop10000 loops, best of 3: 139 µs per loop10000 loops, best of 3: 76.8 µs per loop

oldlist = [str(i) for i in range(500)][cc lang='python'] newlist = []

def f_global():    for word in oldlist:        newlist.append(word.upper())

%timeit f_global()

10000 loops, best of 3: 108 µs per loop

def f_local(l):     newlist = []     for word in l:         newlist.append(word.upper())     return newlist

%timeit newlist = f_local(oldlist)

10000 loops, best of 3: 90 µs per loop

В данном случае все дело в инструкциях использующихся для загрузки локальных и глобальных переменных LOAD_GLOBAL и LOAD_FAST соответственно. Как видно из тестов, на Python 3.3.3 при правильной организации доступа к данным можно получить ускорение порядка 20%.

import dis

print("Global data:")dis.dis(f_global)print("Local data:")dis.dis(f_local)

Global data:   4           0 SETUP_LOOP              33 (to 36)               3 LOAD_GLOBAL              0 (oldlist)               6 GET_ITER                     >>    7 FOR_ITER                25 (to 35)              10 STORE_FAST               0 (word)

  5          13 LOAD_GLOBAL              1 (newlist)              16 LOAD_ATTR                2 (append)              19 LOAD_FAST                0 (word)              22 LOAD_ATTR                3 (upper)              25 CALL_FUNCTION            0 (0 positional, 0 keyword pair)              28 CALL_FUNCTION            1 (1 positional, 0 keyword pair)              31 POP_TOP                           32 JUMP_ABSOLUTE            7         >>   35 POP_BLOCK                    >>   36 LOAD_CONST               0 (None)              39 RETURN_VALUE         Local data:  2           0 BUILD_LIST               0               3 STORE_FAST               1 (newlist)

  3           6 SETUP_LOOP              33 (to 42)               9 LOAD_FAST                0 (l)              12 GET_ITER                     >>   13 FOR_ITER                25 (to 41)              16 STORE_FAST               2 (word)

  4          19 LOAD_FAST                1 (newlist)              22 LOAD_ATTR                0 (append)              25 LOAD_FAST                2 (word)              28 LOAD_ATTR                1 (upper)              31 CALL_FUNCTION            0 (0 positional, 0 keyword pair)              34 CALL_FUNCTION            1 (1 positional, 0 keyword pair)              37 POP_TOP                           38 JUMP_ABSOLUTE           13         >>   41 POP_BLOCK            

  5     >>   42 LOAD_FAST                1 (newlist)              45 RETURN_VALUE

Вызов функции VS сохранение и вызов

В рекомендациях по оптимизации кода на Python можно встретить упоминания того, что если функцию, вызывающуюся много раз подряд сначала сохранить в какой-либо переменной, а потом вызывать, то код более быстрым (в ущерб читабельности, конечно). Причина все та же – глобальные данные (в данном случае функция) загружаются дольше чем локальные. Теперь в роли медленной команды выступает LOAD_ATTR, а в качестве быстрой, как и раньше, LOAD_FAST.

def direct_call():     newlist = []     newlist.append(1)     return newlist

def store_and_call():    newlist = []    na = newlist.append    na(1)    return newlist

import disprint("Direct call:")dis.dis(direct_call)print("Store and call:")dis.dis(store_and_call)

Direct call:   2           0 BUILD_LIST               0               3 STORE_FAST               0 (newlist)

  3           6 LOAD_FAST                0 (newlist)               9 LOAD_ATTR                0 (append)              12 LOAD_CONST               1 (1)              15 CALL_FUNCTION            1 (1 positional, 0 keyword pair)              18 POP_TOP              

  4          19 LOAD_FAST                0 (newlist)              22 RETURN_VALUE         Store and call:  7           0 BUILD_LIST               0               3 STORE_FAST               0 (newlist)

  8           6 LOAD_FAST                0 (newlist)               9 LOAD_ATTR                0 (append)              12 STORE_FAST               1 (na)

  9          15 LOAD_FAST                1 (na)              18 LOAD_CONST               1 (1)              21 CALL_FUNCTION            1 (1 positional, 0 keyword pair)              24 POP_TOP              

 10          25 LOAD_FAST                0 (newlist)              28 RETURN_VALUE

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

sysdev.me

7 простых способов оптимизировать код Python – PyLab

1. Используйте операции с множествами (set)

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

Поскольку Python автоматически изменяет размер хеш-таблицы, скорость будет постоянной (O (1)) вне зависимости от размера набора. Именно это ускоряет выполнение операций.

В Python операции с множествами включают объединение, пересечение и разность. Поэтому вы можете попробовать использовать их в своем коде – там, где это возможно. Обычно эти операции работают быстрее, чем итерации по спискам.

 

2. Избегайте использования глобальных переменных.

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

Это вынуждает ограничить использование глобальных переменных. Можно объявить внешнюю переменную, используя ключевое слово global (что тоже не всегда правильно 😊).Кроме того, лучше сделать локальную копию, прежде чем использовать глобальные переменные внутри циклов.

3. Использование внешних библиотек / пакетов.

Некоторые библиотеки python имеют эквивалент «C» с теми же функциями, что и исходная библиотека. Будучи написаны на “C”, они работают быстрее. Например, попробуйте использовать cPickle вместо использования pickle.

Можно использовать <Cython>  , который является оптимизирующим статическим компилятором для обоих Python. Это расширение Python, поддерживающее функции и типы C. Код на нем выполняется быстрее и эффективнее.

Также можно использовать пакет PyPy. Он включает компилятор JIT (Just-in-time), который делает код Python невероятно быстрым. PyPy можно дополнительно настроить для еще большего повышения производительности.

4. Используйте встроенные модули и функции.

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

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

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

При работе в цикле нужно кэшировать вызов метода, а не вызывать метод для объекта. В противном случае поиск получится дороговатым.

Просто взгляните на этот пример, и сразу станет понятно, о чем речь.

6. Оптимизация использования строк.

Конкатенация строк идет медленно, никогда не делайте это внутри цикла. Вместо этого используйте метод join. Или используйте функцию форматирования для формирования унифицированной строки.

Операции RegEx в Python выполняются быстро, поскольку они в конечном итоге приходят к C-коду. Однако, в некоторых случаях, основные методы строки, такие как , работают лучше.

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

7. Оптимизация при помощи оператора if.

Как и в большинстве языков программирования, в Python есть “ленивая” оценка. Это означает, что если есть цепочка условий «and», то проверка остановится на первом ложном условии.

Это поведение Python можно использовать в целях опитмизации кода . Например, если вы ищете фиксированный шаблон в списке, можно уменьшить область поиска, добавив условие «and», которое становится ложным, если размер целевой строки меньше длины шаблона.

Также можно сначала проверить быстрое условие (если оно есть), например «строка должна начинаться с @», или «строка должна заканчиваться точкой».

Поделитесь с друзьями:

pylab.ru

performance - Оптимизация кода Python

Если вы задаете вопрос об оптимизации кода python вообще (что, я думаю, это должно быть;), тогда есть всевозможные вещи, которые вы можете делать, но сначала:

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

Тем не менее, есть несколько подходов, которые вы можете предпринять (потому что иногда вы действительно хотите сделать код на Python быстрее):

Существует много способов профилирования кода python, но есть два, которые я упомянул: cProfile (или profile) module и PyCallGraph.

Cprofile

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

Вы можете запустить функцию в cProfile следующим образом:

import cProfile cProfile.run('myFunction()', 'myFunction.profile')

Затем для просмотра результатов:

import pstats stats = pstats.Stats('myFunction.profile') stats.strip_dirs().sort_stats('time').print_stats()

Это покажет вам, какие функции большую часть времени потрачены.

PyCallGraph

PyCallGraph предоставляет самый красивый и, возможно, самый простой способ профилирования программ python - и это хорошее введение в понимание того, где время ваша программа потрачена, однако она добавляет значительные накладные расходы

Чтобы запустить pycallgraph:

pycallgraph graphviz ./myprogram.py

Simple! Вы получаете графическое изображение png как результат (возможно, через некоторое время...)

Если вы пытаетесь сделать что-то в python, что модуль уже существует (возможно, даже в стандартной библиотеке), тогда используйте этот модуль!

Большинство стандартных библиотечных модулей написаны на C, и они будут выполняться в сотни раз быстрее, чем эквивалентные реализации python, скажем, bisection search.

Интерпретатор сделает для вас кое-что, например, цикл. В самом деле? Да! Вы можете использовать ключевые слова map, reduce и filter, чтобы значительно ускорить тесные циклы:

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

for x in xrange(0, 100): doSomethingWithX(x)

против

map(doSomethingWithX, xrange(0,100))

Ну, очевидно, это может быть быстрее, потому что интерпретатору нужно иметь дело только с одним утверждением, а не с двумя, но это немного расплывчато... на самом деле это происходит быстрее по двум причинам:

В цикле for каждый цикл вокруг цикла python должен точно проверять, где находится функция doSomethingWithX! даже с кэшированием это немного накладные расходы.

(Обратите внимание, что этот раздел действительно касается крошечных крошечных оптимизаций, которые вы не должны допускать, чтобы повлиять на ваш обычный, читаемый стиль кодирования!) Если вы исходите из программирования на компилированном языке, например c или Fortran, то некоторые вещи о производительности различных операторов python могут быть неожиданными:

try: ing дешево, if дорого стоит

Если у вас есть такой код:

if somethingcrazy_happened: uhOhBetterDoSomething() else: doWhatWeNormallyDo()

И doWhatWeNormallyDo() выкинет исключение, если произойдет что-то сумасшедшее, тогда было бы быстрее упорядочить ваш код следующим образом:

try: doWhatWeNormallyDo() except SomethingCrazy: uhOhBetterDoSomething()

Почему? хорошо переводчик может погрузиться прямо и начать делать то, что вы обычно делаете; в первом случае интерпретатор должен выполнять поиск символа каждый раз, когда выполняется оператор if, поскольку имя может ссылаться на что-то другое с момента последнего выполнения оператора! (И поиск имени, особенно если somethingcrazy_happened есть global, может быть нетривиальным).

Вы имеете в виду Who??

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

Неоптимизированная функция:

def foo(): if condition_that_rarely_changes: doSomething() else: doSomethingElse()

Оптимизированный подход, вместо использования переменной, использует тот факт, что интерпретатор выполняет поиск имени по функции в любом случае!

Когда условие становится истинным:

foo = doSomething # now foo() calls doSomething()

Когда условие становится ложным:

foo = doSomethingElse # now foo() calls doSomethingElse()

PyPy - это реализация python, написанная на python. Неужели это означает, что он будет запускать код бесконечно медленнее? Ну нет. PyPy на самом деле использует компилятор Just-In-Time (JIT) для запуска программ python.

Если вы не используете внешние библиотеки (или те, которые вы используете, совместимы с PyPy), то это очень просто способ (почти наверняка) ускорить повторяющиеся задачи в вашей программе.

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

Конечно, первое, на что вы должны обратить внимание, это улучшить ваши алгоритмы и структуры данных, а также рассмотреть такие вещи, как кеширование, или даже нужно ли вам делать это в первую очередь, но в любом случае:

Есть немало вещей, даже из-за моего ограниченного опыта, который я пропустил, но этот ответ был уже достаточно длинным!

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

qaru.site

Python Оптимизация нужна для улучшения программ. Экопарк Z

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

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

Профилирование и оптимизация кода

В стандартной библиотеке Python имеется профайлер (модуль profile), который можно использовать для сбора статистики о времени работы отдельных функций.

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

from timeit import Timer tmp = "Python 3.2.2 (default, Jun 12 2011, 15:08:59) [MSC v.1500 32 bit (Intel)] on win32." def case1(): # А. инкрементальные конкатенации в цикле s = "" for i in range(10000): s += tmp def case2(): # Б. через промежуточный список и метод join s = [] for i in range(10000): s.append(tmp) s = "".join(s) def case3(): # В. списковое выражение и метод join return "".join([tmp for i in range(10000)]) def case4(): # Г. генераторное выражение и метод join return "".join(tmp for i in range(10000)) for v in range(1,5): print (Timer("func()","from __main__ import case%s as func" % v).timeit(200))

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

Здесь есть несколько очевидных правил.

Python имеет следующие особенности и связанные с ними правила оптимизации.

Инструмент под названием Pychecker поможет проанализировать исходный код на Python и выдать рекомендации по найденным проблемам (например, неиспользуемые имена, изменение сигнатуры метода при его перегрузке и т. п.). В ходе такого статического анализа исходного кода могут быть выявлены и ошибки.

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

Приведенная программа сохранена в файле import Timer.py и проверена на работоспособность. Расцветка программы не совпадает с расцветкой, принятой в Python версии 3.6, и лишь зря существенно удлиняет HTML-код. Привожу текст программы без расцветки — при переносе текста программы Python 3.6 без всяких уговоров расцветит её.

from timeit import Timer

tmp = «Python 3.2.2 (default, Jun 12 2011, 15:08:59) [MSC v.1500 32 bit (Intel)] on win32.»

def case1: # А. инкрементальные конкатенации в циклеs = «»for i in range(10000):s += tmp

def case2: # Б. через промежуточный список и метод joins = []for i in range(10000):s.append(tmp)s = «».join(s)

def case3: # В. списковое выражение и метод joinreturn «».join([tmp for i in range(10000)])

def case4: # Г. генераторное выражение и метод joinreturn «».join(tmp for i in range(10000))

for v in range(1,5):print (Timer(«func()»,»from __main__ import case%s as func» % v).timeit(200))

!…

Приглашаю всех высказываться в Комментариях. Критику и обмен опытом одобряю и приветствую. В хороших комментариях сохраняю ссылку на сайт автора!

И не забывайте, пожалуйста, нажимать на кнопки социальных сетей, которые расположены под текстом каждой страницы сайта.Продолжение тут…

ep-z.ru

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

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

Чаще всего, предлагают следующие решения:

Безусловно, решения верные. Но у каждого из них есть свои недостатки.Psyco — прекрасный модуль, достигающий ускорения кода в сотни процентов, но: поддерживаются лишь 32-битный Python версий не выше 2.6, большое потребление памяти и тот факт, что в последнее время разработка psyco сбавила темпы, последнее обновление на сайте датировано 16.07.2010. Возможно, с выходом Psyco v2 ситуация изменится, но пока что, этот модуль применим не всегда.Python C Extensions (рекомендую отличную статью rushman Пишем модуль расширения для Питона на C) — создатели Python'a сделали всем разработчикам, использующим этот язык, неоценимый подарок — Python/С API, дающий возможность относитенльно прозрачной интеграции Си-шного кода в программы на Python'e. Недостатков у этого решения только два:

  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) мы получили время 2.4 сек, т.е. 78% от исходного.Шаг 1. Профилирование Стандартная библиотека Python'a, это просто клондайк полезнейших модулей. Сейчас нас интересует модуль cProfile, благодаря которому, профилирование кода становится простым и, даже, интересным занятием. Полную документацию по этому модулю можно найти здесь, нам же хватит пары простых команд.

python -m cProfile sample.pyКлюч интерпетатора -m позволяет запускать модули как отдельные программы, если сам модуль предоставляет такую возможность. Результатом этой команды будет получение «профиля» программы — таблицы, вида 4613944 function calls (4613943 primitive calls) in 2.818 seconds

Ordered by: internal time

ncalls tottime percall cumtime percall filename:lineno(function) 1 2.309 2.309 2.766 2.766 mand_slow.py:22(mandelbrot) ... С её помощью, легко определить места, требующие оптимизации (строки с наибольшими значениями ncalls (кол-во вызовов функции), tottime и percall (время работы всех вызовов данной функции и каждого отдельного соответственно)).

Для удобства можно добавить ключ -s time, отсортировав вывод профилировщика по времени выполнения.

В моем случае интересной частью вывода было (время выполнение отличается от указанного выше, т.к. профилировщик добавляет свой «оверхед»): 4613944 function calls (4613943 primitive calls) in 2.818 seconds

Ordered by: internal time

ncalls tottime percall cumtime percall filename:lineno(function) 1 2.309 2.309 2.766 2.766 mand_slow.py:22(mandelbrot) 3533224 0.296 0.000 0.296 0.000 {abs} 360000 0.081 0.000 0.081 0.000 {math.atan2} 360000 0.044 0.000 0.044 0.000 {math.cos} 360000 0.036 0.000 0.036 0.000 {math.sqrt} ... Итак, профиль получен, теперь займемся оптимизацией вплотную.

Шаг 2. Анализ профиля Видим, что на первом месте по времени стоит наша основная функция mandelbrot, за ней идет системная функция abs, за ней несколько функций из модуля math, далее — одиночные вызовы функций, с минимальными временными затратами, они нам не интересны.

Итак, системные функции, «вылизаные» сообществом, нам врядли удастся улучшить, так что перейдем к нашему собственному коду:

Шаг 3. Математика Сейчас, код выглядит так:pix = img.load() #загрузим массив пикселей def mandelbrot(height, itt, width): step_x = (2 - width / 1.29) / (width / 2.6) - (1 - width / 1.29) / (width / 2.6) #шаг по оси х for Y in xrange(height): y = (Y - height / 2) / (width / 2.6) #для Y рассчет шага не так критичен как для Х, его отсутствие положительно повлияет на точность x = - (width / 1.29) / (width / 2.6) for X in xrange(width): x += step_x z = complex(x, y) phi = math.atan2(y, x - 0.25) p = math.sqrt((x - 0.25) ** 2 + y ** 2) pc = 0.5 - 0.5 * math.cos(phi) if p <= pc: #если лежит в области кардиоиды - отмечаем pix[X, Y] = (255, 255, 255) continue Z_i = 0j for i in xrange(itt): #проверка на выход за "границы бесконечности" Z_i = Z_i ** 2 + z if abs(Z_i) > 2: color = (i * 255) // itt pix[X, Y] = (color, color, color) break else: pix[X, Y] = (255, 255, 255) print("\r%d/%d" % (Y, height)), Заметим, что оператор возведения в степень ** — довольно «общий», нам же необходимо лишь возведение во вторую степень, т.е. все конструкции вида x**2 можно заменить на х*х, выиграв таким образом еще немного времени. Посмотрим на время:1.9 сек, или 62% изначального времени, достигнуто простой заменой двух строк:p = math.sqrt((x - 0.25) ** 2 + y ** 2) ... Z_i = Z_i **2 + z на:p = math.sqrt((x - 0.25) * (x - 0.25) + y * y) ... Z_i = Z_i * Z_i + z Шажки 5, 6 и 7. Маленькие, но важные Прописная истина, о которой знают все программисты на Python — работа с глобальными переменными медленней работы с локальными. Но часто забывается факт, что это верно не только для переменных но и вообще для всех объектов. В коде функции идут вызовы нескольких функций из модуля math. Так почему бы не импортировать их в самой функции? Сделано:def mandelbrot(height, itt, width): from math import atan2, cos, sqrt pix = img.load() #загрузим массив пикселей Еще 0.1сек отвоевано. Вспомним, что abs(x) вернет число типа float. Так что и сравнивать его стоит с float а не int:if abs(Z_i) > 2: ------> if abs(Z_i) > 2.0: Еще 0.15сек. 53% от начального времени.

И, наконец, грязный хак. В конкретно этой задаче, можно понять, что нижняя половина изображения, равна верхней, т.о. число вычислений можно сократить вдвое, получив в итоге 0.84сек или 27% от исходного времени.

Заключение
Профилируйте. Используйте timeit. Оптимизируйте. Python — мощный язык, и программы на нем будут работать со скоростью, пропорциональной вашему желанию разобраться и все отполировать:) Цель данной статьи, показать, что за счет мелких и незначительных изменения, таких как замен ** на *, можно заставить зеленого змея ползать до двух раз быстрее, без применения тяжелой артиллерии в виде Си, или шаманств psyco. Также, можно совместить разные средства, такие как вышеуказанные оптимизации и модуль psyco, хуже не станет:)

Спасибо всем кто дочитал до конца, буду рад выслушать ваши мнения и замечания в комментариях!

UPD Полезную ссылку в коментариях привел funca.

habr.com

Оптимизация кода Python Ru Python

Если ваш вопрос касается оптимизации кода python в целом (который, я думаю, должен быть;), тогда есть всевозможные вещи, которые вы можете делать, но сначала:

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

Тем не менее, есть несколько подходов, которые вы можете предпринять (потому что иногда вы действительно хотите сделать код на Python быстрее):

Существует много способов профилирования кода python, но есть два, о которых я упоминаю: cProfile (или profile ) и PyCallGraph .

Cprofile

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

Вы можете запустить функцию в cProfile следующим образом:

import cProfile cProfile.run('myFunction()', 'myFunction.profile')

Затем для просмотра результатов:

import pstats stats = pstats.Stats('myFunction.profile') stats.strip_dirs().sort_stats('time').print_stats()

Это покажет вам, какие функции большую часть времени проводят.

PyCallGraph

PyCallGraph обеспечивает PyCallGraph красивый и, возможно, самый простой способ профилирования программ python – и это хорошее введение в понимание того, где потрачено время в вашей программе, однако оно добавляет значительные накладные расходы

Чтобы запустить pycallgraph:

pycallgraph graphviz ./myprogram.py

Просто! Вы получаете графическое изображение png как результат (возможно, через некоторое время …)

Если вы пытаетесь что-то сделать в python, что модуль уже существует (возможно, даже в стандартной библиотеке), тогда используйте этот модуль!

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

Интерпретатор сделает для вас кое-что, например, цикл. В самом деле? Да! Вы можете использовать ключевые слова, reduce и filter чтобы значительно ускорить работу жестких циклов:

рассматривать:

for x in xrange(0, 100): doSomethingWithX(x)

против:

map(doSomethingWithX, xrange(0,100))

Очевидно, что это может быть быстрее, потому что интерпретатору нужно иметь дело только с одним утверждением, а не с двумя, но это немного расплывчато … на самом деле это быстрее по двум причинам:

В цикле for каждый цикл вокруг цикла python должен точно проверять, где doSomethingWithX функция doSomethingWithX ! даже при кэшировании это немного накладные расходы.

(Обратите внимание, что этот раздел действительно касается крошечных крошечных оптимизаций, которые вы не должны позволять влиять на ваш нормальный, читаемый стиль кодирования!) Если вы исходите из фона программирования на компилированном языке, например c или Fortran, то некоторые вещи о производительность различных операторов python может быть удивительной:

try: ing дешево, if ing дорого

Если у вас есть такой код:

if somethingcrazy_happened: uhOhBetterDoSomething() else: doWhatWeNormallyDo()

И doWhatWeNormallyDo() будет генерировать исключение, если произойдет что-то сумасшедшее, тогда было бы быстрее упорядочить ваш код следующим образом:

try: doWhatWeNormallyDo() except SomethingCrazy: uhOhBetterDoSomething()

Зачем? хорошо переводчик может погрузиться прямо и начать делать то, что вы обычно делаете; в первом случае интерпретатор должен выполнять поиск символа каждый раз, когда выполняется оператор if, поскольку имя может ссылаться на что-то другое с момента последнего выполнения оператора! (И поиск имени, особенно если somethingcrazy_happened global может быть нетривиальным).

Вы имеете в виду, кто ???

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

Неоптимизированная функция:

def foo(): if condition_that_rarely_changes: doSomething() else: doSomethingElse()

Оптимизированный подход, вместо использования переменной, использует тот факт, что интерпретатор выполняет поиск имени по функции в любом случае!

Когда условие становится истинным:

foo = doSomething # now foo() calls doSomething()

Когда условие становится ложным:

foo = doSomethingElse # now foo() calls doSomethingElse()

PyPy – это реализация python, написанная на python. Неужели это означает, что он будет запускать код бесконечно медленнее? Ну нет. PyPy фактически использует компилятор Just-In-Time (JIT) для запуска программ python.

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

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

Конечно, первое, на что вы должны обратить внимание, это улучшить ваши алгоритмы и структуры данных, а также рассмотреть такие вещи, как кеширование, или даже если вам нужно делать это в первую очередь, но в любом случае:

Есть немало вещей, даже из-за моего ограниченного опыта, который я пропустил, но этот ответ был уже достаточно длинным!

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

www.rupython.com

optimization - Оптимизация с помощью Python (scipy.optimize)

Даже жесткие эти вопросы немного устарели. Я хотел добавить альтернативное решение, которое может быть полезно для других, которые наткнулись на этот вопрос в будущем.

Теперь наша проблема разрешима аналитически. Вы можете начать с записи лагранжиана задачи оптимизации с ограничением (равенства):

L = \sum_i (x_i/y_i)^\gamma - \lambda (\sum x_i - 1)

Оптимальное решение можно найти, установив первую производную этого лагранжиана равной нулю:

0 = \partial L / \partial x_i = \gamma x_i^{\gamma-1}/\y_i - \lambda => x_i \propto y_i^{\gamma/(\gamma - 1)}

Используя эту информацию, оптимизационная задача может быть решена просто и эффективно с помощью:

In [4]: def analytical(y, gamma=0.2): x = y**(gamma/(gamma-1.0)) x /= np.sum(x) return x xanalytical = analytical(y) xanalytical, objective_function(xanalytical, y) Out [4]: (array([ 0.29466774, 0.33480719, 0.37052507]), -265.27701765929692)

Решение CT Zhu элегантно, но может нарушить ограничение положительности на третьей координате. Для gamma = 0.2 это не кажется проблемой на практике, но для разных гамм вы легко сталкиваетесь с проблемами:

In [5]: y = [0.2, 0.1, 0.8] opt = minimize(F, np.array([0., 1.]), args=(np.array(y), 2.0), method='Nelder-Mead') trans_x(opt.x), opt.fun Out [5]: (array([ 1., 1., -1.]), -11.249999999999998)

Для других задач оптимизации с теми же вероятностными симплексными ограничениями, что и ваша проблема, но для которых нет аналитического решения, возможно, стоит рассмотреть проецируемые градиентные методы или аналогичные. Эти методы используют тот факт, что существует быстрый алгоритм проектирования произвольной точки на это множество, см. https://en.wikipedia.org/wiki/Simplex#Projection_onto_the_standard_simplex.

(Чтобы увидеть полный код и улучшить визуализацию уравнений, посмотрите на ноутбук Jupyter http://nbviewer.jupyter.org/github/andim/pysnippets/blob/master/optimization-simplex-constraints.ipynb)

qaru.site


Prostoy-Site | Все права защищены © 2018 | Карта сайта