Работа через принцип единой ответственности (SRP) в Python, когда звонки стоят дорого

12

Некоторые базовые точки:

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

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

from operator import add

class Vector:
    def __init__(self,list_of_3):
        self.coordinates = list_of_3

    def move(self,movement):
        self.coordinates = list( map(add, self.coordinates, movement))
        return self.coordinates

    def revert(self):
        self.coordinates = self.coordinates[::-1]
        return self.coordinates

    def get_coordinates(self):
        return self.coordinates

## Operation with one vector
vec3 = Vector([1,2,3])
vec3.move([1,1,1])
vec3.revert()
vec3.get_coordinates()

По сравнению с этим:

from operator import add

def move_and_revert_and_return(vector,movement):
    return list( map(add, vector, movement) )[::-1]

move_and_revert_and_return([1,2,3],[1,1,1])

Если я собираюсь распараллелить что-то подобное, это довольно объективная потеря производительности. Ум это просто пример; В моем проекте есть несколько мини-подпрограмм с такой математикой, хотя с ними гораздо проще работать, нашим профилировщикам это не нравится.


Как и где мы можем использовать SRP без ущерба для производительности в Python, поскольку его внутренняя реализация напрямую влияет на него?

Существуют ли обходные пути, например, какой-то препроцессор, который готовит релиз?

Или Python плохо справляется с обработкой кода?

lucasgcb
источник
4
Возможный дубликат Важна ли микрооптимизация при кодировании?
комар
19
Для чего это стоит, ваши два примера кода не различаются по количеству обязанностей. SRP не является методом подсчета.
Роберт Харви
2
@RobertHarvey Вы правы, извините за плохой пример, и я отредактирую лучший, когда у меня будет время. В любом случае ухудшается читаемость и удобство обслуживания, и в итоге SRP выходит из строя в кодовой базе, когда мы сокращаем классы и их методы.
lucasgcb
4
обратите внимание, что вызовы функций являются дорогостоящими на любом языке , хотя компиляторы AOT могут позволить себе такую ​​роскошь
Eevee
6
Используйте JIT-реализацию Python, такую ​​как PyPy. В основном следует решить эту проблему.
Бакуриу

Ответы:

17

просто Python плохо справляется с обработкой кода?

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

Существует обходной путь, Cython, который является скомпилированной версией Python и намного быстрее.

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

  1. Не оптимизируйте, пока у вас есть проблемы, а затем ищите узкие места

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

  2. Это всего лишь несколько миллисекунд, другие вещи будут медленнее

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

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

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

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

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

Ewan
источник
1
Хотя «Ответ Роберта» помогает охватить некоторые основания для потенциальных недоразумений, связанных с выполнением такого рода оптимизации (что соответствует этому вопросу ), я чувствую, что это отвечает на ситуацию немного более прямо и в соответствии с контекстом Python.
lucasgcb
2
извините, это несколько коротко. У меня нет времени, чтобы написать больше. Но я думаю, что Роберт ошибается в этом. Лучший совет с питоном , кажется, в профиль , как вы код. Не думайте, что это будет производительным и оптимизировать, только если вы обнаружите проблему
Ewan
2
@ Иван: Вам не нужно сначала писать всю программу, чтобы последовать моему совету. Один или два метода более чем достаточны для получения адекватного профилирования.
Роберт Харви
1
Вы также можете попробовать pypy, который является JITted Python
Eevee
2
@Ewan Если вы действительно беспокоитесь о производительности из-за вызовов функций, то, что вы делаете, вероятно, не подходит для Python. Но тогда я действительно не могу придумать много примеров там. Подавляющее большинство бизнес-кода ограничено вводом-выводом, а тяжелые ресурсы процессора обычно обрабатываются путем вызова собственных библиотек (numpy, tenorflow и т. Д.).
Во
50

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

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

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

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


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

Роберт Харви
источник
3
Я думаю, что это было правдой, пока мы не изобрели облачные вычисления. Теперь одна из двух функций фактически стоит в 4 раза больше, чем другая
Ewan
2
@ Иван 4 раза может не иметь значения, пока вы не измерили его достаточно значимым, чтобы о нем заботиться. Если Foo занимает 1 мс, а Bar - 4 мс, это нехорошо. Пока вы не поймете, что передача данных по сети занимает 200 мс. В этот момент Бар медленнее не имеет большого значения. (Только один возможный пример того, что замедление в X раз не имеет заметного или значительного влияния, не обязательно должно быть супер-реалистичным.)
Becuzz
8
@Ewan Если сокращение счета сэкономит вам 15 долл. США в месяц, но подрядчику потребуется 125 долл. США в час на его исправление и проверку, я могу легко обосновать, что не стоит потратить время на бизнес (или, по крайней мере, сделать это неправильно). сейчас, если время выхода на рынок имеет решающее значение и т. д.). Всегда есть компромиссы. И то, что имеет смысл в одном обстоятельстве, может не иметь значения в другом.
Becuzz
3
Ваши счета AWS очень низкие
Ewan
6
@Ewan AWS округляет до потолка партиями в любом случае (стандарт составляет 100 мс). Это означает, что такая оптимизация спасет вас только в том случае, если она последовательно не будет подталкивать вас к следующему фрагменту.
Delioth
2

Сначала некоторые уточнения: Python - это язык. Существует несколько различных интерпретаторов, которые могут выполнять код, написанный на языке Python. Ссылочная реализация (CPython) обычно является той, на которую ссылаются, когда кто-то говорит о «Python», как будто это реализация, но важно быть точным, говоря о характеристиках производительности, поскольку они могут сильно отличаться в разных реализациях.

Как и где мы можем использовать SRP без ущерба для производительности в Python, поскольку его внутренняя реализация напрямую влияет на него?

Случай 1.) Если у вас есть чистый код Python (<= Python Language version 3.5, 3.6 имеет «поддержку бета-уровня»), который опирается только на чистые модули Python, вы можете использовать SRP везде и использовать PyPy для его запуска. PyPy ( https://morepypy.blogspot.com/2019/03/pypy-v71-released-now-uses-utf-8.html ) является интерпретатором Python, который имеет компилятор Just in Time (JIT) и может удалить функцию накладные расходы при условии, что у них достаточно времени для «прогрева» путем отслеживания исполняемого кода (несколько секунд IIRC). **

Если вы ограничены использованием интерпретатора CPython, вы можете извлечь медленные функции в расширения, написанные на C, которые будут предварительно скомпилированы и не будут подвержены каким-либо затратам интерпретатора. Вы по-прежнему можете использовать SRP везде, но ваш код будет разделен между Python и C. Это лучше или хуже для удобства сопровождения, чем выборочное отказ от SRP, но придерживаться только кода Python зависит от вашей команды, но если у вас есть критические для производительности разделы вашего код, это, несомненно, будет быстрее, чем даже самый оптимизированный чистый Python-код, интерпретируемый CPython. Многие из самых быстрых математических библиотек Python используют этот метод (numpy и scipy IIRC). Который - хороший переход в Случай 2 ...

Случай 2.) Если у вас есть код Python, который использует расширения C (или полагается на библиотеки, которые используют расширения C), PyPy может или не может быть полезным в зависимости от того, как они написаны. См. Http://doc.pypy.org/en/latest/extending.html для получения подробной информации, но в целом сводится к тому, что CFFI имеет минимальные издержки, тогда как CTypes медленнее (использование его с PyPy может быть даже медленнее, чем CPython)

Cython ( https://cython.org/ ) - еще один вариант, с которым у меня не так много опыта. Я упоминаю это для полноты картины, чтобы мой ответ мог «стоять сам по себе», но не требует какой-либо экспертизы. Из-за моего ограниченного использования я чувствовал, что мне пришлось работать усерднее, чтобы получить те же улучшения скорости, которые я мог получить «бесплатно» с PyPy, и если мне нужно что-то лучше, чем PyPy, было бы так же легко написать свое собственное расширение C ( что имеет преимущество, если я повторно использую код в другом месте или извлекаю его часть в библиотеку, весь мой код все еще может работать под любым интерпретатором Python и не должен запускаться Cython).

Я боюсь быть "заблокированным" в Cython, тогда как любой код, написанный для PyPy, также может работать под CPython.

** Некоторые дополнительные заметки о PyPy в производстве

Будьте очень осторожны при принятии любых решений, которые имеют практический эффект «привязки» к PyPy в большой кодовой базе. Поскольку некоторые (очень популярные и полезные) сторонние библиотеки не работают хорошо по причинам, упомянутым ранее, это может привести к очень трудным решениям позже, если вы поймете, что вам нужна одна из этих библиотек. Мой опыт в основном заключается в использовании PyPy для ускорения некоторых (но не всех) микросервисов, которые чувствительны к производительности в корпоративной среде, где это добавляет незначительную сложность в нашу производственную среду (у нас уже развернуто несколько языков, некоторые с разными основными версиями, такими как 2.7 против 3,5 работает в любом случае).

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

Стивен Джексон
источник
Да! В этом случае я рассматривал расширения C, вместо того, чтобы отказываться от принципа и писать дикий код, другие ответы создавали у меня впечатление, что это будет медленно, несмотря на то, что я не поменялся местами с эталонным интерпретатором. Чтобы очистить его, ООП все равно быть разумным подходом на ваш взгляд?
lucasgcb
1
в случае 1 (2-й абзац) вы не получаете то же самое при вызове функций, даже если сами функции выполняются?
Ewan
CPython - единственный интерпретатор, к которому обычно относятся серьезно. PyPy интересен , но он, конечно, не встречает широкого распространения. Кроме того, его поведение отличается от CPython и не работает с некоторыми важными пакетами, например, scipy. Немногие вменяемые разработчики рекомендуют PyPy для производства. Таким образом, различие между языком и реализацией несущественно на практике.
jpmc26
Я думаю, что ты ударил ноготь по голове, хотя. Нет причины, по которой у вас не было бы лучшего интерпретатора или компилятора. Это не свойственно Python как языку. Вы просто застряли с практическими реалиями
Ewan
@ jpmc26 Я использовал PyPy на производстве и рекомендую подумать об этом другим опытным разработчикам. Он отлично подходит для микросервисов, использующих falconframework.org для облегченных API отдыха (в качестве одного примера). Поведение отличается, потому что разработчики полагаются на детали реализации, которые НЕ являются гарантией языка, не является причиной, чтобы не использовать PyPy. Это причина переписать ваш код. Тот же самый код может все равно сломаться, если CPython вносит изменения в свою реализацию (что он может делать до тех пор, пока он все еще соответствует спецификации языка).
Стивен Джексон