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

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元素的值

检查一个 data.table 列中的所有元素以查看另一个 data.table 列中出现的每个值的最快方法

用VBA重置excel控件值的方法。

如何重置全局声明的向量[重复]

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