在向量中分配临时元素(知道最大数量)的最快方法?

Posted

技术标签:

【中文标题】在向量中分配临时元素(知道最大数量)的最快方法?【英文标题】:Fastest way to allocate temporary elements (knowing maximum number) in a vector? 【发布时间】:2015-05-29 16:43:37 【问题描述】:

在一个函数中,我需要将一些整数存储在一个向量中。该函数被多次调用。我知道它们小于 10,但是对于函数的每次调用,这个数字都是可变的。怎样选择才能有更好的表现?

在示例中我发现:

std::vector<int> list(10)
std::vector<int>::iterator it=list.begin();
unsigned int nume_of_elements_stored;

for ( ... iterate on some structures ... )
    if (... a specific condition ...)
         *it= integer from structures ;
         it++;
         nume_of_elements_stored++;
     

慢于:

std::vector<int> list;
unsigned int num_of_elements_stored(0);

for ( ... iterate on some structures ... )
    if (... a specific condition ...)
         list.push_back( integer from structures )
     

num_of_elements_stored=list.size();

【问题讨论】:

如果reserve + emplace_back 实现优于两者,我不会感到震惊。 @WhozCraig:鉴于他在向量中放入的内容是ints,emplace_back 与复制相比产生影响的可能性非常小。 通常您不会将元素的数量单独存储在vector 中。只需使用size() 我不得不问 - 为什么您认为数组创建和销毁(使用这种数量级的数组)是性能瓶颈?也许如果你补充说这是每秒执行无数次或其他什么,它可能会成为一个更大的问题。 【参考方案1】:

我要在这里走一条非常不酷的路线。冒着被钉在十字架上的风险,我建议std::vector 在这里不是很好。一个例外是,如果您对内存分配器很幸运,并通过分配器获得了创建和销毁通常无法提供的大量 vectors 的时间局部性。

等等!

在人们杀了我之前,我想说vector 很棒,一般来说,它是可用的最全面的数据结构之一。但是,当您查看这样的热点时(希望使用分析器),由于在紧密循环中反复创建了一堆很小的vectors,这就是vector 的这种直接使用可以咬你的地方。

问题在于它是一个堆分配结构(基本上是一个动态数组),当我们处理大量像这样的小数组时,我们真的想在顶部使用经常缓存的内存尽可能便宜地分配/释放的堆栈。

缓解这种情况的一种方法是在重复调用中重复使用相同的向量。将其存储在外部调用函数的范围内并通过引用传递它,clear 它,执行您的push_backs,冲洗并重复。值得注意的是,clear 不会释放向量中的任何内存,因此它保留了之前的容量(当我们想要重用相同的内存并播放时间局部性时,这很有用)。

但是在这里我们可以玩到那个堆栈。作为一个简化的例子(使用 C 风格的代码,在 C++ 中不是很 kosher,甚至不关心异常安全,但更容易说明):

int stack_mem[32];
int num = 0;
int cap = 32;
int* ptr = stack_mem;

for ( ... iterate on some structures ... )

    if (... a specific condition ...)
    
         if (num == cap)
         
             cap *= 2;
             int* new_ptr = static_cast<int*>(malloc(cap * sizeof(int)));
             memcpy(new_ptr, ptr, num * sizeof(int));
             if (ptr != stack_mem)
                  free(ptr);
             ptr = new_ptr;
         
         ptr[num++] = your_int;
    


if (ptr != stack_mem)
     free(ptr);

当然,如果你使用这样的东西,你应该将它正确地包装在一个可重用的类模板中,该模板进行边界检查,不使用memcpy,具有异常安全性,正式的 push_back 方法,emplace_back,复制 ctor ,移动 ctor,交换,可能是填充 ctor,范围 ctor,擦除,范围擦除,插入,范围插入,大小,空,迭代器/开始/结束,使用放置新以避免需要复制分配或默认 ctor 等。

解决方案在N &lt;= 32 时使用堆栈(可以使用适合您的常见情况需要的不同数字),然后在超出时切换到堆。这允许它有效地处理您的常见案例场景,但也不仅仅是在那些罕见的案例场景中,N 在某些病理情况下可能很大。这使得它在某种程度上可以与 C 中的可变长度数组相媲美(我实际上希望我们在 C++ 中拥有这一点,至少在std::dynarray 可用之前),但没有堆栈溢出倾向,因为它在极少数情况下切换到堆。

我将所有这些符合标准的形式应用到一个基于这个想法的结构和一个接受&lt;T, FixedN&gt; 的类模板中,现在使用它几乎和vector 一样多,因为我使用 teeny 处理过很多这样的案例重复创建的数组在绝大多数常见情况下应该适合堆栈(但总是具有那些极其罕见的例外可能性)。它消除了我在地图上与内存相关的许多分析器热点。

...但是应用这个基本想法可能会给您带来很大的提升。如果在您的测量中得到回报,您可以应用上述那种将其包装到保留 C++ 对象语义的安全容器中的努力,我认为在您的情况下应该相当多。

【讨论】:

同意。 int list[10];int numInts 设置为实际使用的整数值的数量将比 std::vector&lt;int&gt; list(10);LOT Folly small_vector 可能会感兴趣。 这是一个很好的解决方案,但是我需要在列表向量之后进行操作。所以对我来说,保留一个向量而不是一个数组更安全! @TommasoFerrari small_vector 如果您真的处于性能紧缩状态,那么您可能会非常感兴趣。它可以为您提供安全且符合序列的界面,使其易于与vector 互换。它看起来几乎与我很久以前在专有代码库中最终实现的用于优化小向量热点的代码相同,实际上可能会更好(我的代码是为 C++03 设计的)。 @TommasoFerrari 向量和数组都只是有序的值集。您可以对其中一个进行的任何操作,都可以对另一个进行。【参考方案2】:

我可能会选择一种中间立场:

std::vector<int> list;
list.reserve(10);

...其余的可能与您的第二个版本非常相似。然而,老实说,这是否真的会产生很大的影响可能还有待商榷。

【讨论】:

我不确定我是否理解。如果您知道最多保留 10 个元素,那么 10 个元素的简单 C 数组不是更简单更快吗?类似于int list [10]。为什么要用向量? @lrleon:因为(除其他外)数组不跟踪自己的大小。在这种情况下,我想他可以使用它,但是在我下降到使用数组之前,我必须非常渴望获得额外的速度(而且我完全不确定我是否会获得任何额外的速度)。 【参考方案3】:

如果你使用静态向量,它只会被分配一次。 第一个示例运行速度较慢,因为它每次调用都会分配和销毁向量。

【讨论】:

我发现你说的不是真的。我试图在一个类中构造向量列表并将函数定义为类方法。它似乎比第一个解决方案更慢。我认为在优化编译中,编译器知道如何管理内存。

以上是关于在向量中分配临时元素(知道最大数量)的最快方法?的主要内容,如果未能解决你的问题,请参考以下文章

标准向量性能/替代

在 C++ 的构造函数中分配对象向量时出错

删除向量的元素

试图从 Derived* 的向量中分配 Base* 的向量

在 OpenCV 中分配矩阵元素

检查元素是不是在两个向量中的最快方法