python - 为什么显式调用魔术方法比 "sugared"语法慢?

标签 python performance magic-methods syntactic-sugar

当我遇到一组看起来很奇怪的计时结果时,我正在处理一个需要可哈希、可比较且快速的小型自定义数据对象。这个对象的一些比较(和散列方法)只是委托(delegate)给一个属性,所以我使用了类似的东西:

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
  • 哈希:
    • 显式:1.0805227295846862
    • 加糖:1.0135617737162192
  • 等于:
    • 显式: 2.349765956168767
    • 加糖 2.1486044757355103
  • 小于:
    • 显式:1.156479287717275
    • 加糖:1.0673696685109917

最佳答案

两个原因:

  • API 查找仅查看类型。他们不看self.foo.__hash__ , 他们寻找 type(self.foo).__hash__ .这是一本少看的字典。

  • C 插槽查找比纯 Python 属性查找(将使用 __getattribute__ )更快;相反,查找方法对象(包括描述符绑定(bind))完全在 C 中完成,绕过了 __getattribute__ .

所以你必须缓存 type(self._foo).__hash__在本地查找,即使这样调用也不会像 C 代码那样快。如果速度非常重要,请坚持使用标准库函数。

避免直接调用魔术方法的另一个原因是比较运算符比调用一个魔术方法做的更多;这些方法也反射(reflect)了版本;对于 x < y , 如果 x.__lt__未定义或 x.__lt__(y)返回 NotImplemented单例,y.__gt__(x)也被咨询。

关于python - 为什么显式调用魔术方法比 "sugared"语法慢?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22940769/

相关文章:

python - 在 Pandas 中绘制子图时出现 IndexError

android - 如何在Android上实现流畅的 fragment 交易动画

python - 为另一个数组中的所有 float 查找数组中最接近的 float

performance - ASP.NET MVC : Store data in OnActionExecuting and retrieve it in OnActionExecuted in a controller

php - 如何在 PHP 中使用接口(interface)和魔术方法

Python - 访问 JSON 元素

python - 从大型 CSV 中读取随机行并写入不同的 CSV 文件

python - 为什么调用 Python 的 'magic method' 不像对应的运算符那样进行类型转换?

php - 在 PHP 中我的类的任何方法之前调用特殊方法

python - 正则表达式匹配以分号分隔的标记序列的字符串