【问题标题】:How to implement a cache friendly dynamic binary tree?如何实现缓存友好的动态二叉树?
【发布时间】:2017-06-13 17:24:40
【问题描述】:

根据包括Wikipedia 在内的多个来源,实现二叉树最常用的两种方法是:

  1. 节点和指针(或引用),每个节点显式拥有其子节点。
  2. 数组,其中子节点的位置由其父节点的索引隐式给出。

第二个显然在内存使用引用位置方面更胜一筹。但是,如果您要允许从树中插入移除,可能会导致树不平衡。这是因为这种设计的内存使用量是树深度的指数函数。

假设您希望支持此类插入和删除。如何实现树,以便树遍历充分利用 CPU 缓存。

我正在考虑为节点创建一个对象池并将它们分配到一个数组中。这样节点将靠近在一起->因此具有良好的参考位置。

但是如果节点的大小和缓存行的大小一样,这有什么意义吗?

如果您的 L1 行大小为 64 字节,并且您访问 std::vector<std::uint8_t>(64) 的第一个成员,您可能会将向量的全部内容保存在 L1 缓存中。这意味着您可以非常快速地访问任何元素。但是如果元素的大小与缓存行大小相同怎么办?由于 L1、L2 和 L3 高速缓存的高速缓存行是 likely not to be very different,因此在这里引用位置似乎没有任何帮助。我错了吗?还有什么可以做的?

【问题讨论】:

  • “第二个显然在几乎任何意义上都优于”除了缓存之外 - 为什么?
  • @NeilButterworth 这对我来说是一件愚蠢的事情。我试图让它更精确。如果您有其他建议,请随时编辑。
  • 可能是 std::deque 而不是 std::vector(或数组)。 “典型的实现使用一系列单独分配的固定大小的数组。”来自en.cppreference.com/w/cpp/container/deque 还检查了 std::vector 的性能(将尝试找到这个参考) - 使用 std 的随机插入/删除::vector 与 std::list 相比,向量在最多 100,00 个元素(大约)时表现更好
  • 发现它“现代 C++:你需要知道的 - Herb Sutter”,从演示开始大约 46 分钟开始channel9.msdn.com/Events/Build/2014/2-661

标签: c++ memory-management data-structures binary-tree cpu-cache


【解决方案1】:

除非您正在研究如何改进二叉树的缓存访问模式,否则我觉得这是XY problem - 您要解决的问题是什么?为什么您认为二叉树是解决您的问题的最佳算法?预期的工作集大小是多少?

如果你正在寻找一个通用的关联存储,有多种缓存友好(其他关键字:“cache-efficient”、“cache-oblivious”)算法,例如Judy arrays,其中有一个@987654323 @。

如果您的工作集足够小,并且您只需要一组有序的项目,那么一个简单的有序数组可能就足够了,这可能会带来另一个性能优势 - branch prediction

最后,要找出最适合您的用例的方法是尝试和衡量不同的方法。

【讨论】:

    【解决方案2】:

    使用块分配器。

    您有一个或少数几个连续的内存“池”,您可以从中分配固定大小的块。它被实现为一个链表。所以分配很简单

    answer = head, 
    head = head->next, 
    return answer; 
    

    释放很简单

    tofree->next = head;
    head = tofree;
    

    如果您允许多个池,当然您需要编写代码来确定池,这会增加一点复杂性,但不会太多。它本质上是一个简单的内存分配系统。 由于所有池成员在内存中都靠得很近,因此您可以在小树上获得良好的缓存一致性。对于大树,你必须更聪明一点。

    【讨论】:

      猜你喜欢
      • 2011-02-05
      • 2015-06-05
      • 2012-01-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-01-02
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多