V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
movq
V2EX  ›  C++

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

  •  
  •   movq · 2022-04-22 23:29:43 +08:00 · 2416 次点击
    这是一个创建于 980 天前的主题,其中的信息可能已经有所发展或是发生改变。
    for (int i = 0; i < nums.size(); ++i) {
                 // xxx   
                }
    

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

    10 条回复    2022-04-24 06:14:17 +08:00
    hanxiV2EX
        1
    hanxiV2EX  
       2022-04-22 23:41:36 +08:00 via Android
    测试一下?自己这个 size 函数,里面加打印。
    huaouo
        2
    huaouo  
       2022-04-22 23:41:39 +08:00
    misdake
        3
    misdake  
       2022-04-22 23:46:31 +08:00
    开启优化,如果函数内部很容易推断出不可能修改长度的话,是会优化掉的
    Tanix2
        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 );
    ```
    misaka19000
        5
    misaka19000  
       2022-04-23 00:08:06 +08:00
    直接看编译得到的汇编代码啊
    ipwx
        6
    ipwx  
       2022-04-23 00:15:05 +08:00   ❤️ 2
    @misdake 根据我的理解,这种优化不是“判断出不会修改长度”,而是分两步:

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

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

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

    我不喜欢,大部分代码对性能感知都没那么明显。一优化反而增加阅读负担。
    111qqz
        9
    111qqz  
       2022-04-23 14:50:55 +08:00
    @3dwelcome #8 这么写可能是被 strlen 坑怕了[
    FrankHB
        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 优化……
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2805 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 07:00 · PVG 15:00 · LAX 23:00 · JFK 02:00
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.