1. 条件传送指令

日常编程中有很多根据某个条件对变量赋不同值这样的模式,比如:

int cmov(int num) {
int result = 10;
if(num<10){
result = 1;
}else{
result = 0;
}
return result;
}

如果不进行编译优化会产出cmp-jump组合,即根据cmp比较的结果进行跳转。可以使用gcc -O0查看:

cmov(int):
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-20], edi
int result = 10;
mov DWORD PTR [rbp-4], 10
cmp DWORD PTR [rbp-20], 9
jg .L2
mov DWORD PTR [rbp-4], 1
jmp .L3
.L2:
mov DWORD PTR [rbp-4], 0
.L3:
mov eax, DWORD PTR [rbp-4]
pop rbp
ret

如果num>9就为result赋1,否则赋0。正因为跳转是条件的,CPU必须要等到条件成立才执行后面的指令(即数据依赖于条件),这会让处理器变慢,所以CPU通常会使用分支预测算法,提前执行某个概率更高的分支,即使这个时候条件没有成立。但问题是,这是预测不是断言,如果它预测条件成立进入A分支给R赋值V1,实际却可能是条件失败,应该继续B分支给R赋V2,那么CPU还需要撤销给R赋值V1。而条件传送系列指令(cmovcc,setcc)则不同,它根据EFLAGS的位条件性的选择给R赋V1还是V2,这样就没有分支预测失败的性能惩罚。回到上面的例子,使用gcc8的-O3可以看到产出的条件传送指令:

cmov(int):
xor eax, eax
cmp edi, 9
setle al
ret

产出变短了,code cache能容纳下更多代码不说,setle也会根据ZF结果,即小于等于9则给al赋1,否则赋0。我们还顺便证明了C++有条件表达式消除,那么HotSpot有吗?还真有,但是别高兴的太早,它可能和你想的大相径庭...

2. C1编译器中的条件表达式消除

[Inside HotSpot] C1编译器工作流程及中间表示中我们提到OpenJDK12的C1编译器将字节码转化为机器码的过程分为多个阶段:

第一个阶段它生成HIR,一种C1内部用到的表示字节码的图结构。该阶段同时也会对它进行优化,其中就包括本文要讨论的条件表达式消除(Conditional expression elimination),这一部分代码位于c1_Optimizer.cpp。但是在研究之前还需要做一些准备工作,我们无法直接查看虚拟机JIT产出的本地代码,需要下载hsdis-amd64.dll,将它放在jdk/bin/server/目录下,然后虚拟机加上参数-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly。另外,为了确保虚拟机用C1编译方法且做了条件表达式消除,还要加上-Xcomp -XX:TieredStopAtLevel=1 -XX:+DoCEE

3. CEE示例

准备好后我们可以开始尝试,准备一段和上面C++差不多的代码:

package com.github.kelthuzadx;

public class C1Optimizations {
public static int conditionalExpressionElimination(int depend){
if(depend<10){
return 1;
}else{
return 0;
}
} public static void main(String[] args) {
conditionalExpressionElimination(1024);
}
}

然后加上参数查看汇编形式的产出(实际上还是本地代码的,只是汇编形式输出而已):

; Simplified
com/github/kelthuzadx/C1Optimizations.conditionalExpressionElimination(I)I
0x000001afb0a691c0: mov %eax,-0x9000(%rsp)
0x000001afb0a691c7: push %rbp
0x000001afb0a691c8: sub $0x30,%rsp ;*iload_0 0x000001afb0a691cc: cmp $0xa,%edx 0x000001afb0a691cf: jge L1 ;*if_icmpge
0x000001afb0a691d5: mov $0x1,%eax
0x000001afb0a691da: add $0x30,%rsp
0x000001afb0a691de: pop %rbp
0x000001afb0a691df: mov 0x120(%r15),%r10 ; 安全点
0x000001afb0a691e6: test %eax,(%r10) ; 轮询
0x000001afb0a691e9: retq ;*ireturn L1:
0x000001afb0a691ea: mov $0x0,%eax
0x000001afb0a691ef: add $0x30,%rsp
0x000001afb0a691f3: pop %rbp
0x000001afb0a691f4: mov 0x120(%r15),%r10 ; 安全点
0x000001afb0a691fb: test %eax,(%r10) ; 轮询
0x000001afb0a691fe: retq
0x000001afb0a691ff: nop
0x000001afb0a69200: nop

并没有优化?HotSpot不认为上面的Java代码是条件表达式。它认为的条件表达式是三元表达式,有些出乎意料,但是也请继续吧:

package com.github.kelthuzadx;

public class C1Optimizations {
public static int conditionalExpressionElimination(int depend){
return depend>10?1:0;
} public static void main(String[] args) {
conditionalExpressionElimination(1024);
}
}

要确定是HotSpot认为这是条件表达式而不是我们认为这是条件表达式,得加上-XX:+PrintIR -XX:+PrintLIRWithAssembly辅证(也可使用 -XX:+PrintCEE),产它出C1 HIR及其汇编表示。首先看看原始HIR:

IR after parsing
B4 [0, 0] -> B0 sux: B0
empty stack
inlining depth 0
__bci__use__tid____instr____________________________________
. 0 0 14 std entry B0 B0 (SV) [0, 3] -> B2 B1 sux: B2 B1 pred: B4
empty stack
inlining depth 0
__bci__use__tid____instr____________________________________
1 0 i5 10
. 3 0 6 if i4 <= i5 then B2 else B1 B1 (V) [6, 7] -> B3 sux: B3 pred: B0
empty stack
inlining depth 0
__bci__use__tid____instr____________________________________
6 0 i7 1
. 7 0 8 goto B3
stack [0:i7] B3 (V) [11, 11] pred: B1 B2Stack:
0 i11 [ i7 i9] stack [0:i11]
inlining depth 0
__bci__use__tid____instr____________________________________
. 11 0 i12 ireturn i11 B2 (V) [10, 11] -> B3 sux: B3 pred: B0
empty stack
inlining depth 0
__bci__use__tid____instr____________________________________
10 0 i9 0
. 11 0 10 goto B3
stack [0:i9]

根据右边助记指令还是很好理解: 如果i4<i5depend<10则返回1,反之返回0。接着查看条件表达式消除之后的HIR:

IR after CEE
B4 [0, 0] -> B0 sux: B0
empty stack
inlining depth 0
__bci__use__tid____instr____________________________________
. 0 0 14 std entry B0 B0 (SV) [0, 3] -> B3 sux: B3 pred: B4
empty stack
inlining depth 0
__bci__use__tid____instr____________________________________
1 0 i5 10
3 0 i15 0
3 0 i16 1
3 0 i17 i4 <= i5 ? i15 : i16
. 3 0 18 goto B3
stack [0:i17] B3 (V) [11, 11] pred: B0
stack [0:i17]
inlining depth 0
__bci__use__tid____instr____________________________________
. 11 0 i12 ireturn i17

i17的值视条件而定,如果条件i4<i5则i17为0,否则为1,最后返回。这里的模式正是条件传送指令的工作机制,HIR也有明显的改变,经过一番折腾,我们成功的让HotSpot对条件表达式做了一次消除优化。不过还没有结束,再看看HIR经过一系列步骤最终生成的本地代码:

[Disassembling for mach='i386:x86-64']
2 std_entry
0x0000026a64f209a0: mov %eax,-0x9000(%rsp)
0x0000026a64f209a7: push %rbp
0x0000026a64f209a8: sub $0x30,%rsp 0x0000026a64f209ac: cmp $0xa,%edx 0x0000026a64f209af: mov $0x0,%eax
0x0000026a64f209b4: jle 0x0000026a64f209bf
0x0000026a64f209ba: mov $0x1,%eax 0x0000026a64f209bf: add $0x30,%rsp
0x0000026a64f209c3: pop %rbp
0x0000026a64f209c4: mov 0x120(%r15),%r10
0x0000026a64f209cb: test %eax,(%r10)
0x0000026a64f209ce: retq

很遗憾,即便高层次上HIR做了条件表达式消除,后面到低层次LIR再到本地代码生成也没有产出cmov系列指令。

[Inside HotSpot] C1编译器优化:条件表达式消除的更多相关文章

  1. [Inside HotSpot] C1编译器优化:全局值编号(GVN)

    1. 值编号 我们知道C1内部使用的是一种图结构的HIR,它由基本块构成一个图,然后每个基本块里面是SSA形式的指令,关于这点如可以参考[Inside HotSpot] C1编译器工作流程及中间表示. ...

  2. [Inside HotSpot] C1编译器HIR的构造

    1. 简介 这篇文章可以说是Christian Wimmer硕士论文Linear Scan Register Allocation for the Java HotSpot™ Client Compi ...

  3. [Inside HotSpot] C1编译器工作流程及中间表示

    1. C1编译器线程 C1编译器(aka Client Compiler)的代码位于hotspot\share\c1.C1编译线程(C1 CompilerThread)会阻塞在任务队列,当发现队列有编 ...

  4. JavaScrip条件表达式优化

    目录 1,前言 2,多条件if语句优化 3,参数默认值 4,Switch语句优化 1,前言 今早看了一篇文章<JavaScrip实现:如何写出漂亮的条件表达式>,原创于:华为云开发者社区, ...

  5. java编译器优化和运行期优化

    概述    最近在看jvm优化,总结一下学习的相关知识 (一)javac编译器 编译过程 1.解析与填充符号表过程 1).词法.语法分析    词法分析将源代码的字符流转变为标记集合,单个字符是程序编 ...

  6. [Inside HotSpot] Java的方法调用

    1. 方法调用模块入口 Java所有的方法调用都会经过JavaCalls模块.该模块又细分为call_virtual调用虚函数,call_static调用静态函数等.虚函数调用会根据对象类型进行方法决 ...

  7. C1编译器的实现

    总览 词法.语法分析 分析方案 词法 语法 符号表 类型系统 AST 语义检查 EIR代码生成器 MIPS代码生成器 寄存器分配 体系结构相关特性优化 使用说明 编译 运行 总览 C1语言编译器及流程 ...

  8. C#编译器优化那点事

    使用C#编写程序,给最终用户的程序,是需要使用release配置的,而release配置和debug配置,有一个关键区别,就是release的编译器优化默认是启用的. 优化代码开关即optimize开 ...

  9. 【转】C 编译器优化过程中的 Bug

    C 编译器优化过程中的 Bug 一个朋友向我指出一个最近他们发现的 GCC 编译器优化过程(加上 -O3 选项)里的 bug,导致他们的产品出现非常诡异的行为.这使我想起以前见过的一个 GCC bug ...

随机推荐

  1. 任务调度--spring下的任务调度quartz

    之前写过Timer实现任务调度,这篇文章用来写一下在spring下使用quartz实现任务调度,直接上代码: 定义任务对象: package com; /** * 1. 定义任务对象 * * @aut ...

  2. 一个靠谱的phpredisadmin文件

    http://download.csdn.net/detail/newjueqi/7227487

  3. HTTP状态码的详细解释,供参考

    HTTP状态码详解 常用对照表 状态码 含义 100 客户端应当继续发送请求.这个临时响应是用来通知客户端它的部分请求已经被服务器接收,且仍未被拒绝.客户端应当继续发送请求的剩余部分,或者如果请求已经 ...

  4. 开机出现loading (hd0)/ntldr。。。

    电脑一开机就出现ntldr is missing的原因:1.操作系统文件损坏.2.MBR表损坏.3.硬盘数据线松了.4.硬盘坏了.解决方法:1.重新安装操作系统.2.用U盘或光盘引导,进入PE系统,用 ...

  5. 【BZOJ 2004】: [Hnoi2010]Bus 公交线路

    题目链接: TP 题解:   所以说,超显眼的数据范围啊. 很显然我们对于每个P的区间都是要有k个站被bus停留,然后考虑转移的话应该是把这k个站里的某个bus往前走,那么转移也很显然了,n的范围很大 ...

  6. BZOJ_3048_[Usaco2013 Jan]Cow Lineup _双指针

    BZOJ_3048_[Usaco2013 Jan]Cow Lineup _双指针 Description Farmer John's N cows (1 <= N <= 100,000) ...

  7. SA SD SE 区别

    [SA(System Analysis)系统分析师] 通过一系列分析手法把User想要的结果,以各种文件方式表达出来. 此过程着重于工作流程和处理逻辑. 规划系统功能和模块. 定出初步的数据库内容及系 ...

  8. java.lang.ClassNotFoundException: com.mysql.jdbc.Drive

    Linux下使用eclipse开发web项目,运行的时候出现 Java.lang.ClassNotFoundException: com.MySQL.jdbc.Driver,解决办法如下: 1.导入M ...

  9. Python核心编程

    对<Python核心编程>的褒奖" The long-awaited second edition of Wesley Chun's Core PythonProgramming ...

  10. 离线安装mysql数据库

    开源数据库mysql,目前使用很广泛.作为程序员开发项目时,与关系型数据库打交道最多的估计也是mysql了.那么本文首先讲解如何离线安装mysql数据库,毕竟有很多项目部署在内网. 1.离线安装 本人 ...