在向量的每个结构元素中重置值的最快方法?
Posted
技术标签:
【中文标题】在向量的每个结构元素中重置值的最快方法?【英文标题】:Fastest way to reset a value in every struct element of a vector? 【发布时间】:2014-03-27 19:25:19 【问题描述】:很像this question,只是vector<int>
我有vector<struct myType>
。
如果我想为向量中的每个元素重置(或为此设置某个值)myType.myVar
,最有效的方法是什么?
现在我正在迭代:
for(int i=0; i<myVec.size(); i++) myVec.at(i).myVar = 0;
但既然保证向量是连续存储的,那么肯定有更好的方法吗?
【问题讨论】:
根据这个答案***.com/a/849190/16429,C++ 向量实际上是连续存储的。 @Clay 我读到了一个答案,他们不是(现在找不到)——但这有更多的支持。将相应地进行编辑,谢谢。 【参考方案1】:重置需要遍历向量的每个元素,因此它需要至少 O(n) 复杂度。您当前的算法需要 O(n)。
在这种特殊情况下,您可以使用operator[]
而不是at
(这可能会引发异常)。但我怀疑这是您的应用程序的瓶颈。
在此注释中,您可能应该使用std::fill
:
std::fill(myVec.begin(), myVec.end(), 0);
但是除非你想去字节级别,把一块内存设置为0
,这不仅会让你头疼,而且在大多数情况下也会让你失去可移植性,这里没有什么可以改进的。
【讨论】:
myVec.resize(10, 0)
肯定是 O(1)?我想知道是否有类似的方法,但在结构中保留其他变量的值。不过谢谢!
@OllieFord。不,resize
的复杂度也是linear。
好的,谢谢。我有一个后续问题,是否可以将其合并到std::sort
的“最后一次迭代”中,以重置它所基于的排序值。请告知我应该将其作为新问题发布,还是在详细信息中进行编辑?
@OllieFord,“合并”是什么意思?
选词不当。在我完成基于myVal
的排序后,我想设置myVal = 0
。我可以在std::sort
的最后一次运行中以某种方式做吗?【参考方案2】:
代替下面的代码
for(int i=0; i<myVec.size(); i++) myVec.at(i).myVar = 0;
按如下方式进行:
size_t sz = myVec.size();
for(int i=0; i<sz; ++i) myVec[i].myVar = 0;
由于 "at" 方法在内部检查索引是否超出范围。但是由于您的循环索引正在处理(myVec.size()),您可以避免额外的检查。否则这是最快的方法。
编辑
除此之外,我们可以在执行for循环之前存储vector的size()。这样可以确保在for循环中不再调用size()方法。
【讨论】:
不错!感谢您在.at()
上提供的额外信息 - 我只使用它,因为我知道它“更安全”。现在我也知道(至少部分)为什么:)
既然我们这么喜欢微优化,你也应该把myVec.size()
存储在一个变量中,而不是每次循环都调用一个函数。
哦,你也应该使用++i
而不是i++
。你知道。保存std::size_t
整数的副本可能很有用。
@OllieFord:当 at() 被调用时,它会调用 size() 方法,然后检查 index 是否小于 size。所以在某种程度上,这在访问每个元素时调用 size() 的开销非常小。 operator[] 访问只做指针添加和返回。我想这会有所帮助。
@tmp:它在每次迭代中显式调用 size() 。在循环开始之前调用它一次并将其分配给一个变量。【参考方案3】:
最快的方法之一是执行循环展开并打破传统for
循环造成的速度限制,这些循环会导致大量现金溢出。在你的情况下,因为它是运行时的事情,所以没有办法应用模板元编程,所以旧的 Duff 的设备 的变体就可以解决问题
#include <iostream>
#include <vector>
using namespace std;
struct myStruct
int a;
double b;
;
int main()
std::vector<myStruct> mV(20);
double val(20); // the new value you want to reset to
int n = (mV.size()+7) / 8; // 8 is the size of the block unroll
auto to = mV.begin(); //
switch(mV.size()%8)
case 0: do (*to++).b = val;
case 7: (*to++).b = val;
case 6: (*to++).b = val;
case 5: (*to++).b = val;
case 4: (*to++).b = val;
case 3: (*to++).b = val;
case 2: (*to++).b = val;
case 1: (*to++).b = val;
while (--n>0);
// just printing to verify that the value is set
for (auto i : mV) std::cout << i.b << std::endl;
return 0;
在这里,我选择执行 8 块展开,以重置 myStruct
结构的值(比方说)b。可以调整块大小并有效地展开循环。请记住,这是memcpy
中的底层技术,也是编译器将尝试的优化之一(通常是循环展开)(实际上他们在这方面做得很好,所以我们不妨让他们完成他们的工作)。
【讨论】:
Duff 的设备并不能保证性能会更快。在许多情况下,它会降低性能。【参考方案4】:除了前面所说的之外,您应该考虑到,如果您打开优化,编译器可能会执行循环展开,这将使循环本身更快。
【讨论】:
【参考方案5】:同样,预增量 ++i
比后增量 i++
需要更少的指令。
解释here
【讨论】:
【参考方案6】:请注意不要花费大量时间考虑编译器会为您处理的优化细节。
以下是我理解的 OP 的四种实现,以及使用 gcc 4.8 和 --std=c++11 -O3 -S
生成的代码
声明:
#include <algorithm>
#include <vector>
struct T
int irrelevant;
int relevant;
double trailing;
;
显式循环实现,大致来自提供给 OP 的答案和 cmets。除了标签之外,两者都产生了相同的机器代码。
.cfi_startproc
movq (%rdi), %rsi
void clear_relevant(std::vector<T>* vecp) movq 8(%rdi), %rcx
for(unsigned i=0; i<vecp->size(); i++) xorl %edx, %edx
vecp->at(i).relevant = 0; xorl %eax, %eax
subq %rsi, %rcx
sarq $4, %rcx
testq %rcx, %rcx
je .L1
.p2align 4,,10
.p2align 3
.L5:
void clear_relevant2(std::vector<T>* vecp) salq $4, %rdx
std::vector<T>& vec = *vecp; addl $1, %eax
auto s = vec.size(); movl $0, 4(%rsi,%rdx)
for (unsigned i = 0; i < s; ++i) movl %eax, %edx
vec[i].relevant = 0; cmpq %rcx, %rdx
jb .L5
.L1:
rep ret
.cfi_endproc
另外两个版本,一个使用std::for_each
,另一个使用范围for
语法。这里两个版本的代码有细微的差别(标签除外):
.cfi_startproc
movq 8(%rdi), %rdx
movq (%rdi), %rax
cmpq %rax, %rdx
je .L17
void clear_relevant3(std::vector<T>* vecp) .p2align 4,,10
for (auto& p : *vecp) p.relevant = 0; .p2align 3
.L21:
movl $0, 4(%rax)
addq $16, %rax
cmpq %rax, %rdx
jne .L21
.L17:
rep ret
.cfi_endproc
.cfi_startproc
movq 8(%rdi), %rdx
movq (%rdi), %rax
cmpq %rdx, %rax
void clear_relevant4(std::vector<T>* vecp) je .L12
std::for_each(vecp->begin(), vecp->end(), .p2align 4,,10
[](T& o)o.relevant=0;); .p2align 3
.L16:
movl $0, 4(%rax)
addq $16, %rax
cmpq %rax, %rdx
jne .L16
.L12:
rep ret
.cfi_endproc
【讨论】:
以上是关于在向量的每个结构元素中重置值的最快方法?的主要内容,如果未能解决你的问题,请参考以下文章
用Javascript清空(重置)文件类型的INPUT元素的值