【问题标题】:Python tuples as keys slow?Python 元组作为键慢?
【发布时间】:2012-03-10 03:16:58
【问题描述】:

我正在尝试实现对字典中排序元组的快速查找;回答“元组 (3,8) 是否具有关联值,如果是,它是什么?”的问题。让元组中的整数从下方以 0 为界,从上方以 max_int 为界。

我继续使用 Python 的 dict,但发现它非常慢。解决这个问题的另一种方法是创建一个带有 max_int(大部分为空)dicts 的列表 T,并为每个元组 (3,8) 放入 T[3][8] = value。 我虽然这正是 Python 对 dicts 采用的桶哈希方法,但后者在这里快了大约 30 倍(!)。

不过,它也很丑陋(特别是因为我现在要实现 3 元组),所以我非常感谢这里的一些提示。

作为参考,这是我用来获取时间的代码:

import numpy as np
import time

# create a bunch of sorted tuples
num_tuples = 10
max_int = 100
a = np.random.rand(num_tuples,2) * max_int
a = a.astype(int)
for k in xrange(len(a)):
    a[k] = np.sort(a[k])

# create dictionary with tuples as keys
d = {}
for t in a:
    d[tuple(t)] = 42

print d

# do some lookups
m = 100000
start_time = time.time()
for k in xrange(m):
    (3,8) in d.keys()
elapsed = time.time() - start_time
print elapsed

# now create the bucket-list structure mentioned above
t = [{} for k in xrange(max_int)]
for k in xrange(len(a)):
    t[a[k][0]][a[k][1]] = 42

print t

# do some lookups
m = 10000
start_time = time.time()
for k in xrange(m):
    8 in t[3].keys()
elapsed = time.time() - start_time
print elapsed

【问题讨论】:

  • 使用in d.keys() 而不是in d 浪费了您的大部分时间;对我来说,将时间从 1.11s/0.003s 降低到 0.018s/0.0017s。如果您将这样的优化留在桌面上,那么担心速度是愚蠢的。
  • 您可以使用timeit 来执行您的基准测试。更容易。

标签: python dictionary tuples key


【解决方案1】:

比较(a, b) in db in t[a] 的速度有点奇怪,因为后者假定a 必须存在。

无论如何,第一种方式应该总是更快。两个版本都有 ab。第一个有分配元组和散列它的额外开销。但是,第二种方法会进行两次单独的字典查找。

【讨论】:

    【解决方案2】:

    以下是 Python 2.7 的精确计时结果:

    >>> %timeit (3, 8) in d.keys()  # Slow, indeed
    100000 loops, best of 3: 9.58 us per loop
    
    >>> %timeit 8 in t[3].keys()  # Faster
    1000000 loops, best of 3: 246 ns per loop
    
    >>> %timeit (3, 8) in d  # Even faster!
    10000000 loops, best of 3: 117 ns per loop
    
    >>> %timeit 8 in t[3]  # Slightly slower
    10000000 loops, best of 3: 127 ns per loop
    

    他们表明标准的(3, 8) in d(没有.keys() 列表构建)实际上比(不太通用的)8 in t[3] 方法快一点,并且速度是相对快的两倍 8 in t[3].keys() 的问题。 .keys/no .keys 的区别在于 (3, 8) in d.keys() 构建了一个键列表(在 Python 2 中),然后在此列表中查找 (3, 8),这比查找 (3, 8) 慢得多在字典d的哈希表中。

    如 cmets 中所述,计时结果与 Python 3 不同:Python 3 的 keys() 具有快速的 in 测试,因为 keys() 会返回键视图,因此 in 运算符可以使用对应字典的哈希表。

    原始问题的速度差异来自d.keys()t[3].keys()相比建立了一个相对较长的列表。

    PS:%timeit 功能由优秀的IPython shell 提供。原程序可以通过%run prog.py通过IPython执行。

    【讨论】:

    • EOL:关键信息是.keys() 创建了一个迭代器,Python 必须使用O(n) 算法来查找(3, 8),同时使用散列摊销常数时间。
    • @nightcracker:在 Python 3 中是这样吗? KeysView 是否具有线性访问权限?这两个操作的时间非常相似。
    • @Neil G:Python 3 的好区别!在查看 collections.abc 后,我看到 KeysView 继承自 MappingViewSet,因此它确实具有快速包含检查和线性访问。
    【解决方案3】:

    您正在测试不同的值。在字典版本中,查找 100,000 个键,而在桶列表结构中,查找仅查找 10,000 个键。

    除此之外,这段代码的 sn-p 正在减慢速度:(3,8) in d.keys() 如果你只是写了(3,8) in d,那么两个版本的查找时间会非常相似,差异可以忽略不计。试试这个修改后的测试,自己看看:

    import numpy as np
    import time
    
    # create a bunch of sorted tuples
    num_tuples = 10
    max_int = 100
    a = np.random.rand(num_tuples,2) * max_int
    a = a.astype(int)
    for k in xrange(len(a)):
        a[k] = np.sort(a[k])
    
    # create dictionary with tuples as keys
    d = {}
    for t in a:
        d[tuple(t)] = 42
    
    # do some lookups
    m = 100000
    start_time = time.time()
    for k in xrange(m):
        if (3,8) in d:
            pass
    
    elapsed = time.time() - start_time
    print elapsed
    
    # now create the bucket-list structure mentioned above
    t = [{} for k in xrange(max_int)]
    for k in xrange(len(a)):
        t[a[k][0]][a[k][1]] = 42
    
    # do some lookups
    m = 100000
    start_time = time.time()
    for k in xrange(m):
        if 8 in t[3]:
            pass
    
    elapsed = time.time() - start_time
    print elapsed
    

    观察到的行为的原因是(3,8) in d.keys()8 in t[3].keys() 每次都在创建一个新的临时键列表,但第二个版本创建的列表更短。如果您只是使用惯用语 key in dictionary,则不会再创建临时列表,并且两种方法的性能开始看起来相似。

    我会选择第一个版本,它更简单、更易于阅读、理解和惯用 - 如果使用得当,性能与第二个版本一样好。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-06-20
      • 1970-01-01
      • 2019-03-17
      • 2012-04-29
      • 1970-01-01
      • 1970-01-01
      • 2016-11-22
      • 2021-04-02
      相关资源
      最近更新 更多