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

2020-12-19 18:37:10 +08:00
 zvl0reqglvd

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

#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 ?

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

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

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

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

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

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

inline 这个关键词现在的主要作用是允许一个符号在不同的编译单元有重复的定义,这样允许你在头文件里写出函数实现和 inline variable.
mxalbert1996
2020-12-19 19:18:03 +08:00
不知道你这个是不是这种情况,不过 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
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
2020-12-20 00:22:04 +08:00
(inline 有你认为的效果要么是编译器版本太旧,要么就是你用了 always_inline 等私有扩展。。。
按现行 c++标准,inline 关键字不再作为内联使用了)
至于你这里的问题,你可能没开优化测试的吧
786375312123
2020-12-20 00:30:45 +08:00
看看你的 vs 优化开没开
no1xsyzy
2020-12-20 03:04:12 +08:00
@mxalbert1996 按楼主代码,没有反复写 calc(),发生内联相比不发生内联,也不过是把唯一一处 calc(a, b) 替换为 a+b,并且还去掉了 calc 本身的定义,可以说指令的内存占用反而是减少的。
codyfeng
2020-12-20 09:09:23 +08:00
这类问题应该把编译器版本、参数都贴出来
mxT52CRuqR6o5
2020-12-20 14:39:25 +08:00
https://godbolt.org/
不开编译优化我这边看这个在线的编译器结果
gcc 是完全一样,clang 是编译出来的顺序不一样,都没有 inline
想要弄清区别肯定是要看汇编的,不知道你用的什么编译器开的什么编译选项
zvl0reqglvd
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
2020-12-20 20:03:51 +08:00
频繁调用的函数,不内联可以提高 CPU 缓存命中率,你内联之后就没有了函数调用栈,所以慢

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

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

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

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

© 2021 V2EX