for (int i = 0; i < nums.size(); ++i) {
// xxx
}
比如上面这种写法,xxx 里面没有更改 nums 的内容,不知道这个 nums.size()是会编译为常量,还是说真的每次判断的时候都要调用 size()函数
1
hanxiV2EX 2022-04-22 23:41:36 +08:00 via Android
测试一下?自己这个 size 函数,里面加打印。
|
2
huaouo 2022-04-22 23:41:39 +08:00
|
3
misdake 2022-04-22 23:46:31 +08:00
开启优化,如果函数内部很容易推断出不可能修改长度的话,是会优化掉的
|
4
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 ); ``` |
5
misaka19000 2022-04-23 00:08:06 +08:00
直接看编译得到的汇编代码啊
|
6
ipwx 2022-04-23 00:15:05 +08:00 2
@misdake 根据我的理解,这种优化不是“判断出不会修改长度”,而是分两步:
1 、首先进行内联展开。这么一来 .size() 相当于访问了某一段内存。 2 、既然是访问内存就好办了,交给后续的优化步骤。能分配寄存器的,自然不用重新读内存。 |
7
ipwx 2022-04-23 00:17:54 +08:00
@movq 接上一楼:所以我觉得楼主你的问题问的有点不太妥当。首先,不用调用 .size() 和直接当做“常量”是两码事。况且在这个操作中,.size() 内部也不是常量,而是一个“局部没有被修改的变量”。
总之 C++ 模板函数第一步优化肯定是内联展开就是了。如果从这个角度来讲,其实楼主你的问题也不妥当。因为哪怕你修改了 vector ,也不会调用 .size() 函数,因为直接内联展开读写 vector 里面 size 访问的那个 size_t 内存了。 |
8
3dwelcome 2022-04-23 01:27:50 +08:00
不会被优化掉。
有些老外喜欢这样写:for (int i = 0,c=nums.size(); i < c; ++i) { 我不喜欢,大部分代码对性能感知都没那么明显。一优化反而增加阅读负担。 |
10
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 优化…… |