繁体   English   中英

将链表嵌入数据结构有什么好处?

[英]What is the advantage of embedding a linked list into a data structure?

在 FreeBSD 中阅读 kernel 数据结构时,我偶然发现了MBuf MBuf包含一个指向MBuf链中下一个MBuf的指针,实现了一个链表。 每个MBuf本身还包含特定于链表中该节点的数据。

我更熟悉将容器类型与值类型分开的设计(考虑std::listSystem.Collections.Generic.LinkedList )。 我正在努力理解将容器语义嵌入到数据类型中的价值主张——获得了哪些效率? 真的只是为了消除节点实例指针存储吗?

考虑您在列表中有一个节点的迭代器/指针。 为了获取数据,您必须:

  • 从节点读取指向数据的指针
  • 解引用刚刚读取的指针并读取实际数据

另一方面,如果列表概念“嵌入”在您的数据结构中,则您可以在对象与节点本身一起的单个存储操作中读取对象。

分离的列表节点及其数据的另一个问题是列表节点本身很小(通常只有2或3个指针)。 结果,在存储器中保持如此小的结构的存储器开销可能很重要。 您知道-诸如newmalloc实际上消耗的内存要比它们分配的更多-系统使用其自己的树结构来跟踪内存的可用空间和空闲空间。

在这种情况下,将事物分组为单个分配操作是有益的。 您可以尝试将几个列表节点放在小束中,也可以尝试将每个节点与其分配的数据连接起来。

使用侵入性指针(相对于共享指针)或将对象和智能指针数据打包在一起的std::make_shared可以看到类似的策略。


zett42发表评论说std::list<T>T与节点数据保持在一起。 如上所述,这实现了单个存储块,但有一个不同的问题: T不能是多态的。 如果您具有类A及其派生类B ,则node<B>不是node<A>的派生类。 如果您尝试将B插入std::list<A> ,则您的对象将:

  • 在最佳情况下,导致编译错误(无构造函数A::A(const B&)
  • 在最坏的情况下, 切片 B静默地将B表示A的一部分复制到节点中。

如果你想在一个列表来保存多态对象的典型解决方案是真正有std::list<A*>代替std::list<A> 但是,最后您得到了我上面解释的额外间接访问。

另一种方法是制作一个侵入式列表(例如boost::intrusive::list ),其中节点信息实际上是A对象的一部分。 这样,每个节点都可以毫无问题地成为A的导数。

侵入式链接列表的一大优点是,您可以创建一个预先存在的对象列表,而无需任何新分配。 为此,使用std :: lists指针将需要分配内存。

Boost具有侵入式列表实现,并带有使用理由。 http://www.boost.org/doc/libs/1_63_0/doc/html/intrusive.html

获得了什么效率? 真的就是要消除节点实例指针存储吗?

我会说更少的缓存未命中,然后会提高整体性能(即使链表通常不缓存友好的数据结构)。
这样,您不必再跟随一个指针就可以在内存中的某个地方找到数据,并将它们带到每个节点的处理器附近。
此外,如果您在内存的连续区域中构建节点并使用几个指针来管理它们(我们称它们为自由列表和使用中列表,这听起来很熟悉吗?),则可以在性能(至少在列表中不包含很多项目的情况下,否则风险是在内存中来回跳动)。 在这种情况下,in和delete的时间是恒定的(当然,除非必须先在列表中搜索一个节点,然后才能插入特定位置),否则这是另一个优点。

侵入式列表的主要优点之一是您可以廉价地让单个节点属于多个列表。

例如,您可以拥有以 3 种不同方式排序的项目集合,对应于 3 个不同列表中的条目。 例如,用std::list做这件事会很笨拙。

正如@doron 所提到的,我认为的另一大优势是一旦创建了对象,列表管理就需要 0 次分配。

Boost 对侵入式与非侵入式数据结构进行了一些不错的讨论,各有优缺点。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM