Рубрики
Без рубрики

Создание программ Python пылает быстро

Примечание: это было первоначально опубликовано в Martinheinz.dev Hawers Python всегда говорят, что одна из причин … Помечено Python, WebDev, производительность, учебник.

Примечание: это было первоначально опубликовано в martinheinz.dev

Python Ненавистники всегда говорят, что одна из причин, по которым они не хотят использовать, это то, что это медленный . Ну, будь то конкретная программа – независимо от используемого языка программирования – быстрый или медленный, очень сильно зависит от разработчика, который написал его и их навыки и способность писать Оптимизирован и быстро Программы.

Итак, давайте докажем, что некоторые люди неправы и давайте посмотрим, как мы можем улучшить производительность нашего Python Программы и делают их действительно быстро!

Сроки и профилирование

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

Примечание: Это программа, которую я буду использовать для демонстрационных целей, это вычисляет e к власти X (взяты из документов Python):

# slow_program.py
from decimal import *

def exp(x):
    getcontext().prec += 2
    i, lasts, s, fact, num = 0, 0, 1, 1, 1
    while s != lasts:
        lasts = s
        i += 1
        fact *= i
        num *= x
        s += num / fact
    getcontext().prec -= 2
    return +s

exp(Decimal(150))
exp(Decimal(400))
exp(Decimal(3000))

Леди «профилирование»

Во-первых, самый простой и честно очень ленивый раствор – Unix время команда:

~ $ time python3.8 slow_program.py

real    0m11,058s
user    0m11,050s
sys     0m0,008s

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

Самое подробное профилирование

На другом конце спектра есть CPROFILE , что даст вам Слишком много Информация:

~ $ python3.8 -m cProfile -s time slow_program.py
         1297 function calls (1272 primitive calls) in 11.081 seconds

   Ordered by: internal time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        3   11.079    3.693   11.079    3.693 slow_program.py:4(exp)
        1    0.000    0.000    0.002    0.002 {built-in method _imp.create_dynamic}
      4/1    0.000    0.000   11.081   11.081 {built-in method builtins.exec}
        6    0.000    0.000    0.000    0.000 {built-in method __new__ of type object at 0x9d12c0}
        6    0.000    0.000    0.000    0.000 abc.py:132(__new__)
       23    0.000    0.000    0.000    0.000 _weakrefset.py:36(__init__)
      245    0.000    0.000    0.000    0.000 {built-in method builtins.getattr}
        2    0.000    0.000    0.000    0.000 {built-in method marshal.loads}
       10    0.000    0.000    0.000    0.000 :1233(find_spec)
      8/4    0.000    0.000    0.000    0.000 abc.py:196(__subclasscheck__)
       15    0.000    0.000    0.000    0.000 {built-in method posix.stat}
        6    0.000    0.000    0.000    0.000 {built-in method builtins.__build_class__}
        1    0.000    0.000    0.000    0.000 __init__.py:357(namedtuple)
       48    0.000    0.000    0.000    0.000 :57(_path_join)
       48    0.000    0.000    0.000    0.000 :59()
        1    0.000    0.000   11.081   11.081 slow_program.py:1()
...

Здесь мы провели сценарий тестирования с CPROFILE модуль и время аргумент, так что строки упорядочены внутренним временем ( CumTimementme ). Это дает нам много Информацию, строки, которые вы можете видеть выше, составляют около 10% от фактического вывода. От этого мы видим, что exp Функция – это виновник ( сюрприз, сюрприз ) и теперь мы можем получить немного более конкретно с временем и профилированием …

Специальные функции

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

def timeit_wrapper(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()  # Alternatively, you can use time.process_time()
        func_return_val = func(*args, **kwargs)
        end = time.perf_counter()
        print('{0:<10}.{1:<8} : {2:<8}'.format(func.__module__, func.__name__, end - start))
        return func_return_val
    return wrapper

Этот декоратор может затем применяться к функционированию по тестированию, как так:

@timeit_wrapper
def exp(x):
    ...

print('{0:<10} {1:<8} {2:^8}'.format('module', 'function', 'time'))
exp(Decimal(150))
exp(Decimal(400))
exp(Decimal(3000))

Это дает нам вывод, как это:

~ $ python3.8 slow_program.py
module     function   time  
__main__  .exp      : 0.003267502994276583
__main__  .exp      : 0.038535295985639095
__main__  .exp      : 11.728486061969306

Одна вещь, чтобы рассмотреть это какой вид мы на самом деле (хочу) измерить. Время пакет предоставляет time.perf_counter и time.process_time Отказ Разница вот что Perf_Counter Возвращает абсолютное значение, которое включает в себя время, когда ваш процесс Python Progrence не работает, поэтому его может повлиять на нагрузку на машину. С другой стороны Process_Time Возвращает только Пользовательское время (исключая Системное время ), что только время вашего процесса.

Сделать это быстрее

Теперь для веселой части. Давайте сделаем ваши программы Python Run быстрее. Я (в основном) не собираюсь показать вам некоторые хаки, хитрости и фрагменты кода, которые волшебно решат ваши проблемы с производительностью. Это больше о общих идеях и стратегиях, которые при использовании могут оказать огромное влияние на производительность, в некоторых случаях до 30% ускоряется.

Используйте встроенные типы данных

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

Кэширование/воспоминание с lru_cache

Я уже показал этот в предыдущем блоге post здесь , но я думаю, что это стоит повторить его с помощью простого примера:

import functools
import time

# caching up to 12 different results
@functools.lru_cache(maxsize=12)
def slow_func(x):
    time.sleep(2)  # Simulate long computation
    return x

slow_func(1)  # ... waiting for 2 sec before getting result
slow_func(1)  # already cached - result returned instantaneously!

slow_func(3)  # ... waiting for 2 sec before getting result

Функция выше имитирует тяжелые вычисления, используя время . При первом вызове с параметром 1 , он ждет 2 секунды и только тогда возвращает результат. Когда снова вызывается, результат уже кэшируется, поэтому он пропускает корпус функции и немедленно возвращает результат. Для большего количества реальная жизнь Пример См. Предыдущий пост блога здесь Отказ

Используйте локальные переменные

Это связано со скоростью поиска переменных в каждом объеме. Я пишу каждая область Потому что это не только об использовании локальных и глобальных переменных. Существует фактически разница в скорости поиска, даже между – давайте скажем – локальная переменная в функции (самый быстрый), атрибут классов (например, a.g. self.name – медленнее) и глобальный, например, импортированные функции, такие как время. время (самое медленное).

Вы можете улучшить производительность, используя, казалось бы, ненужные (прямые бесполезные) назначения, такие как это:

#  Example #1
class FastClass:

    def do_stuff(self):
        temp = self.value  # this speeds up lookup in loop
        for i in range(10000):
            ...  # Do something with `temp` here

#  Example #2
import random

def fast_function():
    r = random.random
    for i in range(10000):
        print(r())  # calling `r()` here, is faster than global random.random()

Используйте функции

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

def main():
    ...  # All your previously global code

main()

Не доступа к атрибутам

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

#  Slow:
import re

def slow_func():
    for i in range(10000):
        re.findall(regex, line)  # Slow!

#  Fast:
from re import findall

def fast_func():
    for i in range(10000):
        findall(regex, line)  # Faster!

Остерегайтесь струн

Операции на строках могут быть довольно медленными, когда работают в петле, используя например модуль ( % s ) или .Format () Отказ Какие лучшие варианты у нас есть? На основании недавних Tweet от Raymond Hettinger единственное, что мы должны использовать, это F-string Это наиболее читаемый, краткий и самый быстрый метод. Итак, основываясь на этом Tweet, это список методов, которые вы можете использовать – самые медленные:

f'{s} {t}'  # Fast!
s + '  ' + t 
' '.join((s, t))
'%s %s' % (s, t) 
'{} {}'.format(s, t)
Template('$s $t').substitute(s=s, t=t)  # Slow!

Генераторы могут быть быстрыми

Генераторы не по своей природе быстрее, поскольку они были сделаны, чтобы позволить ленивому вычислению, которые сохраняют память, а не время. Тем не менее, сохраненная память может быть причиной вашей программы на самом деле работать быстрее. Как? Ну, если у вас есть большой набор данных, и вы не используете генераторы (итераторы), то данные могут переполнять процессоры Кэш L1 , что значительно замедлит поиск ценностей в памяти значительно.

Когда дело доходит до исполнения, очень важно, чтобы ЦП мог сохранить все данные, над которыми он работает, как можно ближе, что находится в кэше. Вы можете посмотреть Raymond Hettingers говорят Где он упоминает эти проблемы.

Вывод

Первое правило оптимизации – это Не делай этого Отказ Но, если вы действительно должны, то я надеюсь, что эти несколько советов помогают вам с этим. Если вы хотите прочитать дополнительные статьи Python, то вы можете проверить мой предыдущий пост блога в тестировании здесь и общие советы и трюки здесь (часть 1.) или здесь (часть 2.) .

Оригинал: “https://dev.to/martinheinz/making-python-programs-blazing-fast-4knl”