与dict()

时间:2017-07-21 14:23:09

标签: python loops ordereddictionary

这个让我感到困惑。

asset_hist = []
for key_host, val_hist_list in am_output.asset_history.items():
    for index, hist_item in enumerate(val_hist_list):
        #row = collections.OrderedDict([("computer_name", key_host), ("id", index), ("hist_item", hist_item)])
        row = {"computer_name": key_host, "id": index, "hist_item": hist_item}
        asset_hist.append(row)

此代码与已注释掉的集合行完美配合。但是,当我注释掉row = dict行并从集合行中删除注释时,事情变得非常奇怪。这些行中大约有400万行被生成并附加到asset_hist。

因此,当我使用row = dict时,整个循环在大约10毫秒内完成,闪电般快速。当我使用有序词典时,我等了10多分钟,但仍然没有完成。现在,我知道OrderDict应该比dict慢一点,但它应该在最坏的情况下慢大约10倍,而在我的数学中它实际上在这个函数中慢了大约100,000倍。

我决定在最低的循环中打印索引,看看发生了什么。有趣的是,我注意到控制台输出中的溅射。索引将在屏幕上非常快速地打印,然后停止约3-5秒,然后继续。

am_output.asset_history是一个字典,它有一个键,主机,每一行都是一个字符串列表。 E.g。

am_output.asset_history = {“host1”:[“string1”,“string2”,...],“host2”:[“string1”,“string2”,...],...}

编辑:使用OrderedDict进行Sputter分析

此VM服务器上的总内存:仅需8GB ......需要更多的解释。

LOOP NUM

184796(约5秒等待,约60%内存使用)

634481(约5秒等待,约65%内存使用)

1197564(等待约5秒,内存使用率约为70%)

1899247(等待约5秒,内存使用率约为75%)

2777296(约5秒等待,约80%内​​存使用)

3873730(LONG WAIT ......等了20分钟放弃了!,88.3%的内存使用量,进程仍然在运行)

等待发生的地方每次运行都会发生变化。

编辑:再次跑吧,这次它停在3873333,接近它之前停止的位置。在形成行之后它停止了,同时试图追加......我没有注意到最后一次尝试但它也在那里......问题在于追加线,而不是行线...我仍然百思不得其解。这是在长停止之前生成的行(将行添加到print语句中)...更改主机名以保护无辜者:

3873333:OrderedDict([('computer_name','bg-fd5612ea'),('id',1),('hist_item',“sys1 Normalizer(sys1-4):无法从sys1名称确定域名'BG-fd5612ea'“。)])

1 个答案:

答案 0 :(得分:2)

正如你自己的测试证明的那样,你的内存已经不足了。即使在CPython 3.6(其中普通dict实际上是有序的,但尚未作为语言保证),OrderedDictdict相比具有显着的内存开销。它仍然使用边带链表实现,以保留顺序并支持轻松迭代,使用move_to_end重新排序等。您可以通过使用sys.getsizeof进行检查(具体结果因Python版本和构建位宽,32对64位):

>>> od = OrderedDict([("a", 1), ("b", 2), ("c", 3)])
>>> d = {**od}
>>> sys.getsizeof(od)
464   # On 3.5 x64 it's 512
>>> sys.getsizeof(d)
240   # On 3.5 x64 it's 288

忽略存储的数据,此处OrderedDict的开销几乎是普通dict的两倍。如果你在我的机器上制作了400万件这样的物品,那么会增加超过850 MB的间距(3.5和3.6)。

可能是你系统上所有其他程序加上你的Python程序的组合超出了分配给你的机器的RAM,你就会陷入交换颠簸。特别是,每当asset_hist必须为新条目展开时,它可能需要在其中大部分页面(由于缺乏使用而被页面调出),以及每当循环垃圾收集运行触发时(发生完整的GC)默认情况下,大约每70,000个分配和解除分配),所有OrderedDict被调回以检查它们是否仍然在循环之外被引用(您可以通过禁用循环GC来检查GC是否是主要问题{ {3}})。

鉴于您的特定用例,我强烈建议您同时避免使用dictOrderedDict。偶数dict的开销,即使是Python 3.6上的更便宜的形式,当你有一套正好三个固定密钥时,它是极端的。相反,via gc.disable(),设计用于可由名称或索引引用的轻量级对象(它们像常规tuple一样,但也允许将每个值作为命名属性访问),这将大大减少内存你的程序的成本(即使内存不是问题,也可能加快它的速度)。

例如:

from collections import namedtuple

ComputerInfo = namedtuple('ComputerInfo', ['computer_name', 'id', 'hist_item'])

asset_hist = []
for key_host, val_hist_list in am_output.asset_history.items():
    for index, hist_item in enumerate(val_hist_list):
        asset_hist.append(ComputerInfo(key_host, index, hist_item))

唯一不同之处在于您将row['computer_name']替换为row.computer_name,或者如果您需要所有值,则可以将其解压缩为常规tuple,例如comphost, idx, hist = row。如果您暂时需要真实的OrderedDict(不要将其存储在所有内容中),您可以致电row._asdict()以获得与OrderedDict具有相同映射的namedtuple,但这通常不需要。节省的内存是有意义的;在我的系统上,三个元素namedtuple将每个项目的开销减少到72个字节,不到3.6个dict的三分之一,而不到3.6个OrderedDict的六分之一(并且三个元素namedtuple在3.5上保持72个字节,其中dict / OrderedDict在3.6之前更大。它甚至可以节省更多; tuple s(和namedtuple扩展名)被分配为单个连续的C struct,而dict和公司至少有两个分配(一个用于对象结构,一个或者更多用于结构的动态可调整部分),每个部分都可以支付分配器开销和对齐成本。

无论哪种方式,对于您的四百万行情况,使用namedtuple意味着支付(超出值的成本)总开销约275 MB,而915(3.6) - 1100(3.5)MB dict和1770(3.6) - 1950(3.5)MB OrderedDict。当你谈论一个8 GB的系统时,削减1.5 GB的开销是一项重大改进。