为什么将键按顺序插入到python dict中比在无序时更快

时间:2013-08-14 06:39:07

标签: python performance dictionary

我一直在创建巨大的dicts(数百万条目),我注意到如果我用密钥创建它们的速度要快得多。

我认为它与哈希函数的冲突有关,但有人可以解释它为什么会发生以及它是否在python版本中是一致的?

这里有一个人为的例子:

import timeit
import random

def get_test_data(num, size):
    olist, ulist = [], []
    for _ in range(num):
        otest = [str(i) for i in range(size)]
        utest = list(otest)
        random.shuffle(utest)
        olist.append(otest)
        ulist.append(utest)
    return olist, ulist

NUM_TESTS = 20
# Precalculate the test data so we only measure dict creation time
ordered, unordered = get_test_data(NUM_TESTS, 1000000)

def test_ordered():
    dict((k, k) for k in ordered.pop())

def test_unordered():
    dict((k, k) for k in unordered.pop())

print "unordered: ",
print timeit.timeit("test_unordered()",
                    setup="from __main__ import test_unordered, test_ordered",
                    number=NUM_TESTS)
print "ordered: ",
print timeit.timeit("test_ordered()",
                    setup="from __main__ import test_unordered, test_ordered",
                    number=NUM_TESTS)

我机器的输出始终如下:

(X)$ python /tmp/test.py 
unordered:  8.60760807991
ordered:  5.1214389801

我在ubuntu中使用Python 2.7.3精确x86_64

2 个答案:

答案 0 :(得分:8)

我几乎可以肯定这是正在发生的事情:当你第一次创建otest时,字符串将按顺序存储在内存中。创建utest时,字符串指向相同的内存缓冲区,但现在这些位置无序,这会破坏无序测试用例的缓存性能。

这是证据。我用这个版本替换了你的get_test_data函数:

def get_test_data(num, size):
    olist, ulist = [], []
    for _ in range(num):
        nums = range(size)
        random.shuffle(nums)
        utest = [str(i) for i in nums]
        otest = list(utest)
        otest.sort(key=lambda x: int(x))
        olist.append(otest)
        ulist.append(utest)
    return olist, ulist

我的想法是,我现在在内存中连续ulist构建字符串,然后通过使用适当的键对这些字符串进行排序来构建olist。在我的机器上,这会逆转两次测试的运行时间。

答案 1 :(得分:2)

检查source code of the python dict您可以看到连续的字符串或整数会减少冲突。这与@skishore关于更好的缓存区域性的评论结合起来可能就是答案。

  

未来的主要微妙之处:大多数哈希计划依赖于“好”   哈希函数,在模拟随机性的意义上。 Python没有:   它最重要的哈希函数(对于字符串和整数)非常有用   常见的常见情况:

>>> map(hash, (0, 1, 2, 3))
[0, 1, 2, 3]
>>> map(hash, ("namea", "nameb", "namec", "named"))
[-1658398457, -1658398460, -1658398459, -1658398462]
>>>
     

这不一定是坏事!相反,在一张2 ** i的表格中,   将低位i位作为初始表索引是极其重要的   快速,并且对于由a索引的dicts完全没有冲突   连续的整数范围。键是大致相同的   “连续”字符串。所以这给出了比随机更好的行为   常见的情况,这是非常可取的。