基于范围的 for 循环是否有利于性能
Is the ranged based for loop beneficial to performance?
在 Stack Overflow 上阅读有关C++迭代器和性能的各种问题**,我开始想知道编译器是否for(auto& elem : container)
"扩展"为最佳版本?(有点像auto
,编译器会立即推断出正确的类型,因此永远不会变慢,有时甚至更快)。
**例如,你写有关系吗
for(iterator it = container.begin(), eit = container.end(); it != eit; ++it)
或
for(iterator it = container.begin(); it != container.end(); ++it)
对于非无效容器?
标准是你的朋友,参见 [stmt.ranged]/1
对于表单的基于范围的 for 语句
for ( for-range-declaration : expression ) statement
让 range-init 等效于用括号括起来的表达式
( expression )
以及基于范围的 for 表单语句
for ( for-range-declaration : braced-init-list ) statement
让 range-init 等效于大括号初始化列表。在每种情况下,基于范围的
for
语句等效于{ auto && __range = range-init; for ( auto __begin = begin-expr, __end = end-expr; __begin != __end; ++__begin ) { for-range-declaration = *__begin; statement } }
所以是的,标准保证了达到最佳形式。
对于许多容器,例如 vector
,在此迭代期间修改(插入/擦除)它们是未定义的行为。
出于好奇,我决定查看这两种方法的汇编代码:
int foo1(const std::vector<int>& v) {
int res = 0;
for (auto x : v)
res += x;
return res;
}
int foo2(const std::vector<int>& v) {
int res = 0;
for (std::vector<int>::const_iterator it = v.begin(); it != v.end(); ++it)
res += *it;
return res;
}
两种方法的汇编代码(使用 -O3 和 gcc 4.6)完全相同(省略了 foo2
的代码,因为它完全相同):
080486d4 <foo1(std::vector<int, std::allocator<int> > const&)>:
80486d4: 8b 44 24 04 mov 0x4(%esp),%eax
80486d8: 8b 10 mov (%eax),%edx
80486da: 8b 48 04 mov 0x4(%eax),%ecx
80486dd: b8 00 00 00 00 mov $0x0,%eax
80486e2: 39 ca cmp %ecx,%edx
80486e4: 74 09 je 80486ef <foo1(std::vector<int, std::allocator<int> > const&)+0x1b>
80486e6: 03 02 add (%edx),%eax
80486e8: 83 c2 04 add $0x4,%edx
80486eb: 39 d1 cmp %edx,%ecx
80486ed: 75 f7 jne 80486e6 <foo1(std::vector<int, std::allocator<int> > const&)+0x12>
80486ef: f3 c3 repz ret
所以,是的,两种方法是相同的。
更新:同样的观察结果也适用于其他容器(或元素类型),例如vector<string>
和map<string, string>
。在这些情况下,在基于范围的循环中使用引用尤为重要。否则,将创建一个临时代码并显示大量额外的代码(在前面的示例中不需要它,因为vector
仅包含int
值)。
对于map<string, string>
的情况,使用的C++代码片段是:
int foo1(const std::map<std::string, std::string>& v) {
int res = 0;
for (const auto& x : v) {
res += (x.first.size() + x.second.size());
}
return res;
}
int foo2(const std::map<std::string, std::string>& v) {
int res = 0;
for (auto it = v.begin(), end = v.end(); it != end; ++it) {
res += (it->first.size() + it->second.size());
}
return res;
}
汇编代码(对于这两种情况)是:
8048d70: 56 push %esi
8048d71: 53 push %ebx
8048d72: 31 db xor %ebx,%ebx
8048d74: 83 ec 14 sub $0x14,%esp
8048d77: 8b 74 24 20 mov 0x20(%esp),%esi
8048d7b: 8b 46 0c mov 0xc(%esi),%eax
8048d7e: 83 c6 04 add $0x4,%esi
8048d81: 39 f0 cmp %esi,%eax
8048d83: 74 1b je 8048da0
8048d85: 8d 76 00 lea 0x0(%esi),%esi
8048d88: 8b 50 10 mov 0x10(%eax),%edx
8048d8b: 03 5a f4 add -0xc(%edx),%ebx
8048d8e: 8b 50 14 mov 0x14(%eax),%edx
8048d91: 03 5a f4 add -0xc(%edx),%ebx
8048d94: 89 04 24 mov %eax,(%esp)
8048d97: e8 f4 fb ff ff call 8048990 <std::_Rb_tree_increment(std::_Rb_tree_node_base const*)@plt>
8048d9c: 39 c6 cmp %eax,%esi
8048d9e: 75 e8 jne 8048d88
8048da0: 83 c4 14 add $0x14,%esp
8048da3: 89 d8 mov %ebx,%eax
8048da5: 5b pop %ebx
8048da6: 5e pop %esi
8048da7: c3 ret
Range-for 尽可能快,因为它缓存了结束迭代器[引用提供],使用预增量并且只取消引用迭代器一次。
所以如果你倾向于写:
for(iterator i = cont.begin(); i != cont.end(); i++) { /**/ }
然后,是的,range-for 可能会稍微快一些,因为它也更容易编写,没有理由不使用它(在适当的时候)。
注:注:我说它尽可能快,但它并没有比可能快。如果您仔细编写手动循环,则可以实现完全相同的性能。
No.它与带有迭代器的旧for
循环相同。毕竟,基于范围的for
在内部与迭代器配合使用。编译器只是为两者生成等效的代码。
在极少数情况下,它可能更快。由于您无法命名迭代器,因此优化器可以更轻松地证明您的循环无法修改迭代器。例如,这会影响循环展开优化。
- 如何在C++中从两个单独的for循环中添加两个数组
- 为什么我的for循环不能正确获取argv
- 在基于范围的for循环中使用结构化绑定声明
- 通过for循环使用用户输入填充列表
- 使用for循环检查数组中的重复项
- 在for循环中使用auto vs decltype(vec.size())来处理字符串的向量
- 为什么 const std::p air<K,V>& 在 std::map 上基于范围的 for 循环不起作用?
- 正在使用for循环创建QScatterSerie
- Python中的for循环与C++有何不同
- 在更改for循环的第三部分后,未使用for循环结果
- 在 for 循环中查找问题时遇到困难
- 嵌套for循环C++的问题(初学者)
- 如何用for循环在c++中生成单词三角形
- 如何在for循环中包含两个索引值的测试条件
- 带有多个独立参数的C++For循环
- C++ Python 循环"for i, num in enumerate(list):"版本
- C 多循环 for () 基础知识
- 没有条件值的 FOR 循环"for (int i = 1; ; i++)"无法正常工作
- 为用户提供循环for循环的选项
- 打破循环for循环