这个让我完全困惑。

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)

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

因此,当我使用row = dict时,整个循环在大约10毫秒内完成,快如闪电。当我使用有序词典时,我已经等待了10分钟以上,但仍然没有完成。现在,我知道OrderDict应该比字典慢一些,但在最坏的情况下应该慢10倍左右,而根据我的数学计算,该函数实际上要慢100,000倍。

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

am_output.asset_history是一本字典,它具有一个键,主机,并且每一行都是一个字符串列表。例如。

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

编辑:使用OrderedDict进行溅射分析

此VM服务器上的总内存:仅8GB ...需要进一步配置。

圈数

184796(约5秒的等待时间,约60%的内存使用量)

634481(约5秒的等待时间,约65%的内存使用量)

1197564(约5秒的等待时间,约70%的内存使用量)

1899247(〜5秒等待,〜75%内存使用)

2777296(约5秒的等待时间,约80%的内存使用量)

3873730(长时间等待...等待了20分钟,然后放弃了!,内存使用率为88.3%,进程仍在运行)

等待的发生位置随每次运行而变化。

编辑:再次运行它,这次它停止在3873333上,靠近之前停止的位置。它在形成行之后停止,同时尝试追加...我没有注意到这最后一次尝试,但是那也在那里...问题出在追加行,而不是行行...我仍然莫名其妙。这是它在长止点之前产生的行(将行添加到print语句中)...更改主机名以保护无辜的人:

3873333:OrderedDict([[''computer_name','bg-fd5612ea'),('id',1),('hist_item',“sys1 Normalizer(sys1-4):域名不能从sys1名称'bg- fd5612ea'。“)])

最佳答案

正如您自己的测试所证明的,您的内存已用完。即使在CPython 3.6(实际上已经订购了简单的dict,尽管尚不能作为一种语言保证)上,与OrderedDict相比,dict仍具有显着的内存开销;它仍然可以通过边带链表实现,以保留顺序并支持简单的迭代,使用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必须扩展以查找新条目时,很可能需要分页其中的大部分(由于缺乏使用而被分页),并且每当循环垃圾回收运行触发时(大约每70,000个分配都会出现一个完整的GC)和默认分配),所有OrderedDict都返回到页面中,以检查它们是否仍在循环之外被引用(您可以通过禁用循环GC via gc.disable() 来检查GC运行是否是主要问题)。

考虑到您的特定用例,我强烈建议您同时避免使用dictOrderedDict。当您一遍又一遍地拥有一组三个完全相同的固定键时,即使dict甚至Python 3.6上更便宜的形式的开销也是如此。相反,use collections.namedtuple 专​​门为可通过名称或索引引用的轻量级对象而设计(它们的行为类似于常规的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和company至少是两个分配(一个分配给对象结构,一个或多个分配给结构的可动态调整大小的部分),每个分配都可以支付分配器的间接费用和调整成本。

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

关于与dict()相比,Python OrderedDict溅射,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/45240116/

10-12 20:07