为什么我的dict查找不比我在Python中的列表查找更快?

时间:2014-01-18 03:34:57

标签: python list python-2.7 dictionary python-internals

我正在将文件的每一行读入列表和字典,

with open("../data/title/pruned2_titleonly.txt", 'rb') as f_titles:
    titles_lst = f_titles.read().split('\n')
assert titles_lst[-1] == ''
titles_lst.pop() # remove the last element, an empty string

titles_dict = {}
with open("../data/title/pruned2_titleonly.txt", 'rb') as f_titles:
    for i,line in enumerate(f_titles):
        titles_dict[i] = line

我正在通过以随机顺序访问list / dict中的每个项目来测试性能:

n = len(titles_lst)
a = np.random.permutation(n)

%%time
for i in xrange(10):
    t = []
    for b in a:
        t.append(titles_lst[b])
    del t

>>> CPU times: user 18.2 s, sys: 60 ms, total: 18.2 s
>>> Wall time: 18.1 s

%%time
for i in xrange(10):
    t = []
    for b in a:
        t.append(titles_dict[b])
    del t

>>> CPU times: user 41 s, sys: 208 ms, total: 41.2 s
>>> Wall time: 40.9 s

上述结果似乎暗示字典不如查找表的列表有效,即使列表查找为O(n)而dict查找为O(1)。我已经测试了以下内容,看看O(n)/ O(1)的性能是否真实......原来它不是......

%timeit titles_lst[n/2]
>>> 10000000 loops, best of 3: 81 ns per loop

%timeit titles_dict[n/2]
>>> 10000000 loops, best of 3: 120 ns per loop

这笔交易是什么?如果需要注意的话,我在Ubuntu 12.04下使用Python 2.7.6 Anaconda发行版,我在英特尔MKL下构建了NumPy。

3 个答案:

答案 0 :(得分:7)

  

上述结果似乎暗示字典效率不高   作为查找表的列表,即使列表查找是O(n)同时   dict查找是O(1)。我测试了下面的内容,看看是否有   O(n)/ O(1)的表现是真的......事实证明它不是......

dict查找不是O(N),在“获取项目”的意义上,这是你的代码似乎要测试的意义。确定元素存在于何处(如果有的话)可以是O(N),例如, somelist.index(someval_not_in_the_list)someval_not_in_the_list in somelist都必须扫描每个元素。尝试将x in somelistx in somedict进行比较,看看是否存在重大差异。

但只是访问somelist[index]是O(1)(参见Time Complexity page)。并且系数可能会比字典小,也就是O(1),因为你不需要对密钥进行散列。

答案 1 :(得分:4)

list[]正在从列表中的特定已知位置中获取内容 - O(1)

dict[]使用密钥搜索字典 - 平均为O(1)

如果您想比较搜索,请尝试搜索列表 - O(n)

[x for x in list if x==val]

看到真正的差异

答案 2 :(得分:4)

  

交易是什么?

此行为的原因是,对于字典访问,索引(键)被散列,然后使用散列来访问散列表中的值。在列表中,索引是到列表中相应插槽的直接映射,这实际上是计算到内存中的偏移量。

分别参见dictionarieslists的实施。

  

上述结果似乎暗示字典效率不高   作为查找表的列表,

不,我们不能得出结论。您的实验显示的是一个特定实例,将列表中的数字索引访问与哈希表中的散列密钥访问进行比较。显然,要访问字典还有很多工作要做(即散列索引)。

  

我测试了以下内容,看看是否有O(n)/ O(1)性能   真实......事实证明它不是......

我也进行了一些测试,见下文。在我们进入细节之前,请注意所有O(1)状态是访问时间与相应集合对象的大小无关。说明Wikipedia

  

算法被认为是恒定时间(也写为O(1)时间)   如果T(n)的值受一个不依赖于的值的限制   输入的大小。

换句话说,给定一些集合对象x,对该对象的所有访问都将独立于对象的大小。注意,O(1)也意味着,所有访问都将具有完全相同的运行时间。再次引用维基百科:

  

尽管名称为“恒定时间”,但运行时间并非必须如此   独立于问题大小,但运行的上限   时间必须与问题规模无关。

这里的关键词是有界。我们可以通过稍微修改您的程序来验证访问时间是否在合理的范围内。请注意,我正在生成随机输入,而不是从文件构建列表和dict对象。此外,我已将代码简化为仅测试访问权限(您的代码为每一轮构建一个新列表,这可能会改变结果的比例)。

import sha
from timeit import timeit
import random
import numpy as np

N = 1000
# generate random input of size N
titles_lst = [sha.sha(str(x)).digest() for x in range(1, N)]
titles_dict = {}
for i in range(0, len(titles_lst)):
    titles_dict[i] = titles_lst[i]
# permutate access pattern
n = len(titles_lst)
a = np.random.permutation(n)

def access_list():
    x = titles_lst[b]

def access_dict():
    x = titles_dict[b]

# run measurements for dictionary
X = []
C = 1000
for i in range(C):
    for b in a:
       X.append(timeit(access_dict, number=C))
print "dict std=%f avg=%f" % (np.std(X), np.mean(X))
# run measurements for list       
X = []
for i in range(C):
    for b in a:
       X.append(timeit(access_list, number=C))
print "list std=%f avg=%f" % (np.std(X), np.mean(X))

在我的2.7GHz macmini上,这会产生以下结果。

N=100 C=1000 dict std=0.000001 avg=0.000002
N=100 C=1000 list std=0.000001 avg=0.000001

N=1000 C=1000 dict std=0.000001 avg=0.000002
N=1000 C=1000 list std=0.000001 avg=0.000001

N=10000 C=1000 dict std=0.000001 avg=0.000002
N=10000 C=1000 list std=0.000001 avg=0.000001

显然,测试确认字典和列表确实在O(1)中有访问时间。他们还使用数字索引/键确认字典查找平均比使用数字索引/键的列表访问更慢