gun*_*gor 5 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)
Run Code Online (Sandbox Code Playgroud)
此代码与注释掉的集合行完美配合.但是,当我注释掉row = dict行并从集合行中删除注释时,事情变得非常奇怪.这些行中大约有400万个生成并附加到asset_hist.
因此,当我使用row = dict时,整个循环在大约10毫秒内完成,闪电般快速.当我使用有序词典时,我等了10多分钟,但仍然没有完成.现在,我知道OrderDict应该比dict慢一点,但它应该在最坏的情况下慢大约10倍,而我的数学实际上它在这个函数中慢了大约100,000倍.
我决定在最低的循环中打印索引,看看发生了什么.有趣的是,我注意到控制台输出中的溅射.索引将在屏幕上快速打印,然后停止约3-5秒,然后继续.
am_output.asset_history是一个字典,它有一个键,主机,每一行都是一个字符串列表.例如
am_output.asset_history = {"host1":["string1","string2",...],"host2":["string1","string2",...],...}
编辑:使用OrderedDict进行溅射分析
此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"".)])
正如您自己的测试所证明的那样,您的内存不足。即使在 CPython 3.6 上(其中 plaindict
实际上是有序的,尽管尚未作为语言保证),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
Run Code Online (Sandbox Code Playgroud)
忽略存储的数据,此处的开销OrderedDict
几乎是普通 的两倍dict
。如果您要在我的机器上制作 400 万个此类项目,则会增加超过 850 MB 的开销(在 3.5 和 3.6 上)。
系统上的所有其他程序加上 Python 程序的组合很可能超出了分配给您的计算机的 RAM,并且您陷入了交换抖动的困境。特别是,每当asset_hist
需要扩展新条目时,可能需要对其大部分进行分页(由于缺乏使用而被调出),并且每当触发循环垃圾收集运行时(大约每 70,000 次分配就会发生一次完整的 GC,并且默认情况下释放),所有的OrderedDict
s 都会被分页回来检查它们是否仍然在循环之外被引用(您可以通过禁用循环 GC 来检查 GC 运行是否是主要问题)gc.disable()
。
鉴于您的特定用例,我强烈建议避免dict
两者OrderedDict
。当你一遍又一遍地dict
拥有一组恰好三个固定键时,即使是 Python 3.6 上更便宜的形式,even 的开销也是极端的。相反,使用collections.namedtuple
,它是为可通过名称或索引引用的轻量级对象而设计的(它们的行为类似于常规tuple
s,但也允许将每个值作为命名属性访问),这将大大减少程序的内存成本(并可能加快程序的速度)即使内存不是问题)。
例如:
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))
Run Code Online (Sandbox Code Playgroud)
使用上的唯一区别是您替换row['computer_name']
为row.computer_name
,或者如果您需要所有值,您可以像常规 一样解压它tuple
,例如comphost, idx, hist = row
。如果您暂时需要一个 true OrderedDict
(不要为所有内容存储它们),您可以调用row._asdict()
来获取OrderedDict
具有与 相同映射的an namedtuple
,但这通常不需要。节省内存是有意义的;在我的系统上,三个元素namedtuple
将每个项目的开销降低到 72 字节,甚至不到 3.6 的三分之一dict
,不到 3.6 的六分之一OrderedDict
(并且三个元素namedtuple
在 3.5 上仍然是 72 字节,其中dict
/OrderedDict
更大3.6)。它可能节省的甚至更多;tuple
s (并namedtuple
通过扩展)被分配为单个连续的 C struct
,而dict
和 company 至少有两次分配(一次用于对象结构,一个或多个用于结构的动态调整大小部分),其中每一个都可能支付分配器开销对齐成本。
无论哪种方式,对于您的 400 万行场景,使用namedtuple
都意味着支付(超出值的成本)总计约 275 MB 的开销,而 915 (3.6) - 1100 (3.5) MB 和dict
1770 (3.6) - 1950 ( 3.5) MB 为OrderedDict
. 当您谈论 8 GB 系统时,减少 1.5 GB 的开销是一项重大改进。