简体   繁体   English

C++ STL - 容器实现

[英]C++ STL - Containers implementation

I am currently learning a lot about Intrusive Containers.我目前正在学习很多关于侵入式容器的知识。 So I often compare them to the standard containers.所以我经常将它们与标准容器进行比较。

Let's take the std::list for example.让我们以 std::list 为例。 I read that this container is usually implemented as a doubly-linked list.我读到这个容器通常被实现为一个双向链表。 But I didn't read anything of how the nodes are implemented in detail.但是我没有详细阅读节点是如何实现的。 I assume there is a 'previous' and 'next' pointer, but what about the object that belongs to such a node.我假设有一个“上一个”和“下一个”指针,但是属于这样一个节点的对象呢? Is it a Pointer to an object, or the object itself, that is constructed in the nodes allocated memory?它是指向对象的指针,还是在节点分配的内存中构造的对象本身?

In Boost.Intrusive it is stated that their containers have a better locality (see here: https://www.boost.org/doc/libs/1_72_0/doc/html/intrusive/usage_when.html , or here: https://www.boost.org/doc/libs/1_72_0/doc/html/intrusive/performance.html ).在 Boost.Intrusive 中声明它们的容器具有更好的局部性(参见此处: https ://www.boost.org/doc/libs/1_72_0/doc/html/intrusive/usage_when.html,或此处: https:/ /www.boost.org/doc/libs/1_72_0/doc/html/intrusive/performance.html )。 I am not sure why that is the case.我不确定为什么会这样。 When the node in the std::list holds an object and the intrusive container holds a node in their object, how does that lead to better locality?当 std::list 中的节点包含一个对象并且侵入式容器在其对象中包含一个节点时,这如何导致更好的局部性?

Is it a Pointer to an object, or the object itself, that is constructed in the nodes allocated memory?它是指向对象的指针,还是在节点分配的内存中构造的对象本身?

In Boost.Intrusive, the element type of the container is the node.在 Boost.Intrusive 中,容器的元素类型节点。 In order to make it compatible with the container, you have to modify your element type so that it includes data members required by the container - either by inheriting from a base class (eg list_base_hook , see here ) or by adding a special data member (eg list_member_hook ).为了使其与容器兼容,您必须修改元素类型,使其包含容器所需的数据成员 - 通过从基类继承(例如list_base_hook ,请参见此处)或添加特殊数据成员(例如list_member_hook )。 This is why they are called intrusive containers.这就是它们被称为侵入式容器的原因。 In comparison, the standard containers do not require you to modify your classes and instead wrap them in container nodes, if necessary.相比之下,标准容器不需要您修改类,而是在必要时将它们包装在容器节点中。

When the node in the std::list holds an object and the intrusive container holds a node in their object, how does that lead to better locality?当 std::list 中的节点包含一个对象并且侵入式容器在其对象中包含一个节点时,这如何导致更好的局部性?

In std::list each container node (which contains your element) is allocated separately, in its own dynamic memory allocation.std::list ,每个容器节点(包含您的元素)在其自己的动态内存分配中单独分配。 The node contains pointers to the previous and the next elements in the list.该节点包含指向列表中上一个和下一个元素的指针。 Since each node is allocated separately, their locality depends on the memory allocator used, but generally you can assume that different nodes are located at arbitrary locations in memory, possibly distant and not in order.由于每个节点都是单独分配的,因此它们的位置取决于所使用的内存分配器,但通常您可以假设不同的节点位于内存中的任意位置,可能距离较远且无序。 Additionally, traversing the list requires to dereference a pointer to the next element on each iteration, which typically hampers memory caching algorithms in the CPU.此外,遍历列表需要在每次迭代时取消引用指向下一个元素的指针,这通常会妨碍 CPU 中的内存缓存算法。

In boost::intrusive::list , the container does not impose any memory allocation strategy on the user.boost::intrusive::list ,容器不会对用户强加任何内存分配策略。 It is possible to have a single memory region for multiple or all elements of the intrusive container, which makes them more closely packed and possibly ordered in memory.侵入式容器的多个或所有元素可以有一个单一的内存区域,这使得它们在内存中更紧密地排列并且可能有序。 This requires more work from the user, of course.当然,这需要用户做更多的工作。 List iteration still requires pointer dereferencing and hurts prefetcher in the CPU, but if container elements are closely packed, chances are high that each next node will be in a cache line that was already fetched from memory for the previous element(s).列表迭代仍然需要取消引用指针并损害 CPU 中的预取器,但如果容器元素紧密排列,则每个下一个节点很有可能位于已经从内存中为前一个元素获取的缓存行中。

Another thing to note is that intrusive containers are much more useful when you need to store element in multiple containers at once.另一件需要注意的事情是,当您需要一次将元素存储在多个容器中时,侵入式容器会更有用。 With standard containers, you have to use pointers to refer to the element from each container.对于标准容器,您必须使用指针来引用每个容器中的元素。 For example:例如:

// Element type
class Foo {};

std::list< std::shared_ptr< Foo > > list;
std::map< int, std::shared_ptr< Foo > > map;

In this example, you have at least one allocation for a Foo object, one allocation for the list node and one allocation for the map node.在此示例中,您至少有一个Foo对象分配,一个list节点分配和一个map节点分配。 Each of these allocations is arbitrarily located in memory.这些分配中的每一个都任意位于内存中。 Accessing the element either via list or via map requires an additional level of indirection.通过listmap访问元素需要额外的间接级别。

With intrusive containers, you can reduce this to just one allocation and no extra indirection:使用侵入式容器,您可以将其减少到仅一次分配,而无需额外的间接访问:

// List hook
typedef boost::intrusive::list_base_hook<> FooListHook;
// Map/set hook
typedef boost::intrusive::set_base_hook<
    boost::intrusive::optimize_size< true >
> FooSetHook;

// Node type
class Foo :
    public FooListHook,
    public FooSetHook
{
    ...
};

boost::intrusive::list< Foo, boost::intrusive::base_hook< FooListHook > > list;
boost::intrusive::set< Foo, boost::intrusive::base_hook< FooSetHook >, ... > set;

In this case, neither list nor set does memory allocation of their own, all the necessary data is already within the Foo node, which you allocate yourself.在这种情况下, listset都不会自己分配内存,所有必要的数据都已经在您自己分配的Foo节点中。 Iterating through either of the containers automatically fetches both hooks and Foo contents (at least partially) into cache, which makes memory accesses faster.遍历任一容器会自动将钩子和Foo内容(至少部分)提取到缓存中,这使得内存访问更快。 There are other benefits to this approach as well, like the ability to switch between the two containers' iterators without expensive element lookup.这种方法还有其他好处,例如无需昂贵的元素查找即可在两个容器的迭代器之间切换的能力。

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

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