这个让我感到困惑。
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'“。)])
答案 0 :(得分:2)
正如你自己的测试证明的那样,你的内存已经不足了。即使在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
必须为新条目展开时,它可能需要在其中大部分页面(由于缺乏使用而被页面调出),以及每当循环垃圾收集运行触发时(发生完整的GC)默认情况下,大约每70,000个分配和解除分配),所有OrderedDict
被调回以检查它们是否仍然在循环之外被引用(您可以通过禁用循环GC来检查GC是否是主要问题{ {3}})。
鉴于您的特定用例,我强烈建议您同时避免使用dict
和OrderedDict
。偶数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的开销是一项重大改进。