最好使用整数键的dict还是很长的列表?

时间:2014-06-23 05:45:14

标签: python python-2.7

基本上,我需要创建一个包含非连续整数ID的查找表。我想知道,就查找速度而言,我通常最好不要使用带有整数键的dict,或者使用带有大量空索引的非常长的list。在我看来,list可能仍然会更快,因为Python应该确切知道在哪里看,但我想知道是否有任何后端进程与dict进行补偿以及是否有额外的内存对那些空list个插槽的要求会否定(可能)更容易遍历list的速度增益。是否有listdict的替代品可能更适合这种情况?

我已经看到了这个问题,但它并没有完全回答我的问题:Dictionary access speed comparison with integer key against string key

ETA:我在我的程序中实现了两次这样的查找表。一个实例看到最大id为5,000,其中填充了70-100个对象;另一个的最大ID为750,其中20-30填充。

3 个答案:

答案 0 :(得分:11)

要回答有关dict vs list的问题,您必须提供有关元素数量,缺失元素数量等的完全信息,因此,我们可以准确估计两个数据结构的内存使用情况,并尝试预测和/或检查它们的性能。

一般来说,dictN键值对比listN所需的内存要多得多:

  • dict必须跟踪密钥
  • dict永远不会超过2/3。发生这种情况时,分配的内存加倍(这需要在dict上进行O(1)摊销时间操作。

然而可以替代这些数据结构,它们应该提供非常好的性能:blistblist包提供了与list匹配的接口,只有使用B树实现。它可以有效地处理稀疏列表。大多数操作都需要O(1)O(log n)时间,因此它们非常有效。

例如,您可以先创建稀疏blist

from blist import blist

seq = blist([None])
seq *= 2**30    # create a 2**30 element blist. Instantaneous!

然后您只能设置具有值的索引:

for i, value in zip(indices, values):
    seq[i] = value

完整文档为here

请注意blist提供了其他有效的操作,例如:

  • 连接两个blist s O(log n)时间
  • 拍摄[i:j]切片需要O(log n)时间
  • 在给定索引处插入元素需要O(log n)次操作
  • 弹出元素(从每个位置)执行O(log n)次操作

由于您提供了一些数字,以下是与dict的对比方式:

>>> from blist import blist
>>> b = blist([None])
>>> b *= 5000
>>> for i in range(100):b[i] = i
... 
>>> b.__sizeof__()
2660
>>> d = dict()
>>> for i in range(100):d[i] = i
... 
>>> d.__sizeof__()
6216
>>> b = blist([None])
>>> b *= 750
>>> for i in range(30):b[i] = i
... 
>>> b.__sizeof__()
1580
>>> d = dict()
>>> for i in range(30):d[i] = i
... 
>>> d.__sizeof__()
1608

在这两种情况下,blist占用的内存较少(在第一种情况下,占用等效dict的内存的1/3)。请注意,blist占用的内存也取决于索引是否连续(连续更好)。然而,即使使用随机索引:

>>> b = blist([None])
>>> b *= 5000
>>> import random
>>> for i in range(100):b[random.randint(0, 4999)] = i
... 
>>> b.__sizeof__()
2916

它仍然比dict好得多。

即使查找时间更好:

In [1]: from blist import blist
   ...: import random
   ...: 

In [2]: b = blist([None])

In [3]: b *= 5000

In [4]: for i in range(100):b[random.randint(0, 4999)] = i

In [5]: %timeit b[0]
10000000 loops, best of 3: 50.7 ns per loop

In [6]: d = dict()

In [7]: for i in range(100):d[random.randint(0, 4999)] = i

In [10]: %timeit d[1024]   # 1024 is an existing key in this dictionary
10000000 loops, best of 3: 70.7 ns per loop

In [11]: %timeit b[1024]
10000000 loops, best of 3: 50.7 ns per loop

请注意,list需要大约47 ns才能在我的计算机上查找索引,因此blist在小型查找性能方面非常接近list列出你所拥有的。

答案 1 :(得分:1)

<强>解释
 1.列表末尾的appendpop速度很快  2.列表开头的insertpop速度很慢(这两个函数后面有一个很重的操作)
 3.最好使用collection.degue作为第二种情况。

字典:
 4.与列表相比,访问操作更快



循环浏览词典和列表:

  1. 字典使用iteritems()方法同时检索密钥及其对应的值。
  2. 列表使用enumerate()用于相同目的。

    备注:
  3. 如果你的问题只是关于循环速度,iteritems()和enumerate()
  4. 之间没有实质性区别
  5. iteritems()在Python 3.x中是removed
  6. zip()方法是一个很难避免的过程。

答案 2 :(得分:1)

我认为这个问题没有一般性答案。它取决于整数id,可用内存和性能要求的重新分区。规则是:

  • 列表查找速度更快,因为您不必计算密钥的哈希值。
  • 如果密钥的最大值很大,则字典可能更紧凑
  • 如果您的最大密钥非常大(大约2 ^ 30),您将浪费大量内存,系统将开始交换,这会大大降低性能

这可能是一条经验法则:

  • 如果有&#34;少数&#34;如果您知道最大的密钥将是&#34;合理的&#34;低(相对于你接受花在其上的记忆)=&gt;使用清单
  • 如果未验证以下要求且您没有性能要求=&gt;使用词典
  • 如果前面两个假设都不成立,你将不得不尝试一些散列函数优化 - 我在下面详述

dict的理论是一个数组,其索引是应用于键的散列函数的结果。 Python algorythm已正确优化,但它是一个通用的。如果您知道自己有特殊的重新分区,可以尝试找到一个专门适用于您的重新分区的哈希值。您可以在Hash functions上的维基百科文章或旧的标准C库hash

上找到更多信息。