为什么dict查找总是比列表查找更好?

时间:2017-04-28 23:32:13

标签: python performance optimization big-o python-internals

我使用字典作为查找表但我开始怀疑列表是否会更适合我的应用程序 - 查找表中的条目数量并不大。我知道列表在引擎盖下使用C数组,这使我得出结论,在列表中只查找几个项目比在字典中更好(访问数组中的一些元素比计算哈希更快)。

我决定介绍其他选择,但结果让我感到惊讶。列表查找只有单个元素才能更好!请参见下图(对数 - 对数图):

list vs dict lookup time

所以问题就出现了: 为什么列表查找效果不佳? 我缺少什么?

在一个侧面问题上,引起我注意的其他事情是一点点"不连续"在大约1000个条目之后的dict查找时间。我单独绘制了dict查找时间以显示它。

dict lookup time

ps1我知道O(n)vs O(1)数组和散列表的摊销时间,但通常情况下,迭代数组的少量元素比使用更好哈希表。

p.s.2以下是我用来比较字典和列表查找时间的代码:

import timeit

lengths = [2 ** i for i in xrange(15)]

list_time = []
dict_time = []
for l in lengths:
    list_time.append(timeit.timeit('%i in d' % (l/2), 'd=range(%i)' % l))
    dict_time.append(timeit.timeit('%i in d' % (l/2),
                                   'd=dict.fromkeys(range(%i))' % l))
    print l, list_time[-1], dict_time[-1]

p.s.3使用Python 2.7.13

3 个答案:

答案 0 :(得分:49)

  

我知道列表在引擎盖下使用C数组,这使我得出结论,在列表中只查找几个项目比在字典中更好(访问数组中的一些元素比计算哈希更快)。 / p>

访问一些数组元素很便宜,当然,但计算==在Python中是非常重要的。在第二张图中看到那个尖峰?这就是在那里为两个整数计算==的成本。

您的列表查找需要比您的dict查找计算==更多。

与此同时,对于很多对象来说,计算哈希可能是一个非常重要的操作,但是对于这里涉及的所有内容,它们只是哈希自己。 (-1将散列为-2,大整数(技术上long s)将散列为较小的整数,但这不适用于此。)

Dict查找在Python中并不是那么糟糕,特别是当你的密钥只是一个连续的整数范围时。这里的所有int都是哈希,而Python使用自定义的开放寻址方案而不是链接,所以你的所有键在内存中几乎就像你使用列表一样连续(也就是说,指向键的指针结束)在PyDictEntry s)的连续范围内。查找过程很快,在您的测试用例中,它总是在第一个探测器上点击正确的键。

好的,回到图2中的尖峰。第二个图中1024个条目的查找时间的峰值是因为对于所有较小的大小,你要查找的整数都是< = 256,所以它们都掉了在CPython的小整数缓存范围内。 Python的参考实现为-5到256(包括5和256)的所有整数保留规范整数对象。对于这些整数,Python能够使用快速指针比较来避免经历(令人惊讶的重量级)计算==的过程。对于较大的整数,in的参数不再是dict中匹配整数的对象,Python必须经历整个==过程。

答案 1 :(得分:22)

简短的回答是列表使用线性搜索,而dicts使用摊销的O(1)搜索。

此外,当1)哈希值不匹配时,或者当存在身份匹配时,dict搜索可以跳过相等性测试。列表仅受益于身份意味着平等优化。

早在2008年,我就这个主题发表了演讲,您可以在其中找到所有细节:https://www.youtube.com/watch?v=hYUsssClE94

搜索列表的逻辑大致是:

for element in s:
    if element is target:
        # fast check for identity implies equality
        return True
    if element == target:
        # slower check for actual equality
        return True
return False

对于dicts,逻辑大致是:

h = hash(target)
for i in probe_sequence(h, len(table)):
    element = key_table[i]
    if element is UNUSED:
        raise KeyError(target)
    if element is target:
        # fast path for identity implies equality
        return value_table[i]
    if h != h_table[i]:
        # unequal hashes implies unequal keys
        continue
    if element == target:
        # slower check for actual equality
        return value_table[i]

字典哈希表通常在三分之一到三分之二的范围内,因此无论大小如何,它们往往几乎没有碰撞(上面显示的循环几次)。此外,哈希值检查可以防止不必要的慢速等式检查(浪费的相等性检查在2 ** 64中大约为1)。

如果你的时间关注整数,那么还有其他一些效果。 int的散列是int本身,因此散列非常快。此外,这意味着如果您存储连续的整数,则根本不会发生冲突。

答案 2 :(得分:0)

你说"访问数组中的一些元素比计算哈希值更快#34;。

字符串的简单散列规则可能只是一个总和(最后有一个模数)。这是一个无分支操作,可以与字符比较相媲美,特别是当前缀长匹配时。