Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/157.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
STL链接结构如何处理分配? C++标准模板库提供了许多容器类型,它们具有非常明显的实现,如链表和映射。_C++_Performance_Memory Management_Stl - Fatal编程技术网

STL链接结构如何处理分配? C++标准模板库提供了许多容器类型,它们具有非常明显的实现,如链表和映射。

STL链接结构如何处理分配? C++标准模板库提供了许多容器类型,它们具有非常明显的实现,如链表和映射。,c++,performance,memory-management,stl,C++,Performance,Memory Management,Stl,对于高度链接的结构,一个非常基本的优化是使用自定义子分配器和提供固定大小分配的私有内存池。考虑到STL对性能的重视,我希望能够执行这种或类似的优化。同时,所有这些容器都有一个可选的分配器模板参数,如果能够为已经使用自定义分配器的对象提供自定义分配器,这在很大程度上似乎是多余的 因此,如果我正在寻找具有STL的最高性能链接结构,我是否需要指定自定义分配器,或者我是否可以依靠STL来完成这项工作?这在很大程度上取决于您的工作负载 如果您不经常遍历数据结构,甚至不必优化任何内容。你最好把时间花在别处

对于高度链接的结构,一个非常基本的优化是使用自定义子分配器和提供固定大小分配的私有内存池。考虑到STL对性能的重视,我希望能够执行这种或类似的优化。同时,所有这些容器都有一个可选的分配器模板参数,如果能够为已经使用自定义分配器的对象提供自定义分配器,这在很大程度上似乎是多余的


因此,如果我正在寻找具有STL的最高性能链接结构,我是否需要指定自定义分配器,或者我是否可以依靠STL来完成这项工作?

这在很大程度上取决于您的工作负载

如果您不经常遍历数据结构,甚至不必优化任何内容。你最好把时间花在别处

如果您确实进行了迭代,但是您的负载很大,并且您对每个项目都做了大量的工作,那么默认实现不太可能成为瓶颈。迭代的低效率将被每项工作所吞噬

如果您存储小元素int、指针,执行琐碎的操作并多次迭代结构,那么您将从std::vector或boost::flat_map之类的东西中获得更好的性能,因为它们允许更好的预取操作

当您发现自己正在分配和释放大量小块内存时,分配器最有用。这会导致内存碎片,并可能影响性能

与所有性能建议一样,您需要在目标机器上对工作负载进行基准测试


另外,请确保已打开优化,即-O3。

这在很大程度上取决于您的工作量

如果您不经常遍历数据结构,甚至不必优化任何内容。你最好把时间花在别处

如果您确实进行了迭代,但是您的负载很大,并且您对每个项目都做了大量的工作,那么默认实现不太可能成为瓶颈。迭代的低效率将被每项工作所吞噬

如果您存储小元素int、指针,执行琐碎的操作并多次迭代结构,那么您将从std::vector或boost::flat_map之类的东西中获得更好的性能,因为它们允许更好的预取操作

当您发现自己正在分配和释放大量小块内存时,分配器最有用。这会导致内存碎片,并可能影响性能

与所有性能建议一样,您需要在目标机器上对工作负载进行基准测试


注意:确保已打开优化,即-O3。

虽然标准没有明确禁止此类优化,但实施者的设计选择将很糟糕

首先,我们可以想象一个用例,在这个用例中,池分配不是一个理想的选择。 在模板参数中引用自定义分配器来引入所需的池行为并不困难,但是如果该行为是容器的一部分,则禁用该行为几乎是不可能的

也从OOP的角度来看,你有一个模板,显然它有不止一个的责任,有些人认为它是个坏的标志。 总体答案似乎是肯定的,您确实需要一个自定义分配器


最后,您可以编写一个测试来检查您的具体实现做了什么。

虽然该标准没有明确禁止此类优化,但对于实现者来说,这将是一个糟糕的设计选择

首先,我们可以想象一个用例,在这个用例中,池分配不是一个理想的选择。 在模板参数中引用自定义分配器来引入所需的池行为并不困难,但是如果该行为是容器的一部分,则禁用该行为几乎是不可能的

也从OOP的角度来看,你有一个模板,显然它有不止一个的责任,有些人认为它是个坏的标志。 总体答案似乎是肯定的,您确实需要一个自定义分配器


最后,你可以写一个来检查你的具体实现做什么。

< P>当然它可以从一个标准的LIB实现到下一个不同,但是上次我检查的LIBS,比如MSVC,GNUC++和EASL,链接结构在一个分配中分配节点和元素数据。 然而,每个节点仍然是针对std::allocator一次分配一个,这是一个相当通用的可变长度分配器,尽管它至少可以假设所分配的所有元素都是特定的数据类型,但很多时候我发现它只是默认为VTune和CodeXL会话中的malloc调用。有时甚至还有线程安全内存分配 如果数据结构本身不是为并发修改或同时读/写而设计的,而使用线程安全的通用内存分配一次分配一个节点,那么这就有点浪费了

但是,如果您希望允许客户机将其自己的自定义分配器作为模板参数传入,那么这种设计是有意义的。在这种情况下,您不希望数据结构共享内存,因为这将与分配器可能希望执行的操作相冲突。对于链接结构,尤其是您是否一次分配一个节点,并将更高效的分配技术(如自由列表)的责任传递给分配器,以使每个节点分配更高效,需要做出决定,或者避免依赖于分配器,通过以连续的方式一次分配多个节点并将其池化,从而使数据结构中的池化变得高效和有效。标准库倾向于前一个路由,不幸的是,这会使像std::list和std::map这样的东西在用于默认std::分配器时效率非常低

就我个人而言,对于链接结构,我使用我自己的手动解决方案,该解决方案依赖于32位索引到数组ex:std::vector,它有效地充当池内存,并像索引自由列表一样工作,如下所示:

。。。我们可能实际将链表节点存储在std::vector中。这些链接只是让我们在固定时间内移除东西,并在固定时间内回收这些空白空间的一种方式。真实的示例比上面的伪代码稍微复杂一些,因为该代码只适用于POD。真实的示例与标准容器一样,使用对齐的存储、新的放置和手动dtor调用,但没有那么复杂。类似情况如下:

。。。例如,如果您不希望指针失效来存储列表节点,则使用std::vector或类似std::deque的双链接索引列表。在这种情况下,链接允许我们在遍历向量的连续内存时跳过。其要点是允许在列表的任意位置进行固定时间的删除和插入,同时保留遍历时的插入顺序。如果我们使用swap-to-back和pop-back技术从中间进行固定时间删除,则仅使用std::vector就会丢失插入顺序

除了使所有内容更连续、更便于缓存遍历以及更快地分配和释放外,它还可以将64位体系结构上的链接大小减半,而我们可以使用32位索引,将其转换为存储节点的随机访问序列

链表实际上在C++中积累了一个非常糟糕的ReP,我相信这很大程度上是因为这个原因。人员基准测试针对默认分配器使用std::list,并在遍历过程中出现大量缓存未命中和昂贵的、可能是线程安全的内存分配(插入每个节点并移除每个节点)等形式的瓶颈。类似的情况是,如今人们对无序的_映射和无序的_集的偏好超过了映射和集。哈希表可能总是有一些边缘,但当map和set一次只使用一个通用分配器一个节点,并且在树遍历时会导致大量缓存未命中时,该边缘是如此倾斜

所以,若我在寻找具有 STL,我需要指定一个自定义分配器,还是可以依赖 STL能帮我做那件事吗

测量/配置文件的建议总是明智的,但如果您的需求真的很关键,比如您在每个帧上重复循环数据,并且它存储数十万个或更多元素,同时在每个帧的中间重复插入和移除元素,然后,在使用std::list或std::map之前,我至少会获得一个免费列表。而链表是如此简单的数据结构,如果你真的用标准库中的链表结构来解决热点问题,我建议你使用自己的链表,而不必处理分配器和数据结构的组合来实现一个有效的解决方案,只要有一个如果实现起来非常简单,那么它的默认形式将非常有效地满足您的确切需求

我过去经常摆弄分配器,研究数据结构,并尝试通过试验分配器来提高它们的效率,取得了一定的成功,这足以鼓励我,但效果并不惊人,但我发现,我的生活变得更加容易,只需制作链接结构,将他们的记忆集中在最前面,这给了我最惊人的结果。有趣的是,仅仅创建这些在分配策略方面更高效的数据结构所花费的时间比我在尝试第三方分配程序以及实现自己的分配程序时所花费的时间要少。这里有一个简单的例子 whipped up使用链表检测400万个粒子的碰撞这是一款很老的产品,所以它在i3上运行

它使用单链接列表,使用我自己的类似deque的容器来存储节点,如下所示:

类似的事情是,500k可变大小的代理之间的冲突空间索引只花了2个小时就实现了整个过程,我甚至懒得多线程处理它:

我主要针对那些认为链表效率很低的人指出这一点,因为只要你以一种高效且相对连续的方式存储节点,它们就可以真正成为一个有用的工具添加到你的兵工厂中。我认为C++社区在很大程度上会把它们放得太仓促,因为我完全失去了链接列表。正确地使用,它们可以减少堆分配,而不是将它们相乘并改进空间局部性,而不是降低它的Ex:如果上面使用了一个单独的STD::vector或SmallVector的实例,每个单元格都有固定的SBO,而不是只存储一个32位整数,那么考虑上面的网格图。而且写一个链表并不需要很长时间,比如说,它可以非常高效地分配节点——如果有人花半个多小时来写数据结构和单元测试,我会感到惊讶。比如说,一棵高效的红黑树可能需要几个小时,但这没什么大不了的

这些天我只是直接把链接的节点存储在像std::vector这样的东西里面,我自己的std::deque的chunkier等价物,tbb::concurrent_vector如果我需要构建一个并发链接结构,等。当高效分配被纳入数据结构的职责中时,生活就变得容易多了,而不必将高效分配和数据结构作为两个完全独立的概念来考虑,并且必须将所有这些不同类型的分配器快速地传递到各地。我现在喜欢的设计是:

// Creates a tree storing elements of type T with 'BlockSize' contiguous
// nodes allocated at a time and pooled.
Tree<T, BlockSize> tree;
。。。或者我只是省略BlockSize参数,让节点存储在std::vector中,并进行摊销重新分配,同时连续存储所有节点。我甚至不再需要分配器模板参数了。一旦您将高效的节点分配职责吸收到树结构中,对于您的分配器来说,类模板就不再有什么好处了,因为它就像一个malloc和free接口,而动态分派在您只涉及一次的时候变得非常便宜,例如,如果出于某种原因仍然需要自定义分配器,则每128个节点会连续分配/释放一次

所以,若我在寻找具有 STL,我需要指定一个自定义分配器,还是可以依赖 STL能帮我做那件事吗


回到这个问题,如果你真的有一个非常重要的性能需求,你需要预先准备好大量的数据,你必须通过测量来处理每一帧,或者事后看,你甚至可以考虑滚动一些你自己的数据结构,这些节点存储在STD::向量之类的东西中。虽然这听起来适得其反,但与整天摆弄和试验内存分配器相比,它所需的时间要少得多,更不用说使用32位索引将节点分配到std::vector的索引链表将使链接的成本减半,并且可能比符合std::分配器的自由列表花费更少的时间来实现,例如,如果人们更频繁地这样做,链表可能会再次变得更流行,由于我认为它们在使用时,效率很低,如果用高效的方式分配节点,它们实际上可能是某些问题的优秀数据结构。

当然,它可以从一个标准的LIB实现到下一个不同,但上次我检查的LIBS,如MSVC、GNU C++和EASTL,链接结构在单个分配中分配节点和元素数据

然而,每个节点仍然是针对std::allocator一次分配一个,这是一个相当通用的可变长度分配器,尽管它至少可以假设所分配的所有元素都是特定的数据类型,但很多时候我发现它只是默认为VTune和CodeXL会话中的malloc调用。有时甚至会出现线程安全的内存分配,当数据结构本身不是为并发修改或同时读/写而设计的,而使用线程安全的通用内存分配一次分配一个节点时,这有点浪费

但是,如果您希望允许客户机将其自己的自定义分配器作为模板参数传入,那么这种设计是有意义的。在这种情况下,您不希望数据结构共享内存,因为这将与分配器可能希望执行的操作相冲突。对于链接结构,尤其是是否一次分配一个节点,需要做出决定 nd将更高效的分配技术(如自由列表)的职责传递给分配器,以使每个节点的分配更高效,或者避免依赖分配器使分配更高效,并通过以连续方式一次分配多个节点并对其进行池化来有效地在数据结构中进行池化。标准库倾向于前一个路由,不幸的是,这会使像std::list和std::map这样的东西在用于默认std::分配器时效率非常低

就我个人而言,对于链接结构,我使用我自己的手动解决方案,该解决方案依赖于32位索引到数组ex:std::vector,它有效地充当池内存,并像索引自由列表一样工作,如下所示:

。。。我们可能实际将链表节点存储在std::vector中。这些链接只是让我们在固定时间内移除东西,并在固定时间内回收这些空白空间的一种方式。真实的示例比上面的伪代码稍微复杂一些,因为该代码只适用于POD。真实的示例与标准容器一样,使用对齐的存储、新的放置和手动dtor调用,但没有那么复杂。类似情况如下:

。。。例如,如果您不希望指针失效来存储列表节点,则使用std::vector或类似std::deque的双链接索引列表。在这种情况下,链接允许我们在遍历向量的连续内存时跳过。其要点是允许在列表的任意位置进行固定时间的删除和插入,同时保留遍历时的插入顺序。如果我们使用swap-to-back和pop-back技术从中间进行固定时间删除,则仅使用std::vector就会丢失插入顺序

除了使所有内容更连续、更便于缓存遍历以及更快地分配和释放外,它还可以将64位体系结构上的链接大小减半,而我们可以使用32位索引,将其转换为存储节点的随机访问序列

链表实际上在C++中积累了一个非常糟糕的ReP,我相信这很大程度上是因为这个原因。人员基准测试针对默认分配器使用std::list,并在遍历过程中出现大量缓存未命中和昂贵的、可能是线程安全的内存分配(插入每个节点并移除每个节点)等形式的瓶颈。类似的情况是,如今人们对无序的_映射和无序的_集的偏好超过了映射和集。哈希表可能总是有一些边缘,但当map和set一次只使用一个通用分配器一个节点,并且在树遍历时会导致大量缓存未命中时,该边缘是如此倾斜

所以,若我在寻找具有 STL,我需要指定一个自定义分配器,还是可以依赖 STL能帮我做那件事吗

测量/配置文件的建议总是明智的,但如果您的需求真的很关键,比如您在每个帧上重复循环数据,并且它存储数十万个或更多元素,同时在每个帧的中间重复插入和移除元素,然后,在使用std::list或std::map之前,我至少会获得一个免费列表。而链表是如此简单的数据结构,如果你真的用标准库中的链表结构来解决热点问题,我建议你使用自己的链表,而不必处理分配器和数据结构的组合来实现一个有效的解决方案,只要有一个如果实现起来非常简单,那么它的默认形式将非常有效地满足您的确切需求

我过去经常摆弄分配器,研究数据结构,并尝试通过试验分配器来提高它们的效率,取得了一定的成功,这足以鼓励我,但效果并不惊人,但我发现,我的生活变得更加容易,只需制作链接结构,将他们的记忆集中在最前面,这给了我最惊人的结果。有趣的是,仅仅创建这些在分配策略方面更高效的数据结构所花费的时间比我在尝试第三方分配程序以及实现自己的分配程序时所花费的时间要少。这里有一个我快速制作的例子,它使用链表来检测400万个粒子的碰撞。这很旧,所以它在i3上运行

它使用单链接列表,使用我自己的类似deque的容器来存储节点,如下所示:

类似的事情是,500k可变大小的代理之间的冲突空间索引只花了2个小时就实现了整个过程,我甚至懒得多线程处理它:

我主要针对那些认为链表效率很低的人指出这一点,因为只要你以一种高效且相对连续的方式存储节点,它们就真的很有用 我是你的武器库的补充工具。我认为C++社区在很大程度上会把它们放得太仓促,因为我完全失去了链接列表。正确地使用,它们可以减少堆分配,而不是将它们相乘并改进空间局部性,而不是降低它的Ex:如果上面使用了一个单独的STD::vector或SmallVector的实例,每个单元格都有固定的SBO,而不是只存储一个32位整数,那么考虑上面的网格图。而且写一个链表并不需要很长时间,比如说,它可以非常高效地分配节点——如果有人花半个多小时来写数据结构和单元测试,我会感到惊讶。比如说,一棵高效的红黑树可能需要几个小时,但这没什么大不了的

这些天我只是直接把链接的节点存储在像std::vector这样的东西里面,我自己的std::deque的chunkier等价物,tbb::concurrent_vector如果我需要构建一个并发链接结构,等。当高效分配被纳入数据结构的职责中时,生活就变得容易多了,而不必将高效分配和数据结构作为两个完全独立的概念来考虑,并且必须将所有这些不同类型的分配器快速地传递到各地。我现在喜欢的设计是:

// Creates a tree storing elements of type T with 'BlockSize' contiguous
// nodes allocated at a time and pooled.
Tree<T, BlockSize> tree;
。。。或者我只是省略BlockSize参数,让节点存储在std::vector中,并进行摊销重新分配,同时连续存储所有节点。我甚至不再需要分配器模板参数了。一旦您将高效的节点分配职责吸收到树结构中,对于您的分配器来说,类模板就不再有什么好处了,因为它就像一个malloc和free接口,而动态分派在您只涉及一次的时候变得非常便宜,例如,如果出于某种原因仍然需要自定义分配器,则每128个节点会连续分配/释放一次

所以,若我在寻找具有 STL,我需要指定一个自定义分配器,还是可以依赖 STL能帮我做那件事吗


回到这个问题,如果你真的有一个非常重要的性能需求,你需要预先准备好大量的数据,你必须通过测量来处理每一帧,或者事后看,你甚至可以考虑滚动一些你自己的数据结构,这些节点存储在STD::向量之类的东西中。虽然这听起来适得其反,但与整天摆弄和试验内存分配器相比,它所需的时间要少得多,更不用说使用32位索引将节点分配到std::vector的索引链表将使链接的成本减半,并且可能比符合std::分配器的自由列表花费更少的时间来实现,例如,如果人们更频繁地这样做,链表可能会再次变得更流行,由于我认为它们在以高效分配节点的方式使用时,很容易被视为低效,因此它们实际上可能是解决某些问题的优秀数据结构。

真正的问题是正确选择,然后实现正确运行的程序。5%的代码库(用某种东西替换库存容器或分配器可能会带来一些好处)发生得比较晚,与实际问题相比是小菜一碟。请注意,STL是SGI的一个库,SGI是一家长期不存在的公司。列表是标准库的一部分,源于旧STL中的列表。但这是二十世纪的谈话,4 C++标准之前。标准库是一个规范,有多个实际实现。真正的问题是正确选择,然后实现正确运行的程序。5%的代码库(用某种东西替换库存容器或分配器可能会带来一些好处)发生得比较晚,与实际问题相比是小菜一碟。请注意,STL是SGI的一个库,SGI是一家长期不存在的公司。列表是标准库的一部分,源于旧STL中的列表。但这是二十世纪的谈话,4 C++标准之前。标准库是一个规范,有多个实际实现。