我有vector
:
vector<Body*> Bodies;
它包含我已定义的Body
个对象的指针。
我还有unsigned int const
,其中包含我希望在body
中拥有的bodies
个对象的数量。
unsigned int const NumParticles = 1000;
我填充了Bodies
NumParticles
个Body
个对象。
现在,如果我希望迭代一个循环,例如调用Body
中的每个Bodies
的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();
}
每个人都有专业人士和个人。在安全性,可读性和惯例方面,我想知道哪一个(如果有的话)是更好的做法。
答案 0 :(得分:3)
我希望,鉴于编译器(至少在这种情况下)可以内联std::vector
中的所有相关代码,它将是相同的代码[除了1000是机器代码中的真正常量文字,并且Bodies.size()
将成为&#34;变量&#34;值]。
调查结果的简短摘要:
编译器在每次迭代时都不会为向量的size()
调用函数,它会在循环开始时计算它,并将其用作&#34;常量值& #34 ;.
循环中的实际代码是相同的,只有循环的准备是不同的。
一如既往:如果性能非常重要,请使用您的数据和编译器测量系统。否则,编写对您的设计最有意义的代码(我更喜欢使用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();
}
}
(以及填充数组的main
函数,并调用Process1(),Process2()和Process3() - 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
虽然汇编代码对于每种情况看起来略有不同,但在实践中,我说你很难用来衡量这些循环之间的差异,事实上,{{{代码上的1}}显示它是所有循环的同一时间&#34; [这是循环中10000个元素和100个对Process1,Process2和Process3的调用,否则时间由perf
中的new X
控制:
main
除非你认为百分之十的百分比是显着的 - 并且它可能需要花费一周的时间来运行,但这只是十分之几秒[在我的机器上0.163秒],并且可能更多测量错误比其他任何东西 - 更短的时间实际上是理论上应该是最慢的,Process2,使用 31.29% a.out a.out [.] Process1
31.28% a.out a.out [.] Process3
31.13% a.out a.out [.] Process2
。我做了另一个循环次数较多的运行,现在每个循环的测量值相互之间的0.01% - 换句话说就是花费的时间相同。
当然,如果你仔细观察,你会看到所有三个变体的实际循环内容基本相同,除了vec.size()
的早期部分,这更简单,因为编译器知道我们将在至少有一个循环 - Process3
和Process1
必须检查&#34;是否为向量空白&#34;在第一次迭代之前。这会对非常短的矢量长度产生影响。
答案 1 :(得分:2)
我会投票支持范围:
for (auto* body : Bodies)
{
body->Update();
}
答案 2 :(得分:2)
NumParticles
不是向量的属性。它是相对于矢量的一些外部常数。我更喜欢使用向量的属性size()
。在这种情况下,代码对读者来说更安全,更清晰。
通常使用一些常量而不是size()
对于读者而言,一般来说,常量可能与size()
不等。
因此,如果您想说读者您要处理矢量的所有元素,那么最好使用size()。否则使用常量。
当重音放在常量上时,这个隐式规则当然有例外。在这种情况下,最好使用常量。但这取决于具体情况。
答案 3 :(得分:1)
我建议您使用.size()
函数,而不是定义新的常量。
为什么?
安全性:由于.size()
不会引发任何例外情况,因此使用.size()
非常安全。
可读性:恕我直言,Bodies.size()
比Bodies
更清楚地传达了向量NumParticles
的大小。
公约:根据惯例,最好使用.size()
,因为它是向量的属性,而不是变量NumParticles
。
效果:.size()
是常量复杂性成员函数,因此使用const int
和.size()
之间没有明显的性能差异。< / p>
答案 4 :(得分:0)
我更喜欢这种形式:
for (auto const& it : Bodies)
{
it->Update();
}