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. 17.app后端如何保证通讯安全--aes对称加密

    在上文<16.app后端如何保证通讯安全--url签名>提到,url签名有两个缺点,这两个缺点,如果使用对称加密方法的话,则完全可以避免这两个缺点.在本文中,会介绍对称加密的具体原理,和详 ...

  2. Offcie2013 无法显示 Aurora解决方法

    问题:安装Office2013和Aurora后,Word菜单栏中无Aurora 原因:aurora安装目录中缺失office.dll,word2013未加载COM文件office.dll和模板文件wo ...

  3. 用Hyperledger Fabric(超级账本)来构建Java语言开发区块链的环境

    面向 Java 开发人员的链代码简介 您或许听说过区块链,但可能不确定它对 Java™ 开发人员有何用.本教程将帮助大家解惑.我将分步展示如何使用 Hyperledger Fabric v0.6 来构 ...

  4. pymongo连接MongoDB

    导语 pymongo 是目前用的相对普遍一个python用来连接MongoDB的库,是工作中各种基本需求都能满足具体api可以参考 pymongo APIpymongo github 安装 Mongo ...

  5. Java的参数传递是「值传递」还是「引用传递」?

    关于Java传参时是引用传递还是值传递,一直是一个讨论比较多的话题. 有人说Java中只有值传递,也有人说值传递和引用传递都是存在的,比较容易让人产生疑问. 关于值传递和引用传递其实需要分情况看待. ...

  6. 用Excel导入Oracle数据库plsql

    打开plsql之后,在工具栏点击[tools]--[ODBC Imoprter] 选择导入文件的类型,这里是excel文件,所以选择Excel Files 输入连接数据库的用户名和密码 点击Conne ...

  7. backend_queue.go

    package nsqd // BackendQueue represents the behavior for the secondary message // storage system typ ...

  8. CAD中用户选择实体

    在CAD的很多操作中都会有需要用户选择实体的时候,这里将我最近项目中用到的方法分享一下,程序原意是希望用户选择一个单行文本或者多行文本,并返回所选文本的内容,直接上代码: CString CPaint ...

  9. BZOJ_3343_教主的魔法_分块+二分查找

    BZOJ_3343_教主的魔法_分块+二分查找 题意:教主最近学会了一种神奇的魔法,能够使人长高.于是他准备演示给XMYZ信息组每个英雄看.于是N个英雄们又一次聚集在了一起,这次他们排成了一列被编号为 ...

  10. BZOJ_2724_[Violet 6]蒲公英_分块

    BZOJ_2724_[Violet 6]蒲公英_分块 Description Input 修正一下 l = (l_0 + x - 1) mod n + 1, r = (r_0 + x - 1) mod ...