java 循环展开_深入理解编译优化之循环展开和粗化锁

简介

之前在讲JIT的时候,有提到在编译过程中的两种优化循环展开和粗化锁,今天我们和小师妹一起从Assembly的角度来验证一下这两种编译优化方法,快来看看吧。

循环展开和粗化锁

小师妹:F师兄,上次你讲到在JIT编译的过程中会进行一些编译上面的优化,其中就有循环展开和粗化锁。我对这两种优化方式很感兴趣,能不能展开讲解一下呢?

当然可以,我们先来回顾一下什么是循环展开。

更多精彩内容且看:

区块链从入门到放弃系列教程-涵盖密码学,超级账本,以太坊,Libra,比特币等持续更新

Spring Boot 2.X系列教程:七天从无到有掌握Spring Boot-持续更新

Spring 5.X系列教程:满足你对Spring5的一切想象-持续更新

java程序员从小工到专家成神之路(2020版)-持续更新中,附详细文章教程

循环展开就是说,像下面的循环遍历的例子:

for (int i = 0; i < 1000; i++) {

x += 0x51;

}

复制代码

因为每次循环都需要做跳转操作,所以为了提升效率,上面的代码其实可以被优化为下面的:

for (int i = 0; i < 250; i++) {

x += 0x144; //0x51 * 4

}

复制代码

注意上面我们使用的是16进制数字,至于为什么要使用16进制呢?这是为了方便我们在后面的assembly代码中快速找到他们。

好了,我们再在 x += 0x51 的外面加一层synchronized锁,看一下synchronized锁会不会随着loop unrolling展开的同时被粗化。

for (int i = 0; i < 1000; i++) {

synchronized (this) {

x += 0x51;

}

}

复制代码

万事具备,只欠我们的运行代码了,这里我们还是使用JMH来执行。

相关代码如下:

@Warmup(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS)

@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)

@Fork(value = 1,

jvmArgsPrepend = {

"-XX:-UseBiasedLocking",

"-XX:CompileCommand=print,com.flydean.LockOptimization::test"

}

)

@State(Scope.Benchmark)

@BenchmarkMode(Mode.AverageTime)

@OutputTimeUnit(TimeUnit.NANOSECONDS)

public class LockOptimization {

int x;

@Benchmark

@CompilerControl(CompilerControl.Mode.DONT_INLINE)

public void test() {

for (int i = 0; i < 1000; i++) {

synchronized (this) {

x += 0x51;

}

}

}

public static void main(String[] args) throws RunnerException {

Options opt = new OptionsBuilder()

.include(LockOptimization.class.getSimpleName())

.build();

new Runner(opt).run();

}

}

复制代码

上面的代码中,我们取消了偏向锁的使用:-XX:-UseBiasedLocking。为啥要取消这个选项呢?因为如果在偏向锁的情况下,如果线程获得锁之后,在之后的执行过程中,如果没有其他的线程访问该锁,那么持有偏向锁的线程则不需要触发同步。

为了更好的理解synchronized的流程,这里我们将偏向锁禁用。

其他的都是我们之前讲过的JMH的常规操作。

接下来就是见证奇迹的时刻了。

分析Assembly日志

我们运行上面的程序,将会得到一系列的输出。因为本文并不是讲解Assembly语言的,所以本文只是大概的理解一下Assembly的使用,并不会详细的进行Assembly语言的介绍,如果有想深入了解Assembly的朋友,可以在文后留言。

分析Assembly的输出结果,我们可以看到结果分为C1-compiled nmethod和C2-compiled nmethod两部分。

先看C1-compiled nmethod:

e3e2b04933a5407d35f49c68f8eb42f9.png

第一行是monitorenter,表示进入锁的范围,后面还跟着对于的代码行数。

最后一行是monitorexit,表示退出锁的范围。

中间有个add $0x51,%eax操作,对于着我们的代码中的add操作。

可以看到C1—compiled nmethod中是没有进行Loop unrolling的。

我们再看看C2-compiled nmethod:

50f1e23413775f599749baaa538e8e21.png

和C1很类似,不同的是add的值变成了0x144,说明进行了Loop unrolling,同时对应的锁范围也跟着进行了扩展。

最后看下运行结果:

Benchmark Mode Cnt Score Error Units

LockOptimization.test avgt 5 5601.819 ± 620.017 ns/op

复制代码

得分还不错。

禁止Loop unrolling

接下来我们看下如果将Loop unrolling禁掉,会得到什么样的结果。

要禁止Loop unrolling,只需要设置-XX:LoopUnrollLimit=1即可。

我们再运行一下上面的程序:

4b61523e144e0724e052dccb5e1219ba.png

可以看到C2-compiled nmethod中的数字变成了原本的0x51,说明并没有进行Loop unrolling。

再看看运行结果:

Benchmark Mode Cnt Score Error Units

LockOptimization.test avgt 5 20846.709 ± 3292.522 ns/op

复制代码

可以看到运行时间基本是优化过后的4倍左右。说明Loop unrolling还是非常有用的。

总结

本文介绍了循环展开和粗化锁的实际例子,希望大家能够喜欢。

本文的例子github.com/ddean2009/l…

本文作者:flydean程序那些事

本文链接:www.flydean.com/jvm-jit-loo…

本文来源:flydean的博客

欢迎关注我的公众号:程序那些事,更多精彩等着您!

b739ec46bb5c46d9c0aa4ce35ba1ea56.png

关于找一找教程网

本站文章仅代表作者观点,不代表本站立场,所有文章非营利性免费分享。

本站提供了软件编程、网站开发技术、服务器运维、人工智能等等IT技术文章,希望广大程序员努力学习,让我们用科技改变世界。

[深入理解编译优化之循环展开和粗化锁]http://www.zyiz.net/tech/detail-142322.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值