使用用户定义的对象作为键时,为什么Python中的字典查找总是较慢?

时间:2018-09-01 11:56:23

标签: python performance dictionary hash lookup

我注意到,当我使用用户定义的对象(覆盖__hash__方法)作为Python中字典的键时,查找时间至少增加了5倍。

即使当我使用非常基本的哈希方法(例如以下示例)时,也会观察到此行为:

class A:
    def __init__(self, a):
        self.a = a
    def __hash__(self):
        return hash(self.a)
    def __eq__(self, other):
        if not isinstance(other, A):
            return NotImplemented
        return (self.a == other.a and self.__class__ == 
                other.__class__)

# get an instance of class A
mya = A(42)
# define dict
d1={mya:[1,2], 'foo':[3,4]}

如果我通过两个不同的键对访问进行计时,则会发现性能存在明显差异

%timeit d1['foo']

导致〜100 ns。而

%timeit d1[mya]

导致〜600 ns。

如果我删除了__hash____eq__方法的覆盖,则性能与默认对象处于同一水平

有没有办法避免这种性能损失并且仍然实现自定义的哈希计算?

1 个答案:

答案 0 :(得分:5)

自定义类的默认CPython __hash__实现是用C编写的,并使用对象的内存地址。因此,它不必绝对访问对象,并且可以非常快速地完成操作,因为即使在CPU中,它也只是单个整数运算。

该示例中的“非常基础” __hash__并不像看起来那么简单:

def __hash__(self):
    return hash(self.a)

这必须读取a的属性self,在这种情况下,我将其称为object.__getattribute__(self, 'a'),并且它将在{中查找'a'的值{1}}。这已经涉及到计算__dict__并进行查找。然后,返回的值将传递到hash('a')


要回答其他问题:

  

有没有一种方法可以实现更快的hash方法,该方法返回   可预测的值,我的意思是不是每次运行都随机计算   就像对象的内存地址一样?

任何访问对象属性的速度都会比不需要访问属性的实现慢,但是您可以通过使用__slots__或为该类实现高度优化的C扩展来使属性访问更快。

但是,还有另一个问题:这真的有问题吗?我真的不能相信由于__hash__缓慢而导致应用程序变慢。除非字典中有数以万计的条目,否则__hash__仍然应该非常快,但随后,其他所有内容将变慢并要求进行更大的更改...


我做了一些测试,必须进行更正。在这种情况下,使用__hash__根本无济于事。我的测试实际上表明,在CPython 3.7中,使用__slots__时,上述类变得稍微