在阅读FreeBSD中的内核数据结构时,我偶然发现了MBuf
。 MBuf
包含指向MBuf
链中下一个MBuf
的指针,用于实现链接列表。每个MBuf
本身也包含特定于链接列表中该节点的数据。
我更熟悉将容器类型与值类型分开的设计(考虑std::list
或System.Collections.Generic.LinkedList
)。我正在努力理解将容器语义嵌入数据类型的价值主张 - 获得了哪些效率?这真的是关于消除节点实例指针存储吗?
答案 0 :(得分:2)
考虑您有一个指向列表中节点的迭代器/指针。为了获取您需要的数据:
另一方面,如果列表概念是“嵌入”在您的数据结构中,您可以在单个内存操作中读取您的对象,就像它与节点本身一样。
分隔列表节点及其数据的另一个问题是列表节点本身很小(通常只有2或3个指针)。结果,在存储器中保持这种小结构的存储器开销可能很重要。你知道 - 诸如new
或malloc
之类的操作实际上消耗的内存比它们分配的更多 - 系统使用它们自己的树结构来跟踪内存的空闲位置和不存在的位置。
在这种情况下,将事物分组到单个分配操作中是有益的。您可以尝试将多个列表节点保存在小包中,或者您可以尝试使用它分配的数据连接每个节点。
使用侵入式指针(与共享指针相比)可以看到类似的策略,或者将对象和智能指针数据打包在一起的std::make_shared
。
zett42发表评论std::list<T>
将T
与节点数据保持在一起。这实现了我上面解释的单个内存块,但是有一个不同的问题:T
不能是多态的。如果您有一个班级A
及其派生B
,则node<B>
不是node<A>
的衍生版。如果您努力将B
插入std::list<A>
,您的对象将会:
A::A(const B&)
)B
仅将代表A
的部分复制到节点中。如果要在单个列表中保存多态对象,典型的解决方案实际上是std::list<A*>
而不是std::list<A>
。但是你最终得到了我上面解释的额外间接。
另一种方法是创建一个侵入式列表(例如boost::intrusive::list
),其中节点信息实际上是A
对象的一部分。然后每个节点都可以是A
的导数而没有问题。
答案 1 :(得分:2)
Intrusive链表的一大优势是您可以创建一个预先存在的对象列表,而无需任何新的分配。要使用std ::指针列表执行此操作,需要进行内存分配。
Boost有一个侵入式列表实现,有正当理由可供使用。 http://www.boost.org/doc/libs/1_63_0/doc/html/intrusive.html
答案 2 :(得分:0)
获得了哪些效率?这真的是关于消除节点实例指针存储吗?
我会说缓存丢失更少,然后整体性能更好(即使链接列表通常不是缓存友好的数据结构)。
这样,您不必再按照一个指针在内存中的某处找到您的数据,并将它们放在处理器附近,以用于每个节点。
此外,如果你在一个连续的内存区域构建你的节点并用几个指针管理它们(我们称之为免费列表和使用中的列表,它听起来很熟悉吗?),你可以在性能(至少只要列表中不包含很多项,否则风险就是在内存中来回跳转)。在这种情况下,在和删除中有恒定的时间(当然,除非你必须先搜索列表中的节点才能插入特定的位置),这是另一个优点。