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

Python Cprofile – 7 стратегий для ускорения вашего приложения

Ваше приложение Python медленно? Пришло время для быстрого усиления! Узнайте, как в этом руководстве. Когда вы прочитаете по статье, не стесняйтесь посмотреть видео объяснения: https://youtu.be/yhh977afy-wim. Концепции настройки производительности 101 Я мог бы запустить этот учебник со списком инструментов, которые вы можете использовать для ускорения вашего приложения. Но я … python cprofile – 7 стратегий для ускорения вашего приложения Подробнее »

Автор оригинала: Chris.

Ваше приложение Python медленно? Пришло время для быстрого усиления! Узнайте, как в этом руководстве.

Когда вы прочитали статью, не стесняйтесь смотреть видео объяснения:

Концепции настройки производительности 101

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

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

Итак, что важнее, чем любой инструмент для оптимизации производительности?

Вы должны понимать Универсальные концепции настройки производительности первый.

Хорошо, что вы сможете применить эти концепции на любом языке и в любом приложении.

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

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

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

Преждевременная оптимизация – одна из главных проблем плохо написанного кода. Но что это в любом случае?

Определение: Преждевременная оптимизация Является ли акт расходов ценных ресурсов (время, усилия, линии кода, простота) для оптимизации кода, который не должен оптимизировать.

Там нет проблем с оптимизированным кодом как SE. Проблема в том, что нет такой вещи, как бесплатный обед. Если вы считаете, что вы оптимизируете фрагменты кода, то, что вы действительно делаете, это торговать одну переменную (например, сложность) против другой переменной (E.g. Performance). Примером такой оптимизации состоит в том, чтобы добавить кэш, чтобы не повторно вычислить вещи.

Проблема в том, что если вы делаете это вслепую, вы даже не осознаете вред, который вы делаете. Например, добавление на 50% больше строк кода, просто для повышения скорости выполнения на 0,1%, будет компромисс, который привернут весь процесс разработки программного обеспечения при неоднократно.

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

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

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

Шаги действий:

  • Сделайте свой код как можно читабельно и кратко.
  • Используйте комментарии и следуйте стандартам кодирования (например, Pep8 в Python).
  • Отправьте ваше приложение и выполните пользовательские тестирования.
  • Ваше приложение слишком медленно? Действительно? Хорошо, то сделайте следующее:
  • JOT Включите текущую производительность вашего приложения в считанные секунды, если вы хотите оптимизировать для скорости или байта, если вы хотите оптимизировать для памяти.
  • Не пересекайте эту линию, пока не проверили предыдущую точку.

Сначала измерить, улучшить секунду

То, что вы измеряете улучшены. Наоборот также проводит: что вы не измеряете, не улучшится.

Этот принцип является прямым следствием первого принципа: «Преждевременная оптимизация является корнем всего зла». Почему? Потому что, если вы делаете преждевременное оптимизацию, вы оптимизируете, прежде чем измерить. Но вы всегда должны оптимизировать только после того, как вы начали свои измерения. Нет смысла в «улучшении» выполнения времени, если вы не знаете, с какого уровня вы хотите улучшить. Может быть, ваша оптимизация фактически увеличила время выполнения? Может быть, это вообще не влияло? Вы не можете знать, если вы не запустите никакой попытки оптимизировать с четким эталоном.

Следствие заключается в том, чтобы начать с самых простых, наивных («глупых») кода, которые также легко читать. Это ваш тест. Любая идея оптимизации или улучшения должна улучшить эту тему. Как только вы оказались – строгими измерением – то, что ваша оптимизация улучшает ваш ориентир на X% в производительности (след или скорость памяти), это становится вашим новым эталоном.

Таким образом, ваша гарантирована повысить производительность вашего кода со временем. И вы можете документировать, доказать, и защищать любую оптимизацию к вашему боссу, вашу сверстную группу или даже научное сообщество.

Шаги действий:

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

Парето – король

Я знаю, что это не большая новость: 80/20 Парето Принцип, названный в честь итальянского экономиста Vilfredo Pareto – жив и хорошо в оптимизации производительности.

Чтобы иллюстрировать это, посмотрите на мое текущее использование ЦП, так как я пишу это:

Если вы сюжете это в Python, вы видите следующее распределение Pareto:

Вот код, который производит этот выход:

import matplotlib.pyplot as plt

labels = ['Cortana', 'Search', 'Explorer', 'System',
          'Desktop', 'Runtime', 'Snipping', 'Firefox',
          'Task', 'Dienst', 'Kapersky', 'Dienst2', 'CTF', 'Dienst3']

cpu = [8.3, 6.1, 4.6, 3.8, 2.2, 1.5, 1.4, 0.7, 0.7, 0.6, 0.5, 0.4, 0.3, 0.3]

plt.barh(labels, cpu)
plt.xlabel('Percentage')
plt.savefig('screenshot_performance.jpg')
plt.show()

20% кода требует 80% использования процессора (в порядке, я не проверил, если числа совпадают, но вы получаете точку).

Если бы я хотел уменьшить использование процессора на моем компьютере, мне просто нужно закрыть Cortana и поиск, а поиск – VOILà – значительная часть груза ЦП будет уходить:

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

Это приводит нас к 1 × 1 настройки производительности:

Оптимизация производительности – это фрактал. Как только вы закончите удаление узкого места, вокруг скрывается новое узкое место. Вы «просто» надо многократно удалять узкое место, чтобы получить максимальный «взрыв для вашего доллара».

Шаги действий:

  • Следуйте алгоритму.
  • Определите функцию узкой местности с наибольшим негативным воздействием на вашу производительность).
  • Исправить узкое место.
  • Повторить.

Алгоритмическая оптимизация выигрывает

На данный момент вы уже выяснили, что вам нужно оптимизировать свой код. У вас есть прямые отзывы пользователя, что ваше приложение слишком медленно. Или у вас сильный сигнал (например, через Google Analytics), что ваше медленное веб-приложение вызывает выше, чем обычный уровень отказов и т. Д.

Вы также знаете, где вы сейчас находитесь (в секундах или байтах) и где вы хотите пойти (в секундах или байтах).

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

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

Скажем, вы работаете в финансовом приложении. Вы знаете, ваше узкое место – это функция calculate_roi () Это происходит над всеми комбинациями потенциальных очков покупки и продажи, чтобы рассчитать максимальную прибыль (наивное решение). Поскольку это узкое место всего приложения, ваша первая задача – найти лучший алгоритм. К счастью, вы найдете максимальный алгоритм прибыли. Вычислительная сложность Уменьшает от o (n ** 2) до o (n log n).

(Если эта конкретная тема интересует вас, начните читать Это так статья .)

Шаги действий:

  • Учитывая вашу текущую функцию узкой местности.
  • Можете ли вы улучшить свои структуры данных? Часто есть низкие висячие фрукты, используя наборы Вместо списков (например, проверка членства гораздо быстрее для наборов, чем списки), или Словари вместо коллекций кортежей.
  • Можете ли вы найти лучшие алгоритмы, которые уже доказаны? Можете ли вы настроить существующие алгоритмы для вашей конкретной проблемы под рукой?
  • Проведите много времени Исследование этих вопросов. Окупается. Вы станете лучшим компьютерным ученым в процессе. И это ваше узкое место в конце концов, так что это огромное кредитное средство для вашего приложения.

Все градуется к кэше

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

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

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

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

Простой пример эффективного использования кэширования (иногда называемая воменой) является алгоритм Fibonacci:

cache = dict()

def fib(n):
    if n in cache:
        return cache[n]
    if n < 2:
        return n
    fib_n = fib(n-1) + fib(n-2)
    cache[n] = fib_n
    return fib_n


print(fib(100))
# 354224848179261915075

Проблема в том, что функция вызовов FIB2 (N-1) и FIB2 (N-2) в значительной степени рассчитывают одни и те же вещи. Например, оба отдельно рассчитают значение Fibonacci FIB2 (N-3). Это добавляет!

Но с кэшированием вы можете просто запомнить результаты предыдущих вычислений, чтобы результат для FIB2 (N-3) рассчитывается только один раз. Все остальные раз вы можете вытащить результат из кэша и получить мгновенный результат.

Вот вариант кэширования Python Fibonacci:

def fib(n):
    if n in cache:
        return cache[n]
    if n < 2:
        return n
    fib_n = fib(n-1) + fib(n-2)
    cache[n] = fib_n
    return fib_n

Вы храните результат вычисления FIB (N-1) + FIB (N-2) в кэше. Если у вас уже есть результат номера N-го фибоначчи, вы просто вытащите его из кэша, а не пересчитывая его снова и снова.

Вот удивительное улучшение скорости – просто используя простой кэш:

import time

t1 = time.time()
print(fib2(40))
t2 = time.time()
print(fib(40))
t3 = time.time()

print("Fibonacci without cache: " + str(t2-t1))
print("Fibonacci with cache: " + str(t3-t2))


''' OUTPUT:
102334155
102334155
Fibonacci without cache: 31.577041387557983
Fibonacci with cache: 0.015461206436157227
'''

Есть две основные стратегии, которые вы можете использовать:

  • Выполните вычисления в Advanced («Оффлайн») и храните их результаты в кэше. Это отличная стратегия для веб-приложений, где вы можете заполнить большой кеш один раз (или один раз в день), а затем просто служить результатом ваших предварительностей для пользователей. Для них ваши расчеты “чувствуют себя” восстанавливающе быстро. Но на самом деле вы просто служите им предварительно выявленные значения. Google Maps Suity использует этот трюк для ускорения коротких вычислений на пути.
  • Выполните вычисления, как они появляются («онлайн») и хранят свои результаты в кэше Отказ Эта реактивная форма является наиболее основной и простейшей формой кэширования, где вам не нужно решать, какие вычисления выполняются заранее.

В обоих случаях, чем больше расчетов, которые вы храните, тем выше вероятность «кэш-хит», где вычисления могут быть немедленно возвращены. Но, как у вас обычно есть предел памяти (например, 100 000 записей кэш), вам нужно решить о разумных Политика замены кэша Отказ

Шаги действий:

  • Думаю: как вы можете уменьшить избыточные вычисления? Кэширование будет разумным подходом?
  • Какие данные/вычисления вы кэшируете?
  • Какой размер вашего кэша?
  • Какие записи для удаления, если кеш заполнен?
  • Если у вас есть веб-приложение, вы можете повторно использовать вычисления предыдущих пользователей для вычисления результата вашего текущего пользователя?

Меньше – больше

Ваша проблема слишком тяжелая? Сделать это проще!

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

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

Так что спрашивайте себя: какое у вас текущая функция узкой узки действительно делает? Это действительно стоит усилия? Можете ли вы удалить функцию или предложить версию размеров вниз? Если функция используется 1% ваших пользователей, но 100% воспринимают повышенную задержку, это может быть время для некоторого минимализма!

Шаг действий:

  • Можете ли вы удалить текущее узкое место в целом, просто пропускаете функцию?
  • Можете ли вы упростить проблему?
  • Думаю 80/20: избавиться от одной дорогих функций, чтобы добавить 10 не дорогих.
  • Думаю Возможность стоит : опустить одну важной особенностью, чтобы вы могли преследовать Очень важная особенность.

Знать, когда остановиться

Это легко сделать, но также легко не делать: остановиться!

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

Шаг действий:

  • Попросите себя постоянно: это действительно стоит ли усилия, чтобы продолжать оптимизировать?

Python Profillers

Python поставляется с разными профилировщиками. Если вы новичок в оптимизации производительности, вы можете спросить: Что в любом случае профилировщик?

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

Без этих вещей вы не можете знать, что такое узкое место вашего приложения. И, как вы уже узнали выше, вы не можете начать оптимизировать свой код. Почему? Потому что иначе вы были соучастны в «преждевременной оптимизации» – одновременно из смертоносных грехов в программировании.

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

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

Python Cprofile

Самый популярный Python Profiler называется CPROFILE Отказ Вы можете импортировать его как любую другую библиотеку, используя оператор:

import cProfile

Простое утверждение, но, тем не менее, мощный инструмент в вашем панели инструментов.

Давайте напишем сценарий Python, который вы можете профиль. Скажем, вы придумаете этот (очень) Swear Python Script, чтобы найти 100 случайных простых чисел от 2 до 1000, которые вы хотите оптимизировать:

import random


def guess():
    ''' Returns a random number '''
    return random.randint(2, 1000)


def is_prime(x):
    ''' Checks whether x is prime '''
    for i in range(x):
        for j in range(x):
            if i * j == x:
                return False
    return True


def find_primes(num):
    primes = []
    for i in range(num):
        p = guess()
        while not is_prime(p):
            p = guess()
        primes += [p]
    return primes


print(find_primes(100))
'''
[733, 379, 97, 557, 773, 257, 3, 443, 13, 547, 839, 881, 997,
431, 7, 397, 911, 911, 563, 443, 877, 269, 947, 347, 431, 673,
467, 853, 163, 443, 541, 137, 229, 941, 739, 709, 251, 673, 613,
23, 307, 61, 647, 191, 887, 827, 277, 389, 613, 877, 109, 227,
701, 647, 599, 787, 139, 937, 311, 617, 233, 71, 929, 857, 599,
2, 139, 761, 389, 2, 523, 199, 653, 577, 211, 601, 617, 419, 241,
179, 233, 443, 271, 193, 839, 401, 673, 389, 433, 607, 2, 389,
571, 593, 877, 967, 131, 47, 97, 443]
'''

Программа медленная (и вы чувствуете, что есть много оптимизаций). Но с чего начать?

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

import cProfile
cProfile.run('print(find_primes(100))')

Это действительно так просто. Во-первых, вы пишете свой скрипт. Во-вторых, вы называете cprofile.run () Способ проанализировать его производительность. Конечно, вам необходимо заменить команду исполнения с вашим конкретным кодом, который вы хотите проанализировать. Например, если вы хотите проверить функцию F42 () , вам нужно ввести cprofile.run ('f42 ()') Отказ

Вот вывод предыдущего фрагмента кода (еще не паниковать):

[157, 773, 457, 317, 251, 719, 227, 311, 167, 313, 521, 307, 367, 827, 317, 443, 359, 443, 887, 241, 419, 103, 281, 151, 397, 433, 733, 401, 881, 491, 19, 401, 661, 151, 467, 677, 719, 337, 673, 367, 53, 383, 83, 463, 269, 499, 149, 619, 101, 743, 181, 269, 691, 193, 7, 883, 449, 131, 311, 547, 809, 619, 97, 997, 73, 13, 571, 331, 37, 7, 229, 277, 829, 571, 797, 101, 337, 5, 17, 283, 449, 31, 709, 449, 521, 821, 547, 739, 113, 599, 139, 283, 317, 373, 719, 977, 373, 991, 137, 797]
         3908 function calls in 1.614 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    1.614    1.614 :1()
      535    1.540    0.003    1.540    0.003 code.py:10(is_prime)
        1    0.000    0.000    1.542    1.542 code.py:19(find_primes)
      535    0.000    0.000    0.001    0.000 code.py:5(guess)
      535    0.000    0.000    0.001    0.000 random.py:174(randrange)
      535    0.000    0.000    0.001    0.000 random.py:218(randint)
      535    0.000    0.000    0.001    0.000 random.py:224(_randbelow)
       21    0.000    0.000    0.000    0.000 rpc.py:154(debug)
        3    0.000    0.000    0.072    0.024 rpc.py:217(remotecall)
        3    0.000    0.000    0.000    0.000 rpc.py:227(asynccall)
        3    0.000    0.000    0.072    0.024 rpc.py:247(asyncreturn)
        3    0.000    0.000    0.000    0.000 rpc.py:253(decoderesponse)
        3    0.000    0.000    0.072    0.024 rpc.py:291(getresponse)
        3    0.000    0.000    0.000    0.000 rpc.py:299(_proxify)
        3    0.000    0.000    0.072    0.024 rpc.py:307(_getresponse)
        3    0.000    0.000    0.000    0.000 rpc.py:329(newseq)
        3    0.000    0.000    0.000    0.000 rpc.py:333(putmessage)
        2    0.000    0.000    0.047    0.023 rpc.py:560(__getattr__)
        3    0.000    0.000    0.000    0.000 rpc.py:57(dumps)
        1    0.000    0.000    0.047    0.047 rpc.py:578(__getmethods)
        2    0.000    0.000    0.000    0.000 rpc.py:602(__init__)
        2    0.000    0.000    0.026    0.013 rpc.py:607(__call__)
        2    0.000    0.000    0.072    0.036 run.py:354(write)
        6    0.000    0.000    0.000    0.000 threading.py:1206(current_thread)
        3    0.000    0.000    0.000    0.000 threading.py:216(__init__)
        3    0.000    0.000    0.072    0.024 threading.py:264(wait)
        3    0.000    0.000    0.000    0.000 threading.py:75(RLock)
        3    0.000    0.000    0.000    0.000 {built-in method _struct.pack}
        3    0.000    0.000    0.000    0.000 {built-in method _thread.allocate_lock}
        6    0.000    0.000    0.000    0.000 {built-in method _thread.get_ident}
        1    0.000    0.000    1.614    1.614 {built-in method builtins.exec}
        6    0.000    0.000    0.000    0.000 {built-in method builtins.isinstance}
        9    0.000    0.000    0.000    0.000 {built-in method builtins.len}
        1    0.000    0.000    0.072    0.072 {built-in method builtins.print}
        3    0.000    0.000    0.000    0.000 {built-in method select.select}
        3    0.000    0.000    0.000    0.000 {method '_acquire_restore' of '_thread.RLock' objects}
        3    0.000    0.000    0.000    0.000 {method '_is_owned' of '_thread.RLock' objects}
        3    0.000    0.000    0.000    0.000 {method '_release_save' of '_thread.RLock' objects}
        3    0.000    0.000    0.000    0.000 {method 'acquire' of '_thread.RLock' objects}
        6    0.071    0.012    0.071    0.012 {method 'acquire' of '_thread.lock' objects}
        3    0.000    0.000    0.000    0.000 {method 'append' of 'collections.deque' objects}
      535    0.000    0.000    0.000    0.000 {method 'bit_length' of 'int' objects}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
        3    0.000    0.000    0.000    0.000 {method 'dump' of '_pickle.Pickler' objects}
        2    0.000    0.000    0.000    0.000 {method 'get' of 'dict' objects}
      553    0.000    0.000    0.000    0.000 {method 'getrandbits' of '_random.Random' objects}
        3    0.000    0.000    0.000    0.000 {method 'getvalue' of '_io.BytesIO' objects}
        3    0.000    0.000    0.000    0.000 {method 'release' of '_thread.RLock' objects}
        3    0.000    0.000    0.000    0.000 {method 'send' of '_socket.socket' objects}

Давайте деконструируем его правильно понимать значение вывода. Имя файла вашего сценария – «Code.py». Вот первая часть:

>>>import cProfile
>>>cProfile.run('print(find_primes(100))')
[157, 773, 457, 317, 251, 719, 227, 311, 167, 313, 521, 307, 367, 827, 317, 443, 359, 443, 887, 241, 419, 103, 281, 151, 397, 433, 733, 401, 881, 491, 19, 401, 661, 151, 467, 677, 719, 337, 673, 367, 53, 383, 83, 463, 269, 499, 149, 619, 101, 743, 181, 269, 691, 193, 7, 883, 449, 131, 311, 547, 809, 619, 97, 997, 73, 13, 571, 331, 37, 7, 229, 277, 829, 571, 797, 101, 337, 5, 17, 283, 449, 31, 709, 449, 521, 821, 547, 739, 113, 599, 139, 283, 317, 373, 719, 977, 373, 991, 137, 797]
...

Он по-прежнему дает вам вывод на оболочку – даже если вы не выполняли код напрямую, cprofile.run () Функция сделала. Вы можете увидеть список из 100 случайных простых чисел здесь.

Следующая часть печатает некоторую статистику в оболочку:

         3908 function calls in 1.614 seconds

Хорошо, это интересно: вся программа заняла 1,614 секунды, чтобы выполнить. Всего было выполнено 3908 вызовов функций. Можете ли вы понять, какой?

  • Функция Print () один раз.
  • Функция Find_primes (100) один раз.
  • Функция find_primes () выполняет цикл для цикла 100 раз.
  • В цикле для цикла мы выполняем диапазон (), Угадайте () и IS_PRIME () функции. Программа выполняет функции угадателя () и IS_PRIME () несколько раз на петлевую итерацию до тех пор, пока она не догадалась не догадаться следующим простым числом.
  • Функция Угадания () выполняет метод Randint (21000) один раз.

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

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    1.614    1.614 :1()
      535    1.540    0.003    1.540    0.003 code.py:10(is_prime)
        1    0.000    0.000    1.542    1.542 code.py:19(find_primes)
 ...

Каждая строка обозначает одну функцию. Например, вторая строка обозначает функцию is_prime. Вы можете видеть, что IS_PRIME () имел 535 казней с общим временем 1,54 секунды.

Вау! Вы только что нашли узкое место всей программы: is_prime (). Опять же, полное время выполнения составляло 1,614 секунды, и эта функция доминирует на 95% от общего времени выполнения!

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

Есть две основные идеи:

  • Позвоните в функцию is_prime () реже, а также
  • Оптимизировать производительность самой функции.

Вы знаете, что лучший способ оптимизировать код – поиск более эффективных алгоритмов. Быстрый Поиск показывает гораздо более эффективный алгоритм (см. Функцию IS_PRIME2 () ).

import random


def guess():
    ''' Returns a random number '''
    return random.randint(2, 1000)


def is_prime(x):
    ''' Checks whether x is prime '''
    for i in range(x):
        for j in range(x):
            if i * j == x:
                return False
    return True


def is_prime2(x):
    ''' Checks whether x is prime '''
    for i in range(2,int(x**0.5)+1):
        if x % i == 0:
            return False
    return True


def find_primes(num):
    primes = []
    for i in range(num):
        p = guess()
        while not is_prime2(p):
            p = guess()
        primes += [p]
    return primes


import cProfile
cProfile.run('print(find_primes(100))')

Как вы думаете: наша новая главная проверка быстрее? Давайте изучим результат нашего фрагмента кода:

[887, 347, 397, 743, 751, 19, 337, 983, 269, 547, 823, 239, 97, 137, 563, 757, 941, 331, 449, 883, 107, 271, 709, 337, 439, 443, 383, 563, 127, 541, 227, 929, 127, 173, 383, 23, 859, 593, 19, 647, 487, 827, 311, 101, 113, 139, 643, 829, 359, 983, 59, 23, 463, 787, 653, 257, 797, 53, 421, 37, 659, 857, 769, 331, 197, 443, 439, 467, 223, 769, 313, 431, 179, 157, 523, 733, 641, 61, 797, 691, 41, 751, 37, 569, 751, 613, 839, 821, 193, 557, 457, 563, 881, 337, 421, 461, 461, 691, 839, 599]
         4428 function calls in 0.074 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.073    0.073 :1()
      610    0.002    0.000    0.002    0.000 code.py:19(is_prime2)
        1    0.001    0.001    0.007    0.007 code.py:27(find_primes)
      610    0.001    0.000    0.004    0.000 code.py:5(guess)
      610    0.001    0.000    0.003    0.000 random.py:174(randrange)
      610    0.001    0.000    0.004    0.000 random.py:218(randint)
      610    0.001    0.000    0.001    0.000 random.py:224(_randbelow)
       21    0.000    0.000    0.000    0.000 rpc.py:154(debug)
        3    0.000    0.000    0.066    0.022 rpc.py:217(remotecall)

Сумасшедший – какое улучшение производительности! С старом узким местом код занимает 1,6 секунды. Теперь это занимает всего 0,074 секунды – улучшение производительности времени выполнения 95%!

Это сила анализа узкой узки.

Способ CPROFILE имеет много дополнительных функций и параметров, но этот простой метод CPROFILE.RUN () уже достаточно, чтобы разрешить много производительности узких мест.

Как сортировать вывод метода CPROFILE.RUN ()?

Чтобы сортировать вывод относительно I-й колонны, вы можете пройти Сортировать = я Аргумент метода CPROFILE.RUN (). Вот вывод справки:

>>> import cProfile
>>> help(cProfile.run)
Help on function run in module cProfile:

run(statement, filename=None, sort=-1)
    Run statement under profiler optionally saving results in filename

    This function takes a single argument that can be passed to the
    "exec" statement, and an optional file name.  In all cases this
    routine attempts to "exec" its first argument and gather profiling
    statistics from the execution. If no file name is present, then this
    function automatically prints a simple profiling report, sorted by the
    standard name string (file/line/function-name) that is presented in
    each line.

А вот минимальный пример профилирования вышеуказанного метода Find_Prime ():

import cProfile
cProfile.run('print(find_primes(100))', sort=0)

Выход отсортирован по количеству вызовов функций (первая колонна):

[607, 61, 271, 167, 101, 983, 3, 541, 149, 619, 593, 433, 263, 823, 751, 149, 373, 563, 599, 607, 61, 439, 31, 773, 991, 953, 211, 263, 839, 683, 53, 853, 569, 547, 991, 313, 191, 881, 317, 967, 569, 71, 73, 383, 41, 17, 67, 673, 137, 457, 967, 331, 809, 983, 271, 631, 557, 149, 577, 251, 103, 337, 353, 401, 13, 887, 571, 29, 743, 701, 257, 701, 569, 241, 199, 719, 3, 907, 281, 727, 163, 317, 73, 467, 179, 443, 883, 997, 197, 587, 701, 919, 431, 827, 167, 769, 491, 127, 241, 41]
         5374 function calls in 0.021 seconds

   Ordered by: call count

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
      759    0.000    0.000    0.000    0.000 {method 'getrandbits' of '_random.Random' objects}
      745    0.000    0.000    0.001    0.000 random.py:174(randrange)
      745    0.000    0.000    0.001    0.000 random.py:218(randint)
      745    0.000    0.000    0.000    0.000 random.py:224(_randbelow)
      745    0.001    0.000    0.001    0.000 code.py:18(is_prime2)
      745    0.000    0.000    0.001    0.000 code.py:4(guess)
      745    0.000    0.000    0.000    0.000 {method 'bit_length' of 'int' objects}
       21    0.000    0.000    0.000    0.000 rpc.py:154(debug)
        9    0.000    0.000    0.000    0.000 {built-in method builtins.len}
        6    0.000    0.000    0.000    0.000 threading.py:1206(current_thread)
        6    0.018    0.003    0.018    0.003 {method 'acquire' of '_thread.lock' objects}
        6    0.000    0.000    0.000    0.000 {built-in method _thread.get_ident}
        6    0.000    0.000    0.000    0.000 {built-in method builtins.isinstance}
        3    0.000    0.000    0.000    0.000 threading.py:75(RLock)
        3    0.000    0.000    0.000    0.000 threading.py:216(__init__)
        3    0.000    0.000    0.018    0.006 threading.py:264(wait)
        3    0.000    0.000    0.000    0.000 rpc.py:57(dumps)
        3    0.000    0.000    0.019    0.006 rpc.py:217(remotecall)
        3    0.000    0.000    0.000    0.000 rpc.py:227(asynccall)
        3    0.000    0.000    0.018    0.006 rpc.py:247(asyncreturn)
        3    0.000    0.000    0.000    0.000 rpc.py:253(decoderesponse)
        3    0.000    0.000    0.018    0.006 rpc.py:291(getresponse)
        3    0.000    0.000    0.000    0.000 rpc.py:299(_proxify)
        3    0.000    0.000    0.018    0.006 rpc.py:307(_getresponse)
        3    0.000    0.000    0.000    0.000 rpc.py:333(putmessage)
        3    0.000    0.000    0.000    0.000 rpc.py:329(newseq)
        3    0.000    0.000    0.000    0.000 {method 'append' of 'collections.deque' objects}
        3    0.000    0.000    0.000    0.000 {method 'acquire' of '_thread.RLock' objects}
        3    0.000    0.000    0.000    0.000 {method 'release' of '_thread.RLock' objects}
        3    0.000    0.000    0.000    0.000 {method '_is_owned' of '_thread.RLock' objects}
        3    0.000    0.000    0.000    0.000 {method '_acquire_restore' of '_thread.RLock' objects}
        3    0.000    0.000    0.000    0.000 {method '_release_save' of '_thread.RLock' objects}
        3    0.000    0.000    0.000    0.000 {built-in method _thread.allocate_lock}
        3    0.000    0.000    0.000    0.000 {method 'getvalue' of '_io.BytesIO' objects}
        3    0.000    0.000    0.000    0.000 {method 'dump' of '_pickle.Pickler' objects}
        3    0.000    0.000    0.000    0.000 {built-in method _struct.pack}
        3    0.000    0.000    0.000    0.000 {method 'send' of '_socket.socket' objects}
        3    0.000    0.000    0.000    0.000 {built-in method select.select}
        2    0.000    0.000    0.019    0.009 run.py:354(write)
        2    0.000    0.000    0.000    0.000 rpc.py:602(__init__)
        2    0.000    0.000    0.018    0.009 rpc.py:607(__call__)
        2    0.000    0.000    0.001    0.000 rpc.py:560(__getattr__)
        2    0.000    0.000    0.000    0.000 {method 'get' of 'dict' objects}
        1    0.000    0.000    0.001    0.001 rpc.py:578(__getmethods)
        1    0.000    0.000    0.002    0.002 code.py:26(find_primes)
        1    0.000    0.000    0.021    0.021 :1()
        1    0.000    0.000    0.021    0.021 {built-in method builtins.exec}
        1    0.000    0.000    0.019    0.019 {built-in method builtins.print}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

Если вы хотите узнать больше, изучите официальную документацию.

Как профилировать приложение Flask?

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

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

Эти онлайн-инструменты бесплатны и просты в использовании: вам просто нужно копировать и вставить URL вашего сайта и нажмите кнопку. Затем они укагут вас к потенциальным узким местам вашего приложения. Просто запустите все из них и соберите результаты в файле Excel или около того. Затем проведите некоторое время, думая о возможных узких местах, пока не уверена, что вы нашли главное узкое место.

Вот пример скорости страницы Google для приложения для создания богатства www.wealthdashboard.app :

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

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

Итак, давайте начнем с минимального примера приложение Dash Отказ Обратите внимание, что приложение Dash внутренне управляет сервером Flask:

import dash
import dash_core_components as dcc
import dash_html_components as html

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

app.layout = html.Div(children=[
    html.H1(children='Hello Dash'),

    html.Div(children='''
        Dash: A web application framework for Python.
    '''),

    dcc.Graph(
        id='example-graph',
        figure={
            'data': [
                {'x': [1, 2, 3], 'y': [4, 1, 2], 'type': 'bar', 'name': 'SF'},
                {'x': [1, 2, 3], 'y': [2, 4, 5], 'type': 'bar', 'name': u'Montréal'},
            ],
            'layout': {
                'title': 'Dash Data Visualization'
            }
        }
    )
])

if __name__ == '__main__':
    #app.run_server(debug=True)
    import cProfile
    cProfile.run('app.run_server(debug=True)', sort=1)

Не волнуйтесь, вам не нужно понимать, что происходит. Только одно важно: вместо того, чтобы бежать app.run_server (debut = true) В третьей последней строке вы выполняете cprofile.run (...) обертка Вы сортируете вывод относительно уменьшения времени выполнения (второй столбец). Результат выполнения и завершения приложения Flask выглядит следующим образом:

        6031 function calls (5967 primitive calls) in 3.309 seconds

   Ordered by: internal time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        2    3.288    1.644    3.288    1.644 {built-in method _winapi.WaitForSingleObject}
        1    0.005    0.005    0.005    0.005 {built-in method _winapi.CreateProcess}
        7    0.003    0.000    0.003    0.000 _winconsole.py:152(write)
        4    0.002    0.001    0.002    0.001 win32.py:109(SetConsoleTextAttribute)
       26    0.002    0.000    0.002    0.000 {built-in method nt.stat}
        9    0.001    0.000    0.004    0.000 {method 'write' of '_io.TextIOWrapper' objects}
        6    0.001    0.000    0.003    0.000 :882(_find_spec)
        1    0.001    0.001    0.001    0.001 win32.py:92(_winapi_test)
        5    0.000    0.000    0.000    0.000 {built-in method marshal.loads}
        5    0.000    0.000    0.001    0.000 :914(get_data)
        5    0.000    0.000    0.000    0.000 {method 'read' of '_io.FileIO' objects}
        4    0.000    0.000    0.000    0.000 {method 'acquire' of '_thread.lock' objects}
      390    0.000    0.000    0.000    0.000 os.py:673(__getitem__)
        7    0.000    0.000    0.000    0.000 _winconsole.py:88(get_buffer)
...

Таким образом, произошло 6031 вызовов функций, но промедления выполнения доминировала метод WaitForsingleObject () Как вы можете видеть в первом ряду выходной таблицы. Это имеет смысл, поскольку я только провел сервер и закрыл его – он не обрабатывал ни одного запроса.

Но если вы выполните многие запросы, когда вы протестируете свой сервер, вы бы быстро узнаете о методах узких мест.

Есть некоторые конкретные профилировщики для приложений для колба. Я бы порекомендовал вам Начните смотреть здесь :

Вы можете настроить профилировщик всего за несколько строк кода. Однако эта колба Profiler фокусируется на производительности нескольких конечных точек («URL»). Если вы хотите изучить функцию вызовов одной конечной точки/URL, вы все равно должны использовать модуль Cprofile для тонкозернитого анализа.

Простой способ использования модуля Cprofile в приложении Flask – Werkzeug проект. Используя это так же просто, как упаковочное приложение для колбы, как это:

from werkzeug.contrib.profiler import ProfilerMiddleware
app = ProfilerMiddleware(app)

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

Пример профилирования Pandas

Чтобы профилировать приложение Pandas, вы должны разделить общий скрипт во многие функции и использовать модуль CPROFile Python (см. Выше). Это быстро указывает на потенциальные узкие места.

Однако, если вы хотите узнать о конкретных PandaS DataFrame, вы можете использовать следующие два метода:

Резюме

Вы узнали, как обратиться к проблеме оптимизации производительности концептуально:

  1. Преждевременная оптимизация является корнем всего зла
  2. Сначала измерить, улучшить секунду
  3. Парето – король
  4. Алгоритмическая оптимизация выигрывает
  5. Все градуется к кэше
  6. Меньше – больше
  7. Знать, когда остановиться

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

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

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

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

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

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

Чтобы помочь студентам достичь более высоких уровней успеха Python, он основал сайт программирования образования Finxter.com Отказ Он автор популярной книги программирования Python One-listers (Nostarch 2020), Coauthor of Кофе-брейк Python Серия самооставленных книг, энтузиаста компьютерных наук, Фрилансера и владелец одного из лучших 10 крупнейших Питон блоги по всему миру.

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