在向量中分配临时元素(知道最大数量)的最快方法?
Fastest way to allocate temporary elements (knowing maximum number) in a vector?
在一个函数中,我需要将一些整数存储在一个向量中。该函数被调用了很多次。我知道它们小于 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();
我可能会采取某种中间立场:
std::vector<int> list;
list.reserve(10);
...其余的可能与您的第二个版本非常相似。然而,老实说,这是否真的会产生很大的不同可能值得商榷。
如果您使用静态向量,它将只分配一次。
第一个示例运行速度较慢,因为它在每次调用时分配和销毁向量。
我要在这里走一条非常不酷的路线。冒着被钉在十字架上的风险,我建议 std::vector
在这里不是很好。一个例外是,如果你幸运地使用了内存分配器并通过分配器获得了时间局部性,那么创建和销毁一堆极小的 vectors
通常不会提供。
等等!
在人们杀了我之前,我想说 vector
很棒,一般来说,它是可用的最全面的数据结构之一。但是当你在一个紧密的循环中反复创建一堆极小的 vectors
结果看到这样的热点时(希望使用分析器),这就是 vector
的这种直接用法可以咬你
问题是它是一个堆分配的结构(基本上是一个动态数组),当我们处理一大堆像这样的极小数组时,我们真的想在顶部使用经常缓存的内存当我们可以时,allocate/free 如此便宜的堆栈。
缓解这种情况的一种方法是在重复调用中重复使用相同的向量。将其存储在外部调用函数的范围内并通过引用将其传递,clear
它,执行您的 push_backs
,冲洗并重复。值得注意的是 clear
不会释放向量中的任何内存,因此它保留了以前的容量(当我们想要重用相同的内存并播放到时间局部性时,这里很有用)。
但在这里我们可以玩那个堆栈。作为一个简化的例子(使用 C 风格的代码,在 C++ 中不是很规范,甚至不关心异常安全,但更容易说明):
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);
当然,如果你使用这样的东西,你应该将它正确地包装在一个可重复使用的 class 模板中,该模板进行边界检查,不使用 memcpy
,具有异常安全性,一个正式 push_back 方法,emplace_back,复制构造函数,移动构造函数,交换,可能是填充构造函数,范围构造函数,擦除,范围擦除,插入,范围插入,大小,空,iterators/begin/end,使用 placement new 来避免需要复制赋值或默认构造函数等。
该解决方案在 N <= 32
时使用堆栈(可以使用适合您的常见情况需求的不同数字),然后在超过时切换到堆。这使它能够有效地处理您的常见案例场景,而不仅仅是在 N
在某些病态案例中可能很大的罕见案例场景中。这使得它有点类似于 C 中的可变长度数组(我实际上希望我们在 C++ 中有一些东西,至少在 std::dynarray
可用之前)但是没有 VLA 可能有的堆栈溢出倾向,因为它在极少数情况下切换到堆场景。
我将所有这些符合标准的形式应用到一个基于这个想法的结构中,并使用一个接受 <T, FixedN>
的 class 模板,现在使用它几乎和 vector
一样多,因为我处理过很多这样的案例,其中重复创建了极小的数组,在绝大多数常见情况下,这些数组应该适合堆栈(但总是有那些极其罕见的例外可能性)。它消除了我在地图上与内存相关的许多探查器热点。
...但是应用这个基本想法可能会给您带来很大的提升。如果它在你的测量中得到回报,你可以应用上面的那种努力将它包装到一个安全的容器中,保留 C++ 对象语义,我认为它应该在你的情况下相当多。
在一个函数中,我需要将一些整数存储在一个向量中。该函数被调用了很多次。我知道它们小于 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();
我可能会采取某种中间立场:
std::vector<int> list;
list.reserve(10);
...其余的可能与您的第二个版本非常相似。然而,老实说,这是否真的会产生很大的不同可能值得商榷。
如果您使用静态向量,它将只分配一次。 第一个示例运行速度较慢,因为它在每次调用时分配和销毁向量。
我要在这里走一条非常不酷的路线。冒着被钉在十字架上的风险,我建议 std::vector
在这里不是很好。一个例外是,如果你幸运地使用了内存分配器并通过分配器获得了时间局部性,那么创建和销毁一堆极小的 vectors
通常不会提供。
等等!
在人们杀了我之前,我想说 vector
很棒,一般来说,它是可用的最全面的数据结构之一。但是当你在一个紧密的循环中反复创建一堆极小的 vectors
结果看到这样的热点时(希望使用分析器),这就是 vector
的这种直接用法可以咬你
问题是它是一个堆分配的结构(基本上是一个动态数组),当我们处理一大堆像这样的极小数组时,我们真的想在顶部使用经常缓存的内存当我们可以时,allocate/free 如此便宜的堆栈。
缓解这种情况的一种方法是在重复调用中重复使用相同的向量。将其存储在外部调用函数的范围内并通过引用将其传递,clear
它,执行您的 push_backs
,冲洗并重复。值得注意的是 clear
不会释放向量中的任何内存,因此它保留了以前的容量(当我们想要重用相同的内存并播放到时间局部性时,这里很有用)。
但在这里我们可以玩那个堆栈。作为一个简化的例子(使用 C 风格的代码,在 C++ 中不是很规范,甚至不关心异常安全,但更容易说明):
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);
当然,如果你使用这样的东西,你应该将它正确地包装在一个可重复使用的 class 模板中,该模板进行边界检查,不使用 memcpy
,具有异常安全性,一个正式 push_back 方法,emplace_back,复制构造函数,移动构造函数,交换,可能是填充构造函数,范围构造函数,擦除,范围擦除,插入,范围插入,大小,空,iterators/begin/end,使用 placement new 来避免需要复制赋值或默认构造函数等。
该解决方案在 N <= 32
时使用堆栈(可以使用适合您的常见情况需求的不同数字),然后在超过时切换到堆。这使它能够有效地处理您的常见案例场景,而不仅仅是在 N
在某些病态案例中可能很大的罕见案例场景中。这使得它有点类似于 C 中的可变长度数组(我实际上希望我们在 C++ 中有一些东西,至少在 std::dynarray
可用之前)但是没有 VLA 可能有的堆栈溢出倾向,因为它在极少数情况下切换到堆场景。
我将所有这些符合标准的形式应用到一个基于这个想法的结构中,并使用一个接受 <T, FixedN>
的 class 模板,现在使用它几乎和 vector
一样多,因为我处理过很多这样的案例,其中重复创建了极小的数组,在绝大多数常见情况下,这些数组应该适合堆栈(但总是有那些极其罕见的例外可能性)。它消除了我在地图上与内存相关的许多探查器热点。
...但是应用这个基本想法可能会给您带来很大的提升。如果它在你的测量中得到回报,你可以应用上面的那种努力将它包装到一个安全的容器中,保留 C++ 对象语义,我认为它应该在你的情况下相当多。