V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
• 请不要在回答技术问题时复制粘贴 AI 生成的内容
zvl0reqglvd
V2EX  ›  程序员

请教一个 C++问题,为什么加了 inline 反而效率降低了

  •  
  •   zvl0reqglvd · 2020-12-19 18:37:10 +08:00 · 3790 次点击
    这是一个创建于 1462 天前的主题,其中的信息可能已经有所发展或是发生改变。

    老哥们,请教一下下面的程序是因为什么原因造成的?

    #include <iostream>
    #include <chrono>
    using namespace std;
    
    class Runtime{
    private:
        std::chrono::high_resolution_clock::time_point _start, _end;
    public:
        Runtime(){
    	start();
        }
        
        void start(){
    	_start = std::chrono::high_resolution_clock::now();
        }
        void end() {
    	_end = std::chrono::high_resolution_clock::now();
        }
        long long spend() {
    	return std::chrono::duration_cast<std::chrono::nanoseconds>(_end - _start).count();
        }
        void printTime() {
    	end();
    	std::cout << "time = "
    		  << spend()/1000000 << "/ms = "//毫秒
    		  << spend()/1000 << "/us = "//微秒
    		  << spend() << "/ns" << std::endl;//纳秒
        }
    };
    
    const int N = 1e5;
    int x[N];
    int y[N];
    int z[N];
    
    //inline
    int calc(int a, int b) {
        return a + b;
    }
    
    int main(){
        Runtime r;
        for (int i=0; i<N; ++i) {
    	for (int j=0; j<N; ++j) {
    	    for (int k=0; k<N; ++k)
    		z[i] = calc(y[j], x[k]);
            //z[i] = y[j] + x[k];
    	}
        }
        r.printTime();
    }
    

    加了 inline 比不加慢一倍,不是说 inline 会减少函数压栈出栈的时间开销,相当于直接在调用点插入代码嘛,但是,实际上开销比不加 inline 慢一倍,不加 inline 的开销和下面注释的那句的开销差不多。感觉很奇怪,难道是编译器自动添加了 inline ?

    请问是什么原因造成的啊?

    17 条回复    2020-12-20 20:03:51 +08:00
    BrettD
        1
    BrettD  
       2020-12-19 18:41:37 +08:00 via iPhone
    看一下生成的汇编代码是啥
    Huelse
        2
    Huelse  
       2020-12-19 18:50:42 +08:00
    据我所知,一些编译器是会在编译 release 或者有其他编译选项的时候会自动给`return a+b`这种自动加 inline
    crclz
        3
    crclz  
       2020-12-19 18:54:49 +08:00
    1. 看看汇编,用 vscode 比较一下(右键)
    2. 开-O1 或者-O2,再测一下速度,并且再比较一下汇编代码
    YouLMAO
        4
    YouLMAO  
       2020-12-19 18:55:17 +08:00
    for (int i=0; i<N; ++i) {
    z[i] = y[N] + x[N];
    }

    这就是 gcc 优化的结果
    GeruzoniAnsasu
        5
    GeruzoniAnsasu  
       2020-12-19 19:04:44 +08:00
    首先开不开优化影响很大,如果被 inline 的代码本身 side effect 比较多,未优化情况下可能会额外多做很多事情,比如频繁读写变量的内存,本来这都是完全不需要的

    另外现在 x64 的 fastcall abi,函数调用已经没有访问内存出入栈的开销了,都可以用寄存器解决,所以 inline 省略掉了函数出入栈这个观念也已经是过时甚至错误的了

    再然后,众所周知 c++编译器行为学都只能马后炮,自己看一眼编译出来的汇编就都明白了
    zvl0reqglvd
        6
    zvl0reqglvd  
    OP
       2020-12-19 19:08:15 +08:00
    要的,谢谢老哥们,鞠躬!!
    lifanxi
        7
    lifanxi  
       2020-12-19 19:10:35 +08:00
    看看汇编找原因。

    具体是什么环境出现了你说描述的现象?我在 gcc 8.3 上重现不出来你说的现象。
    nightwitch
        8
    nightwitch  
       2020-12-19 19:16:36 +08:00   ❤️ 3
    inline 这个关键词建议直接忘记它有内联建议这个功能,因为现在编译器基本上无视 inline 的这个语义。
    如果你真的确定要内联一个函数,MSVC 要用__forceinline , gcc 要用__attribute__((always_inline))。

    inline 这个关键词现在的主要作用是允许一个符号在不同的编译单元有重复的定义,这样允许你在头文件里写出函数实现和 inline variable.
    mxalbert1996
        9
    mxalbert1996  
       2020-12-19 19:18:03 +08:00 via Android
    不知道你这个是不是这种情况,不过 inline 可能会造成更多 cache miss 从而影响性能。
    https://en.wikipedia.org/wiki/Inline_expansion
    Inlining also imposes a cost on performance, due to the code expansion (due to duplication) hurting instruction cache performance.[6] This is most significant if, prior to expansion, the working set of the program (or a hot section of code) fit in one level of the memory hierarchy (e.g., L1 cache), but after expansion it no longer fits, resulting in frequent cache misses at that level. Due to the significant difference in performance at different levels of the hierarchy, this hurts performance considerably.
    msg7086
        10
    msg7086  
       2020-12-19 19:44:25 +08:00
    gcc 带上优化编译出来是这样的。

    main:
         vpbroadcastd    ymm1, DWORD PTR x[rip+399996]
         mov    eax, OFFSET FLAT:z
         mov    ecx, OFFSET FLAT:y+400000
    .L3:
         mov    edx, OFFSET FLAT:y
    .L2:
         vpbroadcastd    ymm0, DWORD PTR [rdx]
         add    rdx, 4
         cmp    rcx, rdx
         jne    .L2
         vpaddd   ymm0, ymm1, ymm0
         add    rax, 32
         vmovdqa YMMWORD PTR [rax-32], ymm0
         cmp    rax, OFFSET FLAT:z+400000
         jne    .L3
         xor    eax, eax
         vzeroupper
         ret
    z:
        .zero   400000
    y:
        .zero   400000
    x:
        .zero   400000

    打开 AVX2 以后会直接执行 VPADDD,不仅不会调用 calc,甚至不会调用普通的寄存器加法。
    codehz
        11
    codehz  
       2020-12-20 00:22:04 +08:00 via Android
    (inline 有你认为的效果要么是编译器版本太旧,要么就是你用了 always_inline 等私有扩展。。。
    按现行 c++标准,inline 关键字不再作为内联使用了)
    至于你这里的问题,你可能没开优化测试的吧
    786375312123
        12
    786375312123  
       2020-12-20 00:30:45 +08:00
    看看你的 vs 优化开没开
    no1xsyzy
        13
    no1xsyzy  
       2020-12-20 03:04:12 +08:00
    @mxalbert1996 按楼主代码,没有反复写 calc(),发生内联相比不发生内联,也不过是把唯一一处 calc(a, b) 替换为 a+b,并且还去掉了 calc 本身的定义,可以说指令的内存占用反而是减少的。
    codyfeng
        14
    codyfeng  
       2020-12-20 09:09:23 +08:00 via Android
    这类问题应该把编译器版本、参数都贴出来
    mxT52CRuqR6o5
        15
    mxT52CRuqR6o5  
       2020-12-20 14:39:25 +08:00
    https://godbolt.org/
    不开编译优化我这边看这个在线的编译器结果
    gcc 是完全一样,clang 是编译出来的顺序不一样,都没有 inline
    想要弄清区别肯定是要看汇编的,不知道你用的什么编译器开的什么编译选项
    zvl0reqglvd
        16
    zvl0reqglvd  
    OP
       2020-12-20 16:11:08 +08:00
    @mxT52CRuqR6o5 老哥你好,我用的是 gcc9.2,然后运行的是 g++ -g test.cpp -o test -lm -Wall -std=c++17 -O2 && test.exe ,开了优化的。
    Gwkang
        17
    Gwkang  
       2020-12-20 20:03:51 +08:00 via Android
    频繁调用的函数,不内联可以提高 CPU 缓存命中率,你内联之后就没有了函数调用栈,所以慢
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2049 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 00:02 · PVG 08:02 · LAX 16:02 · JFK 19:02
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.