Почему явные вызовы магических методов медленнее, чем синтаксис «sugared»?

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

def __hash__(self): return self.foo.__hash__() 

Однако после тестирования я обнаружил, что hash(self.foo) заметно быстрее. Любопытно, что я тестировал __eq__ , __ne__ и другие магические сравнения, только чтобы обнаружить, что все они бежали быстрее, если я использовал сладкие формы ( == != , < т. Д.). Почему это? Я предположил, что сахаризованная форма должна была бы вызвать тот же вызов функции под капотом, но, возможно, это не так?

Результаты тайм-аута

Установки: тонкие обертки вокруг атрибута экземпляра, который контролирует все сравнения.

 Python 3.3.4 (v3.3.4:7ff62415e426, Feb 10 2014, 18:13:51) [MSC v.1600 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>> import timeit >>> >>> sugar_setup = '''\ ... import datetime ... class Thin(object): ... def __init__(self, f): ... self._foo = f ... def __hash__(self): ... return hash(self._foo) ... def __eq__(self, other): ... return self._foo == other._foo ... def __ne__(self, other): ... return self._foo != other._foo ... def __lt__(self, other): ... return self._foo < other._foo ... def __gt__(self, other): ... return self._foo > other._foo ... ''' >>> explicit_setup = '''\ ... import datetime ... class Thin(object): ... def __init__(self, f): ... self._foo = f ... def __hash__(self): ... return self._foo.__hash__() ... def __eq__(self, other): ... return self._foo.__eq__(other._foo) ... def __ne__(self, other): ... return self._foo.__ne__(other._foo) ... def __lt__(self, other): ... return self._foo.__lt__(other._foo) ... def __gt__(self, other): ... return self._foo.__gt__(other._foo) ... ''' 

тесты

Мой пользовательский объект обертывает datetime , поэтому я использовал это, но это не должно иметь никакого значения. Да, я создаю даты в тестах, поэтому, очевидно, есть некоторые связанные с ними накладные расходы, но эти накладные расходы постоянны от одного теста к другому, поэтому он не должен иметь значения. Я пропустил __ne__ и __gt__ для краткости, но эти результаты были в основном идентичны показанным здесь.

 >>> test_hash = '''\ ... for i in range(1, 1000): ... hash(Thin(datetime.datetime.fromordinal(i))) ... ''' >>> test_eq = '''\ ... for i in range(1, 1000): ... a = Thin(datetime.datetime.fromordinal(i)) ... b = Thin(datetime.datetime.fromordinal(i+1)) ... a == a # True ... a == b # False ... ''' >>> test_lt = '''\ ... for i in range(1, 1000): ... a = Thin(datetime.datetime.fromordinal(i)) ... b = Thin(datetime.datetime.fromordinal(i+1)) ... a < b # True ... b < a # False ... ''' 

Результаты

 >>> min(timeit.repeat(test_hash, explicit_setup, number=1000, repeat=20)) 1.0805227295846862 >>> min(timeit.repeat(test_hash, sugar_setup, number=1000, repeat=20)) 1.0135617737162192 >>> min(timeit.repeat(test_eq, explicit_setup, number=1000, repeat=20)) 2.349765956168767 >>> min(timeit.repeat(test_eq, sugar_setup, number=1000, repeat=20)) 2.1486044757355103 >>> min(timeit.repeat(test_lt, explicit_setup, number=500, repeat=20)) 1.156479287717275 >>> min(timeit.repeat(test_lt, sugar_setup, number=500, repeat=20)) 1.0673696685109917 
  • Hash:
    • Явно: 1.0805227295846862
    • Сугаред: 1.0135617737162192
  • Равный:
    • Явно: 2.349765956168767
    • Sugared: 2.1486044757355103
  • Меньше, чем:
    • Явный: 1.156479287717275
    • Sugared: 1.0673696685109917

One Solution collect form web for “Почему явные вызовы магических методов медленнее, чем синтаксис «sugared»?”

Две причины:

  • API-запросы смотрят только на тип . Они не смотрят на self.foo.__hash__ , они ищут type(self.foo).__hash__ . Это один из немногих словарей для поиска.

  • __getattribute__ C-слоте быстрее, чем поиск атрибута pure-Python (который будет использовать __getattribute__ ); вместо этого поиск объектов метода (включая привязку дескриптора) выполняется полностью в C, минуя __getattribute__ .

Поэтому вам придется кэшировать поиск type(self._foo).__hash__ локально, и даже тогда вызов будет не таким быстрым, как с C-кода. Просто придерживайтесь стандартных функций библиотеки, если скорость стоит на высоте.

Еще одна причина, по которой не следует напрямую обращаться к магическим методам, заключается в том, что операторы сравнения не просто называют один магический метод; методы также отразили версии; для x < y , если x.__lt__ не определено, или x.__lt__(y) возвращает NotImplemented Singleton, y.__gt__(x) NotImplemented y.__gt__(x) .

  • Почему соединение происходит быстрее обычного конкатенации
  • Медленная производительность с мешком Python Dask?
  • Как работают курсоры в DB-API Python?
  • Как сократить время, затрачиваемое на загрузку файла pickle в python
  • Почему здесь происходит клонирование индексации?
  • Каков самый быстрый способ подготовки данных для RNN с numpy?
  • Разница в производительности между numpy и matlab
  • Самый эффективный способ сделать заявление if-elif-elif-else, когда else сделан больше всего?
  • Ускорить подсчет близлежащих групп?
  • Эффективность искры для Scala vs Python
  • Как повысить эффективность с помощью массивов numpy?
  • Python - лучший язык программирования в мире.