C++ for 循环终止条件里面如果写一个数组的 size,会不会优化呢?

2022-04-22 23:29:43 +08:00
 movq
for (int i = 0; i < nums.size(); ++i) {
             // xxx   
            }

比如上面这种写法,xxx 里面没有更改 nums 的内容,不知道这个 nums.size()是会编译为常量,还是说真的每次判断的时候都要调用 size()函数

2367 次点击
所在节点    C++
10 条回复
hanxiV2EX
2022-04-22 23:41:36 +08:00
测试一下?自己这个 size 函数,里面加打印。
huaouo
2022-04-22 23:41:39 +08:00
misdake
2022-04-22 23:46:31 +08:00
开启优化,如果函数内部很容易推断出不可能修改长度的话,是会优化掉的
Tanix2
2022-04-23 00:00:11 +08:00
源代码

```c++
#include <iostream>
#include <vector>
using namespace std;

int main(){
vector<int> a{1,2,3};
for(int i = 0; i < a.size(); i++){
cout << a[i] << ' ';
}
}
```

g++版本

```shell
g++ --version
g++.exe (tdm64-1) 10.3.0
```

分别用不同优化等级编译,然后反编译,循环部分如下

-O0

```c
for( i = 0; ; ++i ){
v3 = i;
if ( v3 >= std::vector<int>::size(v8) )break;
v4 = (unsigned int *)std::vector<int>::operator[](v8, i);
v5 = std::ostream::operator<<(refptr__ZSt4cout, *v4);
std::operator<<<std::char_traits<char>>(v5, 32i64);
}
```

-O1

```c
do{
v5 = ((__int64 (__fastcall *)(void *, _QWORD))std::ostream::operator<<)(
refptr__ZSt4cout,
*(unsigned int *)(v3 + 4 * v4));
v7 = 32;
std::__ostream_insert<char,std::char_traits<char>>(v5, &v7, 1i64);
v3 = v8;
++v4;
}while ( (v9 - v8) >> 2 > v4 );
```

-O2

```c
do{
v5 = (std::ostream *)std::ostream::operator<<(refptr__ZSt4cout, *(unsigned int *)(v3 + 4 * v4));
std::__ostream_insert<char,std::char_traits<char>>(v5);
v3 = v7;
++v4;
}while ( (v8 - v7) >> 2 > v4 );
```

-O3

```c
do{
v5 = (std::ostream *)std::ostream::operator<<(refptr__ZSt4cout, *v4);
std::__ostream_insert<char,std::char_traits<char>>(v5);
++v4;
}while ( v4 != v3 + 3 );
```
misaka19000
2022-04-23 00:08:06 +08:00
直接看编译得到的汇编代码啊
ipwx
2022-04-23 00:15:05 +08:00
@misdake 根据我的理解,这种优化不是“判断出不会修改长度”,而是分两步:

1 、首先进行内联展开。这么一来 .size() 相当于访问了某一段内存。
2 、既然是访问内存就好办了,交给后续的优化步骤。能分配寄存器的,自然不用重新读内存。
ipwx
2022-04-23 00:17:54 +08:00
@movq 接上一楼:所以我觉得楼主你的问题问的有点不太妥当。首先,不用调用 .size() 和直接当做“常量”是两码事。况且在这个操作中,.size() 内部也不是常量,而是一个“局部没有被修改的变量”。

总之 C++ 模板函数第一步优化肯定是内联展开就是了。如果从这个角度来讲,其实楼主你的问题也不妥当。因为哪怕你修改了 vector ,也不会调用 .size() 函数,因为直接内联展开读写 vector 里面 size 访问的那个 size_t 内存了。
3dwelcome
2022-04-23 01:27:50 +08:00
不会被优化掉。

有些老外喜欢这样写:for (int i = 0,c=nums.size(); i < c; ++i) {

我不喜欢,大部分代码对性能感知都没那么明显。一优化反而增加阅读负担。
111qqz
2022-04-23 14:50:55 +08:00
@3dwelcome #8 这么写可能是被 strlen 坑怕了[
FrankHB
2022-04-24 06:14:17 +08:00
这算是很常见的优化,但说到底都不保证。

@ipwx 就这里的例子第一步是内联肯定是错的。
对 GCC (比如-O1 以上),至少要先做 IPA pure/const 分析,拆了函数分析多此一举。而这又得依赖事先知道是不是会内联,所以在此之前要求有 IPA fnsummary ,以确保局部状态一致。
IPA inline 的两个 pass ( einline 和 inline )在 fnsummary 和 pure/const 之间,而真正干活的 tree inline 还远着。
如果 pure 分析结果能确定内部只依赖参数或者有__attribute__((pure))之类,标记 DECL_PURE_P 。在这些函数的调用表达式上会标记 ECF_PURE ,进而影响 gimple_has_side_effects 的结果,在 tree SSA 循环不变量移动 pass 中提出来。
只要能分析出 xxx 没改 nums ,这里就很明确不需要进一步内联。

@111qqz __builtin_strlen 和靠谱的库函数 strlen 都应该标记为 pure 。
说起来 GCC 甚至还有单独的 tree SSA strlen 优化……

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/848686

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX