使用 .size() 与 const 变量进行循环
Using .size() vs const variable for loops
我有一个vector
:
vector<Body*> Bodies;
它包含指向我定义的Body
对象的指针。
我还有一个unsigned int const
,其中包含我希望在bodies
中拥有的body
对象的数量。
unsigned int const NumParticles = 1000;
我已经用NumParticles
数量的Body
对象填充了Bodies
。
现在,如果我想遍历一个循环,例如在Bodies
中调用Body
的每个 Update() 函数,我有两个选择:
第一:
for (unsigned int i = 0; i < NumParticles; i++)
{
Bodies.at(i)->Update();
}
或第二:
for (unsigned int i = 0; i < Bodies.size(); i++)
{
Bodies.at(i)->Update();
}
每个都有优点和缺点。我想知道哪一个(如果有的话)在安全性、可读性和惯例方面是更好的做法。
,鉴于编译器(至少在这种情况下)可以在std::vector
中内联所有相关代码,它将是相同的代码[除了 1000 是机器代码中真正的常量文字,Bodies.size()
将是"变量"值]。
调查结果的简短摘要:
编译器不会为每次迭代调用一个函数来
size()
向量,而是在循环开始时计算该向量,并将其用作"常量值"。
循环中的实际代码是相同的,只是循环的准备不同。
一如既往:如果性能非常重要,请使用数据和编译器在系统上进行测量。否则,编写对您的设计最有意义的代码(我更喜欢使用
for(auto i : vec)
,因为这既简单又直接[并且适用于所有容器])
支持证据:
拿完咖啡后,我写了这段代码:
class X
{
public:
void Update() { x++; }
operator int() { return x; }
private:
int x = rand();
};
extern std::vector<X*> vec;
const size_t vec_size = 1000;
void Process1()
{
for(auto i : vec)
{
i->Update();
}
}
void Process2()
{
for(size_t i = 0; i < vec.size(); i++)
{
vec[i]->Update();
}
}
void Process3()
{
for(size_t i = 0; i < vec_size; i++)
{
vec[i]->Update();
}
}
(以及填充数组并调用 Process1()、Process2() 和 Process3() 的 main
函数 - main
位于一个单独的文件中,以避免编译器决定内联所有内容并使其难以分辨是什么)
下面是 g++ 4.9.2 生成的代码:
0000000000401940 <_Z8Process1v>:
401940: 48 8b 0d a1 18 20 00 mov 0x2018a1(%rip),%rcx # 6031e8 <vec+0x8>
401947: 48 8b 05 92 18 20 00 mov 0x201892(%rip),%rax # 6031e0 <vec>
40194e: 48 39 c1 cmp %rax,%rcx
401951: 74 14 je 401967 <_Z8Process1v+0x27>
401953: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
401958: 48 8b 10 mov (%rax),%rdx
40195b: 48 83 c0 08 add $0x8,%rax
40195f: 83 02 01 addl $0x1,(%rdx)
401962: 48 39 c1 cmp %rax,%rcx
401965: 75 f1 jne 401958 <_Z8Process1v+0x18>
401967: f3 c3 repz retq
0000000000401970 <_Z8Process2v>:
401970: 48 8b 35 69 18 20 00 mov 0x201869(%rip),%rsi # 6031e0 <vec>
401977: 48 8b 0d 6a 18 20 00 mov 0x20186a(%rip),%rcx # 6031e8 <vec+0x8>
40197e: 31 c0 xor %eax,%eax
401980: 48 29 f1 sub %rsi,%rcx
401983: 48 c1 f9 03 sar $0x3,%rcx
401987: 48 85 c9 test %rcx,%rcx
40198a: 74 14 je 4019a0 <_Z8Process2v+0x30>
40198c: 0f 1f 40 00 nopl 0x0(%rax)
401990: 48 8b 14 c6 mov (%rsi,%rax,8),%rdx
401994: 48 83 c0 01 add $0x1,%rax
401998: 83 02 01 addl $0x1,(%rdx)
40199b: 48 39 c8 cmp %rcx,%rax
40199e: 75 f0 jne 401990 <_Z8Process2v+0x20>
4019a0: f3 c3 repz retq
00000000004019b0 <_Z8Process3v>:
4019b0: 48 8b 05 29 18 20 00 mov 0x201829(%rip),%rax # 6031e0 <vec>
4019b7: 48 8d 88 40 1f 00 00 lea 0x1f40(%rax),%rcx
4019be: 66 90 xchg %ax,%ax
4019c0: 48 8b 10 mov (%rax),%rdx
4019c3: 48 83 c0 08 add $0x8,%rax
4019c7: 83 02 01 addl $0x1,(%rdx)
4019ca: 48 39 c8 cmp %rcx,%rax
4019cd: 75 f1 jne 4019c0 <_Z8Process3v+0x10>
4019cf: f3 c3 repz retq
虽然汇编代码在每种情况下看起来都略有不同,但在实践中,我会说你很难衡量这些循环之间的差异,事实上,对代码的运行perf
表明它"所有循环的时间相同"[这是 100000 个元素和 100 次对 Process1 的调用, 进程 2 和进程 3 在一个循环中,否则时间由 new X
在 main
] 中控制:
31.29% a.out a.out [.] Process1
31.28% a.out a.out [.] Process3
31.13% a.out a.out [.] Process2
除非你认为百分之一的百分之一是重要的——这可能是需要一周才能运行的东西,但这只有零点几秒[在我的机器上是 0.163 秒],而且测量误差可能比其他任何东西都大——而且更短的时间实际上是理论上应该最慢的时间, 进程2,使用vec.size()
。我用更高的循环计数进行了另一次运行,现在每个循环的测量值是彼此的 0.01% - 换句话说,花费的时间相同。
当然,如果你仔细观察,你会发现所有三个变体的实际循环内容基本上是相同的,除了Process3
的早期部分更简单,因为编译器知道我们将至少执行一个循环 - Process1
和Process2
必须在第一次迭代之前检查"向量是否为空"。这将对非常短的矢量长度产生影响。
我会投票支持范围:
for (auto* body : Bodies)
{
body->Update();
}
>NumParticles
不是向量的属性。它是相对于向量的某个外部常数。我更喜欢使用向量的属性size()
。在这种情况下,代码对读者来说更加安全和清晰。
通常使用一些常数而不是size()
对于读者来说意味着通常常量可以不等于size()
。
因此,如果你想告诉读者你要处理向量的所有元素,那么最好使用 size()。否则使用常量。
当然,当重音放在常量上时,这个隐式规则也有例外。 在这种情况下,最好使用常量。但这取决于上下文。
我建议您使用 .size()
函数而不是定义一个新常量。
为什么?
-
安全 :由于
.size()
不会引发任何异常,因此使用.size()
是完全安全的。 -
可读性 :恕我直言,
Bodies.size()
比NumParticles
更清楚地传达了矢量Bodies
的大小。
约定 :根据约定,最好使用
.size()
,因为它是向量的属性,而不是变量NumParticles
。性能:
.size()
是一个恒定复杂度的成员函数,因此使用const int
和.size()
之间没有明显的性能差异。
我更喜欢这种形式:
for (auto const& it : Bodies)
{
it->Update();
}
- 在循环中按顺序遍历成员变量
- 我可以创建一个包含两个变量的 for 循环,但时间复杂度仍然为 O(n) 吗?
- 与 for 循环中的变量混淆
- 如何在循环之外重新输入变量
- 如何使用将字符串拆分为 for 循环中的变量的程序
- 在变量声明中使用 for 循环
- C++ 和 Boost.Python - 如何将变量公开给 python 并在循环中更新它?
- C++ Code 在 for 循环期间不会累积变量中的总和,仅提供最终迭代值
- C++ 在循环中添加计数器变量并再次初始化其值
- (C++)虽然循环一次不起作用,但我引入了多个变量
- 如何添加循环期间分配的变量?
- C++,每个循环初始化一个新的静态变量
- 在 for 循环中更新两个变量时遇到问题C++
- 循环中的变量被设置为下一个数组的元素始终具有相同的内存地址?
- 需要循环帮助以迭代方式添加到程序集中的总和变量
- 在 c++ 中基于范围的 for 循环中使用引用作为控制变量
- 如何在循环中动态创建变量(c++)
- 变量循环范围会导致返回局部变量的地址引用
- ifstream变量循环不打印任何内容
- c++变量/循环问题