JIT编译优化之方法内联

如何实现方法调用

要如何实现方法调用呢?最直接的方法就是可以把调用的函数指令,直接插入在调用函数的地方,然后在编译器编译代码的时候,直接就把函数调用变成对应的指令替换掉,但是如果函数 A 调用了函数 B,然后函数 B 再调用函数 A,我们就得面临在 A 里面插入 B 的指令,然后在 B 里面插入 A 的指令,这样就会产生无穷无尽地替换。这样就像将两面镜子面对面放着,可以看到无穷无尽的镜子一样。如何解决这个问题呢?内存里面开辟一段空间,用栈这个后进先出(LIFO,Last In First Out)的数据结构。栈就像一个乒乓球桶,每次程序调用函数之前,我们都把调用返回后的地址写在一个乒乓球上,然后塞进这个球桶。这个操作其实就是我们常说的压栈。如果函数执行完了,我们就从球桶里取出最上面的那个乒乓球,很显然,这就是出栈。

int static add(int a, int b){ 
   return a+b;
}
int main(){ 
   int x = 5; 
   int y = 10; 
   int u = add(x, y);
}

编译后

int static add(int a, int b){ 
   0: 55 push rbp 
   1: 48 89 e5 mov rbp,rsp 
   4: 89 7d fc mov DWORD PTR [rbp-0x4],edi 
   7: 89 75 f8 mov DWORD PTR [rbp-0x8],esi 
   return a+b; 
   a: 8b 55 fc mov edx,DWORD PTR [rbp-0x4] 
   d: 8b 45 f8 mov eax,DWORD PTR [rbp-0x8] 
   10: 01 d0 add eax,edx} 
   12: 5d pop rbp 
   13: c3 ret 0000000000000014 <main>:
int main(){ 
   14: 55 push rbp 
   15: 48 89 e5 mov rbp,rsp 
   18: 48 83 ec 10 sub rsp,0x10 
   int x = 5; 
   1c: c7 45 fc 05 00 00 00 mov DWORD PTR [rbp-0x4],0x5 
   int y = 10; 
   23: c7 45 f8 0a 00 00 00 mov DWORD PTR [rbp-0x8],0xa 
   int u = add(x, y); 
   2a: 8b 55 f8 mov edx,DWORD PTR [rbp-0x8] 
   2d: 8b 45 fc mov eax,DWORD PTR [rbp-0x4] 
   30: 89 d6 mov esi,edx 
   32: 89 c7 mov edi,eax 
   34: e8 c7 ff ff ff 
   call 0 
   39: 89 45 f4 mov DWORD PTR [rbp-0xc],eax 
   3c: b8 00 00 00 00 mov eax,0x0
} 
   41: c9 leave 
   42: c3 ret

首先明白两个概念:

  • EBP/RBP:基址指针寄存器(extended base pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的底部。(64位机器变为RBP)
  • ESP/RSP:栈指针寄存器(extended stack pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶。(64位机器变为RSP)

call指令相当于 push rip 和 jmp 的结合,rip 是指令地址寄存器,也就是将当前执行函数PC寄存器中的指令地址入栈,并跳转到相应函数处执行,而 ret 指令相当于 pop rip 和 jmp指令的结合,
rbp 和 rsp 用于维护当前帧栈,rbp 指向栈帧的栈底地址,rsp 指向栈顶地址,push pop 和 mov rbp,rsp 主要是为了从卖你函数的栈帧调整为add函数的栈帧。
具体调用过程如下所示


方法调用指令执行过程

方法内联

上文中提到方法调用最简单的方法就是把被调用函数的指令,直接插入在调用函数的地方,虽然这个方法在一些调用关系比较复杂的场景中存在问题,但是对于调用关系比较简单,如上文中add方法,在add方法没有调用其他方法,那么直接将add方法的指令插入main方法中是可行的,这就是很多编译器进行优化的重要场景之一。JVM中大名鼎鼎的JIT便提供这种优化能力。
上文中的例子如果在编译过程中加上优化编译的参数

$ gcc -g -c -O function_example_inline.c
$ objdump -d -M intel -S function_example_inline.o

那么在main函数调用add方法时不会被编译为call指令,而是直接调用add指令,即将add方法的指令插入了main方法中。
方法内联会给我们带来哪些收益呢?

  • CPU需要执行的指令变少了,显而易见,方法内联的方式不需要调用call和ret等指令;
  • 不需要根据地质进行跳转了,所有指令顺序执行;
  • 函数的入栈和处栈不需要了。

总的来说就是方法执行的效率提高了,但是方法内联也是有利有弊的,那么方法内联会带来哪些问题呢?

  • 如果一个内联方法被调用的地方比较多,那么它的指令需要在很多地方被插入,那么整个应用程序占用的内存空间就变大了;
  • 如果一个内敛方法存在继承关系,那么可能需要引入类型判断的额外开销达不到性能优化的目的。

权衡方法内联的利弊,可以总结一下最佳实践

  • 方法体应该尽可能小,我们熟悉的阿里巴巴开发规约中也对方法体的进行了约束,除了可读性、可维护性方面的考虑,也涉及到底层的编译优化;
  • 尽量使用final、private、static来修饰方法,避免继承带来的额外开销;
  • 按需进行JVM参数优化,常见优化参数如下所示
-XX:+PrintCompilation //在控制台打印编译过程信息
-XX:+UnlockDiagnosticVMOptions //解锁对JVM进行诊断的选项参数。默认是关闭的,开启后支持一些特定参数对JVM进行诊断
-XX:+PrintInlining //将内联方法打印出来
-XX:CompileThreshold //参数设置识别为热点方法的阈值
-XX:MaxFreqInlineSize=N //如果方法是经常执行的,默认情况下,方法大小小于325字节的都会进行内联
-XX:MaxInlineSize=N //如果方法不是经常执行的,默认情况下,方法大小小于35字节才会进行内联

参考资料

java程序猿-你的代码居然慢在JIT方法内联上
极客时间-《深入浅出计算机组成原理之函数调用:为什么会发生stack overflow?》

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,923评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,154评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,775评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,960评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,976评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,972评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,893评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,709评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,159评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,400评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,552评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,265评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,876评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,528评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,701评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,552评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,451评论 2 352