我正在编写一些涉及此类内容的Python代码
values = {}
for element in iterable:
values.setdefault(element.name, []).append(element)
因为我之前可以对输入进行排序,所以我也像这样实现了它
values = {}
cur_name = None
cur_list = None
for element in iterable:
if element.name != cur_name:
values[cur_name] = cur_list
cur_name = element.name
cur_list = []
cur_list.append(element)
if cur_list:
values[cur_name] = cur_list
del values[None]
此处输入已按element.name
排序。
第二种方法比第一种方法快得多,而且它也使用了更少的内存。
这是什么原因?
或者我在第二种方法中犯了某种错误?
答案 0 :(得分:4)
每次循环时你的原始代码都会创建一个大部分然后被丢弃的列表。它还会进行多次字典查找(查找方法setdefault
是字典查找,然后方法本身会进行字典查找,以查看对象是否已设置,以及是否存在另一个存储值)。 .name
和.append()
也是字典查找,但它们仍然存在于修订后的代码中。
for element in iterable:
values.setdefault(element.name, []).append(element)
修改后的代码仅在名称更改时查找字典,因此它会从每个循环中删除两个字典查找和方法调用。这就是为什么它更快。
至于内存使用,当列表增长时,它有时可能必须复制数据,但如果只能扩展内存块则可以避免这种情况。我的猜测是,创建所有未使用的临时列表可能会使内存更多碎片并强制更多副本。换句话说,Python实际上并没有使用更多内存,但它可能有更多已分配但未使用的内存。
如果您认为setdefault
需要考虑使用collections.defaultdict
。这可以避免创建列表,除非需要它:
from collections import defaultdict
values = defaultdict(list)
for element in iterable:
values[element.name].append(element)
这可能仍然比你的第二个代码慢,因为它没有利用你的知识,名字都被分组,但一般情况下它优于setdefault
。
另一种方法是使用itertools.groupby
。像这样:
from itertools import groupby
from operator import attrgetter
values = { name: list(elements) for name,elements in
groupby(elements, attrgetter('name')) }
利用排序并将所有内容简化为单个词典理解。
答案 1 :(得分:2)
我可以想到为什么第二种方法更快的几个原因。
values.setdefault(element.name, []).append(element)
在这里,您要为每个element
创建一个空列表,即使您永远不会使用它。您还为每个元素调用setdefault
方法,这相当于一个哈希表查找和一个可能的哈希表写入,加上调用方法本身的成本,这在python中并不是无关紧要的。最后,正如其他人在我发布此答案后指出的那样,您为每个setdefault
查找element
属性一次,即使它始终引用相同的方法。
在第二个例子中,您可以避免所有这些低效率。您只需要根据需要创建任意数量的列表,并且无需调用任何方法即可完成所有操作,但需要list.append
,其中插入了一些字典分配。您也可以通过简单的比较(element.name != cur_name
)替换哈希表查找,这是另一项改进。
我希望您也可以获得缓存优势,因为在向列表添加项目时(这会导致大量cache misses),您并没有跳到地方,但是一次只能在一个列表上工作。这样,相关内存可能位于非常接近CPU的高速缓存层中,因此进程更快。不应低估这种影响 - 从RAM中获取数据比从L1缓存(source读取数据慢两个数量级(或约100倍)慢。
当然排序会增加一点时间,但是python拥有世界上最好和最优化的排序算法之一,所有排序算法都用C编码,所以它不会超过上面列出的好处。
我不知道为什么第二种解决方案的内存效率更高。正如Jiri所指出的那样,它可能是不必要的列表,但我的理解是这些应该是垃圾收集器立即收集的,所以它应该只增加一点内存使用量 - 大小一个空列表。也许是因为垃圾收集器比我想象的更懒。
答案 2 :(得分:1)
您的第一个版本有两个效率低下的部分:
在循环中调用和解除引用 values.setdefault 。你可以在循环之前协调values_setdefault = values.setdefault
,它可以加快速度。
正如其他答案所示,为列表中的每个元素创建新的空列表非常慢且内存效率低下。我不知道如何避免它并立即使用setdefault。