在向量的每个struct元素中重置值的最快方法?

时间:2014-03-27 19:25:19

标签: c++ performance vector

非常类似于this question,除了代替vector<int>我有vector<struct myType>

如果我想为向量中的每个元素重置(或者就此而言,设置为某个值)myType.myVar,那么最有效的方法是什么?

现在我正在迭代:

for(int i=0; i<myVec.size(); i++) myVec.at(i).myVar = 0;

但是由于保证向量是连续存储的,所以肯定是一种更好的方法吗?

6 个答案:

答案 0 :(得分:7)

重置需要遍历向量的每个元素,因此需要至少 O(n)复杂度。您当前的算法需要O(n)。

在这种特殊情况下,您可以使用operator[]代替at(可能会引发异常)。但我怀疑这是你申请的瓶颈。

在此说明中,您应该使用std::fill

std::fill(myVec.begin(), myVec.end(), 0);

但除非你想要字节级别并将一块内存设置为0,这不仅会让你头疼,而且还会让你在大多数情况下失去可移植性,这里没有什么可以改进的。

答案 1 :(得分:6)

而不是下面的代码

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;

由于&#34; at&#34; 方法在内部检查索引是否超出范围。但是当你的循环索引正在照顾(myVec.size())时,你可以避免额外的检查。否则,这是最快的方法。

修改

除此之外,我们可以在执行for循环之前存储向量的size()。这将确保for循环中没有进一步调用方法size()。

答案 2 :(得分:2)

最快的方法之一是执行循环展开并打破传统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中的基础技术和编译器将尝试的一种优化(一般是循环展开)(实际上他们对此非常擅长,所以我们不妨让他们完成自己的工作)。

答案 3 :(得分:1)

除了之前所说的,你应该考虑如果你打开优化,编译器可能会执行循环展开,这将使循环本身更快。

答案 4 :(得分:1)

预增量++i也只需要少于增量i++的指令。 解释here

答案 5 :(得分:1)

谨防花费大量时间考虑编译器将为您处理的优化细节。

以下是我理解OP的四个实现,以及使用gcc 4.8和--std=c++11 -O3 -S生成的代码

声明:

#include <algorithm>
#include <vector>

struct T {
  int irrelevant;
  int relevant;
  double trailing;
};

显式循环实现,大致来自提供给OP的答案和注释。除标签外,两者都生成相同的机器代码。

                                                        .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