Я возился с небольшим пользовательским объектом данных, который должен быть хешируемым, сопоставимым и быстрым, когда я столкнулся с нечетным набором результатов синхронизации. Некоторые из сравнений (и метод хэширования) для этого объекта просто делегируют атрибуту, поэтому я использовал что-то вроде:
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
- Sugared: 1.0135617737162192
- Равный:
- Явный: 2.349765956168767
- Sugared: 2.1486044757355103
- Меньше чем:
- Явный: 1.156479287717275
- Sugared: 1.0673696685109917