在vector的每个struct元素中重置值的最快方法

Fastest way to reset a value in every struct element of a vector?

本文关键字:方法 元素 vector struct      更新时间:2023-10-16

非常像这个问题,除了vector<int>换成了vector<struct myType>

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

现在我正在遍历:

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

但是既然vector保证是连续存储的,那么肯定有更好的方法吗?

重置将需要遍历向量的每个元素,因此它将需要至少 0 (n)的复杂度。你现在的算法需要O(n)

在这种特殊情况下,您可以使用operator[]而不是at(这可能会抛出异常)。但我怀疑这是你的应用程序的瓶颈。

在这一点上你应该使用std::fill:

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

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

代替下面的代码

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()方法。

最快的方法之一是执行循环展开并打破导致大量现金溢出的传统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中的底层技术,也是编译器将尝试的优化之一(通常是循环展开)(实际上它们非常擅长此道,所以我们不妨让它们完成自己的工作)。

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

同样,增量前++i比增量后i++需要的指令少。在这里解释

小心不要花太多时间考虑优化的细节,编译器会帮你处理的。

以下是我所理解的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,另一个使用range 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