Поймите, какие части вашего кода проблематичны

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

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

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

Код этой статьи можно найти на GitHub, а более подробную записанную версию можно найти на YouTube.

Определение профиля и преимущества

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

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

  • Быстрые программы лучше медленных - будь то задержка или пропускная способность.
  • Эффективность памяти хорошая. Большинство из нас боятся ошибок, связанных с нехваткой памяти.
  • Экономить деньги - это здорово. Если ошибки нехватки памяти вас не пугают, возможно, ваши счета за AWS.
  • У оборудования есть ограничения.

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

Правила техники безопасности

Следующие правила были написаны кровью.

  • Убедитесь, что это действительно необходимо. Оптимизированный код, как правило, сложнее писать и читать, что делает его менее удобным в обслуживании и более ошибочным. Нам нужно собрать требования (SLA / SLOS), чтобы понять наше определение «готово».
  • Убедитесь, что наш код хорошо протестирован - я знаю, что все это говорят, но это действительно важно. Будет печально, если мы исправим проблему с производительностью, но наша программа работать не будет.
  • Хорошая работа требует циклов - Мы должны сосредоточиться на проблемных частях кода (узком месте).

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

Типы профайлеров

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

Выбор подходящего инструмента для работы зависит от следующих характеристик:

  • Ресурс, который мы хотим измерить - будь то ЦП, ОЗУ, ввод-вывод или другие экзотические показатели.
  • Стратегия профилирования - то, как профилировщик собирает данные: детерминированным образом с помощью ловушек или статистическим способом, собирая информацию на каждом временном интервале.
  • Детализация профилирования - на каком уровне мы получаем информацию: уровень программы, уровень функции или уровень строки. Очевидно, что мы можем извлечь больше информации, если будем использовать более тонкие уровни детализации, такие как линейный уровень, но это добавит много шума.

Наш пример

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

Расстояние Левенштейна - это строковая метрика для измерения разницы между двумя словами. Расстояние между двумя словами - это минимальное количество односимвольных правок (т. Е. Вставок, удалений или замен), необходимых для замены одного слова другим . - Алгоритм расстояния Левенштейна

Корректор орфографии Норвига исправляет орфографические ошибки, находя наиболее вероятные слова с расстоянием Левенштейна, равным двум или меньше. Этот корректор орфографии кажется довольно наивным, но на самом деле он обеспечивает точность 80% или 90%.

Мы можем использовать наш корректор орфографии, чтобы понять следующее сообщение «grofilingg is not rocet Sgience», и мы получаем «профилирование - это не ракетостроение».

Мы начнем профилирование нашего примера с первого типа профилировщиков, случайных профилировщиков.

Случайные профилировщики

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

Первый инструмент, который мы рассмотрим, - time, который помогает нам измерять пользовательское и системное время за один запуск. Он встроен в Python и поставляется с магией IPython, дополнительной установки не требуется.

Следующий инструмент, который я собираюсь рассмотреть, - это модуль timeit, который помогает нам измерять время выполнения нескольких запусков. Он встроен в Python и поставляется с IPython, дополнительная установка не требуется. Он использует некоторые хитрые приемы, такие как отключение сборки мусора, чтобы сделать результат более согласованным.

Последний инструмент для случайного профилирования, о котором я собираюсь рассказать сегодня, - это memit magic, который помогает нам измерить память процесса. Он не встроен в Python, поэтому требуется установка, но после его установки мы можем использовать магию IPython.

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

У обычных профилировщиков есть следующие плюсы и минусы

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

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

Автономные профилировщики

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

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

Это большой объем текста, который нужно обработать, и он немного зашифрован для первых. По этой причине был изобретен следующий инструмент - SnakeViz. SnakeViz принимает cProfile вывод и создает интуитивно понятные визуализации. Он не встроен в Python, поэтому требуется установка, но после его установки мы можем использовать магию IPython.

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

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

Офлайн-профилировщики имеют следующие плюсы и минусы

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

Это подводит нас к нашему последнему типу профилировщиков - онлайн-профилировщикам.

Интернет-профилировщики

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

Я собираюсь рассмотреть только pyinstrument, который измеряет процессор процесса путем выборки и записи статистики каждую миллисекунду. Он не встроен в Python, поэтому требуется установка, и в нем нет магии IPython.

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

У онлайн-профайлеров есть следующие плюсы и минусы

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

В этой статье мы рассмотрели множество инструментов. Теперь у вас есть возможность найти узкие места в 99% случаев использования.

Последний процент

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

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

  • Вы можете создать собственный автономный профилировщик, используя cProfile или sys.settrace и sys.setprofile.
  • Вы можете создать собственный онлайн-профайлер, используя setitimer или ptrace.
  • Вы можете прикрепить код Python к запущенному процессу используя pyrasite.

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

Совет от профессионала: записывайте входные данные и продолжительность горячих функций.

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

Последние слова

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

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