Попытка / улов или проверка на скорость?

#python #performance #exception-handling #typechecking

#python #Производительность #исключение #проверка типов

Вопрос:

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

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

Этот код используется для raytracer, поэтому любое повышение скорости является отличным.

Вот упрощенная версия моего Vector() класса:

 class Vector:
  def __init__(self, x, y, z):
    self.x = x
    self.y = y
    self.z = z

  def __add__(self, other):
    try:
      return Vector(self.x   other.x, self.y   other.y, self.z   other.z)
    except AttributeError:
      return Vector(self.x   other, self.y   other, self.z   other)
  

В настоящее время я использую try...except метод. Кто-нибудь знает более быстрый метод?


РЕДАКТИРОВАТЬ: Благодаря ответам я попробовал и протестировал следующее решение, которое специально проверяет имя класса перед добавлением Vector() объектов:

 class Vector:
  def __init__(self, x, y, z):
    self.x = x
    self.y = y
    self.z = z

  def __add__(self, other):
    if type(self) == type(other):
      return Vector(self.x   other.x, self.y   other.y, self.z   other.z)
    else:
      return Vector(self.x   other, self.y   other, self.z   other)
  

Я провел тест скорости с использованием этих двух блоков кода timeit , и результаты были довольно значительными:

  1.0528049469 usec/pass for Try...Except
 0.732456922531 usec/pass for If...Else
 Ratio (first / second): 1.43736090753
  

Я не тестировал этот Vector() класс не проверка входных бы то ни было (т. е. перемещение проверить класса и в фактическом коде), но я предполагаю, что это даже быстрее, чем if...else метод.


Позднее обновление: Оглядываясь назад на этот код, это не оптимальное решение.

ООП делает это еще быстрее:

 class Vector:
  def __init__(self, x, y, z):
    self.x = x
    self.y = y
    self.z = z

  def __add__(self, other):
    return Vector(self.x   other.x, self.y   other.y, self.z   other.z)

class Color(Vector):
  def __add__(self, other):
    if type(self) == type(other):
      return Color(self.x   other.x, self.y   other.y, self.z   other.z)
    else:
      return Color(self.x   other, self.y   other, self.z   other)
  

Комментарии:

1. Пожалуйста, используйте timeit и включите реальные результаты синхронизации ваших двух альтернатив.

2. Вы знаете, что два примера выполняют разные функции? Неясно, что вы измеряете, поскольку они несопоставимы.

3. Я опубликую свой код. Не думайте, что я вручную вводил 'a' 1 000 000 раз в окно терминала 😉

4. Ваша if проверка не будет работать для отрицательных чисел, таких как ‘-1’

5. @Blender: Пожалуйста, сосредоточьтесь на этом вопросе. Ваш if ‘a’.isdigit() совершенно неуместен и сбивает с толку. Пожалуйста, удалите это. Если ваша «настоящая» проблема заключается в вашем VectorCheck классе, тогда просто покажите это. Кроме того, не возитесь с причудливыми «линейными выводами» и другой разметкой. ТАКИМ образом, поддерживается полный список изменений. Пожалуйста, просто сосредоточьтесь на одной вещи. Упакуйте свой реальный вопрос во что-то, чему другие могут научиться.

Ответ №1:

Я поддержал ответ Мэтта Джойнера, но хотел бы включить некоторые дополнительные замечания, чтобы прояснить, что наряду с парой других факторов, при выборе между условиями предварительной проверки (известными как LBYL или «Посмотри, прежде чем прыгать») и просто обработкой исключений (известных как EAFP или «Легче попросить прощения, чем разрешения») имеют значение 4 раза.»

Эти тайминги:

  • Время, когда проверка завершается успешно с помощью LBYL
  • Время, когда проверка завершается неудачей с помощью LBYL
  • Время, когда исключение не генерируется с помощью EAFP
  • Время, когда с помощью EAFP создается исключение

Дополнительными факторами являются:

  • Типичное соотношение случаев успешной проверки / сбоя или исключения, вызванного / не вызванного
  • Существует ли условие гонки, которое предотвращает использование LBYL

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

 if <dir does not exist>:
    <create dir> # May still fail if another process creates the target dir
  

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

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

  • если исключение не вызвано, то EAFP близок к свободному
  • однако, если возникает исключение, это сравнительно дорого, поскольку требуется довольно много обработки, связанной с развертыванием стека, созданием исключения и сравнением его с предложениями обработки исключений
  • LBYL, напротив, сопряжен с потенциально высокими фиксированными затратами: дополнительная проверка выполняется всегда, независимо от успеха или неудачи

Это затем приводит к следующим критериям принятия решения:

  • Известно ли, что этот фрагмент кода имеет решающее значение для скорости приложения? Если нет, то не беспокойтесь о том, какой из двух быстрее, беспокойтесь о том, какой из двух легче читать.
  • Является ли предварительная проверка более дорогостоящей, чем стоимость создания и перехвата исключения? Если да, то EAFP всегда быстрее и его следует использовать.
  • Все становится интереснее, если ответ «нет». В этом случае, что быстрее, будет зависеть от того, является ли случай успеха или ошибки более распространенным, а также от относительных скоростей предварительной проверки и обработки исключений. Для окончательного ответа на этот вопрос требуются измерения реального времени.

Как грубое эмпирическое правило:

  • если существует потенциальное условие гонки, используйте EAFP
  • если скорость не критична, просто используйте то, что, по вашему мнению, легче читать
  • если предварительная проверка обходится дорого, используйте EAFP
  • если вы ожидаете, что операция в большинстве случаев будет успешной *, используйте EAFP
  • если вы ожидаете, что операция завершится неудачно более чем в половине случаев, используйте LBYL
  • если сомневаетесь, измерьте это

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

Комментарии:

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

2. @Blender: «переместите всю проверку типов за пределы класса». Универсально верно. Не только для производительности, но и для простоты в целом. Классы просто работают, они не проверяют.

3. Привет. Я не совсем понимаю последний пункт С. Лотта о переносе проверки типов за пределы класса. Как это удаляет строки кода? Как я предполагаю, вам тогда придется выполнять проверку типа везде, где вы используете класс, а не просто делать это один раз в классе.

4. @Jonathon: Я полагаю, что его точка зрения заключается в том, что, помимо явных процедур проверки, часто лучше следовать правилам «Мусор в мусор из» и просто позволять собственным исключениям Python избегать, если люди передают вам бессмысленные аргументы. Python довольно хорош в том, чтобы убедиться, что вы получите исключение в таких случаях вместо того, чтобы молча создавать мусорные данные — только в (относительно редких) последних случаях и случаях, когда ошибки в противном случае сбивают с толку, вам нужно встроить собственную проверку в операции.

5. Недавно мне стало любопытно по этому поводу, поэтому я провел несколько экспериментов. Результаты: gerg.ca/blog/post/2015/try-except-speed . Краткая версия: то, что сказал @ncoghlan, т. Е. исключения дешевле, пока ошибки редки.

Ответ №2:

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

В приведенном вами примере я бы выбрал исключение.

Комментарии:

1. Я работаю с raytracer; иногда я получаю вывод 0.0 , в то время как в других случаях я получаю Vector(1, 0, 1.5) . Мне нужно знать тип, поскольку каждый случай обрабатывается по-разному. Итак, вы рекомендуете мне не делать try...except этого?

2. Ну, я изменил свои try блоки на if blocks, и я получил увеличение скорости в два раза! Спасибо!