Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/281.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/performance/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
未预料到的Python字典行为_Python_Performance_Dictionary - Fatal编程技术网

未预料到的Python字典行为

未预料到的Python字典行为,python,performance,dictionary,Python,Performance,Dictionary,我有一段代码: import time d = dict() for i in range(200000): d[i] = "DUMMY" start_time = time.time() for i in range(200000): for key in d: if len(d) > 1 or -1 not in d: break del d[i] print("--- {} seconds ---".format

我有一段代码:

import time

d = dict()
for i in range(200000):
    d[i] = "DUMMY"

start_time = time.time()

for i in range(200000):
    for key in d:
        if len(d) > 1 or -1 not in d:
            break
    del d[i]

print("--- {} seconds ---".format(time.time() - start_time))
为什么这需要约15秒才能运行

但是,如果我注释掉
deld[I]
或内部循环,它会在0.1秒内运行。

这是因为

for key in d:
    if len(d) > 1 or -1 not in d:
        break
将在第一次迭代时中断,因此您的内部循环基本上是无操作的

添加
del[i]
可以让它做一些真正的工作,这需要时间

更新:显然,上述方法过于简单化了:-)

以下版本的代码显示了相同的特征:

import time
import gc
n = 140000

def main(d):
    for i in range(n):
        del d[i]        # A
        for key in d:   # B
            break       # B

import dis
d = dict()
for i in range(n):
    d[i] = "DUMMY"


print dis.dis(main)
start_time = time.time()
main(d)
print("--- {} seconds ---".format(time.time() - start_time))
使用iterkeys没有什么区别

如果我们在不同大小的
n
上绘制运行时间,我们得到(x轴为n,y轴为秒):

很明显,发生了一些指数级的事情

删除第(A)行或第(B)行会删除指数分量,尽管我不知道为什么

更新2:根据@Blckknght的回答,我们可以通过不频繁地重新灰化项目来恢复一些速度:

def main(d):
    for i in range(n):
        del d[i]
        if i % 5000 == 0:
            d = {k:v for k, v in d.items()}
        for key in d:
            break
或者这个:

def main(d):
    for i in range(n):
        del d[i]
        if i % 6000 == 0:
            d = {k:v for k, v in d.items()}
        try:
            iter(d).next()
        except StopIteration:
            pass
在大n上花费的时间不到原来的一半(130000的通气量在4次运行中是一致的…)


您遇到的问题是由于对一个字典的一个元素(例如,
next(iter(d))
)进行迭代而引起的,该字典曾经很大,但已经缩小了很多。如果您的哈希值不走运,这几乎会很慢,因为迭代所有字典项。这段代码非常“不走运”(可以预见,这是由于Python哈希设计)

出现此问题的原因是,删除项时Python不会重建字典的哈希表。因此,一个字典的哈希表(过去有200000个条目,但现在只剩下1个)仍然有200000多个空格(可能更多,因为它在峰值时可能还没有完全填满)

当您在字典中包含所有值的情况下迭代字典时,查找第一个值非常简单。第一个将位于前几个表条目中的一个。但是,当您清空表时,表的开头将出现越来越多的空格,搜索仍然存在的第一个值将花费越来越长的时间


如果您使用的是整数键(大部分)散列到自身(只有
-1
散列到其他内容),则情况可能更糟。这意味着“完整”词典中的第一个键通常是
0
,下一个
1
,依此类推。当您以递增的顺序删除值时,您将首先非常精确地删除表中最早的键,从而使搜索最大程度地变得更糟。

删除项目后,访问整个键似乎会有一些性能成本。如果您直接访问,则不会产生此成本。因此,我猜想,当删除项时,字典会将其键列表标记为脏,并在更新/重建之前等待对键列表的引用


这解释了为什么在删除内部循环时(没有导致重新生成键列表),性能没有受到影响。这也解释了为什么删除
del d[i]
行时循环速度很快(您没有标记要重建的键列表)。

奇怪的是,如果我删除内部for循环,则
for key in d:
只需执行
del d[i]
,只需几秒钟。…@juanpa.arrivillaga,是的,为什么会这样?这并不能真正解释为什么删除内部循环,但保持
deld[i]
它返回得很快。我无法解释这一点,更奇怪的是,如果我删除d:循环中的键的
,它会回到需要几分之一秒的时间!嗯,Raymond Hettinger和其他核心开发人员有时会回答这个标签上的问题,所以希望他们会出现……啊,这是在迭代器上第一次调用
next
,这需要时间;用类似于
di=iter(d)的东西替换该回路;next(di)
运行时间回到15秒。我可以使用2.7进行复制,因此这看起来与版本无关。
ma_值路径似乎是用于密钥共享字典,通常用于对象属性dict(将其密钥放入所有实例共享的对象中),而不是用于普通dict。