【问题标题】:OrderedDict performance (compared to deque)OrderedDict 性能(与 deque 相比)
【发布时间】:2011-11-18 00:55:52
【问题描述】:

我一直在尝试在 Python 中优化 BFS 实现的性能,我最初的实现是使用 deque 来存储要扩展的节点队列和 dict 来存储相同的节点,这样我就可以有效地查找它是否已经打开了。

我尝试通过迁移到 OrderedDict 来优化(简单性和效率)。然而,这需要更多的时间。使用 deque/dict 完成 400 个样本搜索需要 2 秒,而仅使用 OrderedDict 则需要 3.5 秒。

我的问题是,如果 OrderedDict 的功能与两个原始数据结构相同,那么它至少在性能上不应该相似吗?或者我在这里错过了什么?下面的代码示例。

仅使用 OrderedDict:

open_nodes = OrderedDict()
closed_nodes = {}
current = Node(start_position, None, 0)
open_nodes[current.position] = current

while open_nodes:
  current = open_nodes.popitem(False)[1]
  closed_nodes[current.position] = (current)

  if goal(current.position):
    return trace_path(current, open_nodes, closed_nodes)

  # Nodes bordering current
  for neighbor in self.environment.neighbors[current.position]:
    new_node = Node(neighbor, current, current.depth + 1)
    open_nodes[new_node.position] = new_node

同时使用双端队列和字典:

open_queue = deque()
open_nodes = {}
closed_nodes = {}
current = Node(start_position, None, 0)
open_queue.append(current)
open_nodes[current.position] = current

while open_queue:
  current = open_queue.popleft()
  del open_nodes[current.position]
  closed_nodes[current.position] = (current)

  if goal_function(current.position):
    return trace_path(current, open_nodes, closed_nodes)

  # Nodes bordering current
  for neighbor in self.environment.neighbors[current.position]:
    new_node = Node(neighbor, current, current.depth + 1)
    open_queue.append(new_node)
    open_nodes[new_node.position] = new_node

【问题讨论】:

  • OrderedDict 是用 Python 实现的,而 dict 和 deque 都是用 C 实现的。deque 和 dict 的组合不允许实现 OrderedDict具有与当前实现相同的运行时保证。例如,从OrderedDict 中删除一个项目是摊销 O(1),这对于基于dict 和deque 的实现是不可能的。好吧,如果你幸运的话,雷蒙德会加入并给你一个权威的答案。 :)
  • 如果你使用 python 2.7 并且想要克服 OrderedDict 的性能问题,你可以看看这个项目 - github.com/shoyer/cyordereddict,OrderedDict 的 Cython 实现。

标签: python performance algorithm optimization


【解决方案1】:

deque 和 dict 都是用 C 实现的,运行速度比用纯 Python 实现的 OrderedDict 快。

OrderedDict 的优点是它具有 O(1) 的 getitem、setitem 和 delitem,就像常规的 dicts 一样。这意味着它可以很好地扩展,尽管纯 python 实现速度较慢。

使用双端队列、列表或二叉树的竞争实现通常会在其中一个类别中放弃快速的 big-Oh 时间,以便在另一个类别中获得速度或空间优势。

更新:从 Python 3.5 开始,OrderedDict() 现在具有 C 实现。尽管它没有像其他一些容器那样经过高度优化。它的运行速度应该比纯 python 实现快很多。然后从 Python 3.6 开始,对常规字典进行了排序(尽管还不能保证排序行为)。那些应该跑得更快:-)

【讨论】:

  • 更新:Python 3.5 有一个 OrderedDict 的 C 实现。 bugs.python.org/issue16991
  • 嗨,Raymond,为这个出色的帖子投票。在您提到的讨论中,似乎结论是 set/get/popitem 都是O(1)。您是否有任何来自 python 的官方文件提到它们的性能O(1)。我不挑战你的答案,我只是想阅读更多信息。
  • 我想指出,常规字典不 不应该用于排序,即使人们知道他们的程序只会在 CPython 中使用。对于大多数开发人员来说,速度优势不值得失去向后兼容性和未来发生重大变化的风险。
  • OrderedDict.poplast() 和 OrderedDict.poplast(last=False) 也是 O(1) 吗?底层数据结构是什么?
  • @wprins 在内部,它使用由常规字典扩充的双向链表,可以直接从键中找到给定的链接。
【解决方案2】:

就像 Sven Marnach 所说,OrderedDict 是用 Python 实现的,我想补充一点,它是使用 dict 和 list 实现的。

dict 在 python 中被实现为哈希表。我不确定deque 是如何实现的,但文档中说deque 针对快速添加或访问第一个/最后一个元素进行了优化,所以我猜deque 是作为链表实现的。

我认为,当您在 OrderedDict 上执行 pop 时,python 会执行哈希表查找,这与具有指向最后一个和第一个元素的直接指针的链表相比要慢一些。与哈希表相比,在链表末尾添加元素也更快。

所以,在您的示例中,OrderDict 速度较慢的主要原因是,从链表访问最后一个元素比使用哈希表访问任何元素要快。

我的想法基于Beautiful Code一书中的信息,它描述了dict背后的实现细节,但是我不知道list和deque背后的很多细节,这个答案只是我对事情如何运作的直觉,所以万一我错了,我真的应该对我不确定的事情投反对票。为什么我会谈论我不确定的事情? -因为我想测试我的直觉:)

【讨论】:

  • list 不实现为哈希表。 dict 是。
  • 第二段的第一个单词应该是dict而不是list。你对deque 是一个链表的猜测是正确的。不过,OrderedDict 的基础当然是除了 dict 之外,还有一个链表的纯 Python 实现。由于deque 的界面限制,它不能被deque 替换。
  • Deques 是使用链接的元素集群实现的。这为他们提供了链表的一些好处,同时保留了短数组的缓存局部性好处。
  • 我的意思是第二段中的 dict。对不起。 list 是一个数组,它也很快,但是 pop 在 deque 上的工作比在 list 上快一点。
猜你喜欢
  • 2017-12-27
  • 2021-12-19
  • 2012-05-06
  • 1970-01-01
  • 2012-03-11
  • 2020-08-23
  • 2021-04-02
  • 2021-09-01
  • 2016-12-19
相关资源
最近更新 更多