详解Java编译优化之循环展开和粗化锁

循环展开和粗化锁

我们先来回顾一下什么是循环展开。

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

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:

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

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

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

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

我们再看看C2-compiled nmethod:

和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即可。

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

可以看到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还是非常有用的。

以上就是详解Java编译优化之循环展开和粗化锁的详细内容,更多关于Java编译优化之循环展开和粗化锁的资料请关注我们其它相关文章!

(0)

相关推荐

  • Java for循环Map集合优化实现解析

    这篇文章主要介绍了Java for循环Map集合优化实现解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 在<for循环实战性能优化>中提出了五种提升for循环性能的优化策略,这次我们在其中嵌套循环优化小循环驱动大循环的基础上,借助Map集合高效的查询性能来优化嵌套for循环. 如果小循环和大循环的集合元素数量分别为M和N,则双层For循环的循环次数是M*N,随着M和N的增长,对性能的影响越来越大.因此,本文考虑进一步优化,使得循环次数变为

  • Java利用策略模式优化过多if else代码

    前言 不出意外,这应该是年前最后一次分享,本次来一点实际开发中会用到的小技巧. 比如平时大家是否都会写类似这样的代码: if(a){ //dosomething }else if(b){ //doshomething }else if(c){ //doshomething } else{ ////doshomething } 条件少还好,一旦 else if 过多这里的逻辑将会比较混乱,并很容易出错. 比如这样: 摘自cim中的一个客户端命令的判断条件. 刚开始条件较少,也就没管那么多直接写的:

  • Java for循环性能优化实现解析

    这篇文章主要介绍了Java for循环性能优化实现解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 完成同样的功能,用不同的代码来实现,性能上可能会有比较大的差别,所以对于一些性能敏感的模块来说,对代码进行一定的优化还是很有必要的.今天就来说一下java代码优化的事情,今天主要聊一下对于for(while等同理)循环的优化,它作为三大结构之一的循环,在我们编写代码的时候会经常用到.循环结构让我们操作数组.集合和其他一些有规律的事物变得更加的方

  • Java编译和解释执行对比及原理解析

    编程语言分为低级语言和高级语言,机器语言.汇编语言是低级语言,C.C++.java.python等是高级语言. 机器语言是最底层的语言,能够直接执行.而我们编写的源代码是人类语言, 计算机只能识别某些特定的二进制指令,在程序真正运行之前必须将源代码转换成二进制指令. 汇编语言通过汇编器翻译成机器指令后执行,一条汇编指令,对应着一条机器指令. 高级语言编程的程序有三种执行方式: 1.一种是编译执行,源程序先通过编译器(负责将源程序翻译成目标机器指令)翻译成机器指令,通过编译-->链接-->目标可

  • Java package编译乱码问题解决

    package包-->可以理解为是一个文件夹: package 类的第一行写关键字 告知我的类在哪里: import 用来导入别人的包: 如果上述两个关键字同时出现 先写package(只有一个) 后写import(可以多个); 一.编译执行: 如上,java代码中包含包:如何编译执行呢?编译是在当前目录下(图中表示编译当前目录下的所有.java文件):执行是在当前目录的上一层目录中,格式: java 包名:含主方法的java文件名: 二.乱码解决办法: 新建txt文本文档-->打开txt文本

  • Java优化for循环嵌套的高效率方法

    前几天有人问过我一个问题,就是两个嵌套for循环执行效率的问题,问有什么好的办法替换.当时我想了想,实在想不起来,哎,惭愧!!! 请教了答案,恍然大悟. 比如:两个list中分别装有相同的对象数据. list1中有3万条对象数据. list2中有2万条对象数据(但是对象中的某个属性变量为空).两个list中的id或者其他变量都一模一样.请用最快的方式找出list2中变量为空的那个对象,并且去list1中找出id相同的对象. 或者可以理解成,从list2中找出变量为空的,去list1中找出对应的对

  • java编译器和JVM的区别

    Java虚拟机(JVM)是可运行Java代码的假想计算机.只要根据JVM规格描述将解释器移植到特定的计算机上,就能保证经过编译的任何Java代码能够在该系统上运行.java编译器把java编译成字节码,也就是.class文件,然后JVM给编译成的字节码提供运行环境.java的源代码是无法直接在JVM上运行的. 1.java编译器 Java语言写的源程序通过Java编译器,编译成与平台无关的'字节码程序'(.class文件,也就是0,1二进制程序),然后在OS之上的Java解释器中解释执行. 也相

  • Java编程中的性能优化如何实现

      String作为我们使用最频繁的一种对象类型,其性能问题是最容易被忽略的.作为Java中重要的数据类型,是内存中占据空间比较大的一个对象.如何高效地使用字符串,可以帮助我们提升系统的整体性能. 现在,我们就从String对象的实现.特性以及实际使用中的优化这几方面来入手,深入理解以下String的性能优化. 在这之前,首先看一个问题.通过三种方式创建三个对象,然后依次两两匹配,得出的结果是什么?答案留到最后揭晓. String str1 = "abc"; String str2 =

  • 详解Java编译优化之循环展开和粗化锁

    循环展开和粗化锁 我们先来回顾一下什么是循环展开. 循环展开就是说,像下面的循环遍历的例子: for (int i = 0; i < 1000; i++) { x += 0x51; } 因为每次循环都需要做跳转操作,所以为了提升效率,上面的代码其实可以被优化为下面的: for (int i = 0; i < 250; i++) { x += 0x144; //0x51 * 4 } 注意上面我们使用的是16进制数字,至于为什么要使用16进制呢?这是为了方便我们在后面的assembly代码中快速找

  • 详解Java ReentrantLock可重入,可打断,锁超时的实现原理

    目录 概述 可重入 可打断 锁超时 概述 前面讲解了ReentrantLock加锁和解锁的原理实现,但是没有阐述它的可重入.可打断以及超时获取锁失败的原理,本文就重点讲解这三种情况.建议大家先看下这篇文章了解下ReentrantLock加锁的基本原理,图解Java ReentrantLock公平锁和非公平锁的实现. 可重入 可重入是指一个线程如果获取了锁,那么它就是锁的主人,那么它可以再次获取这把锁,这种就是理解为重入,简而言之,可以重复获取同一把锁,不会造成阻塞,举个例子如下: @Test p

  • windows命令行中java和javac、javap使用详解(java编译命令)

    如题,首先我们在桌面,开始->运行->键入cmd 回车,进入windows命令行.进入如图所示的画面: 可知,当前默认目录为C盘Users文件夹下的Administrator文件夹.一般而言,我们习惯改变当前目录.由于windows有磁盘分区,若要跳到其他磁盘,例如E盘,有几种方法: 1.输入命令: pushd 路径(此命令可将当前目录设为所希望的任一个已存在的路径) 2.输入命令: e:  转移到e盘,然后再输入 cd 转移到所希望的已知路径. 如图: 希望在windows命令行下使用jav

  • 详解Java如何实现基于Redis的分布式锁

    前言 单JVM内同步好办, 直接用JDK提供的锁就可以了,但是跨进程同步靠这个肯定是不可能的,这种情况下肯定要借助第三方,我这里实现用Redis,当然还有很多其他的实现方式.其实基于Redis实现的原理还算比较简单的,在看代码之前建议大家先去看看原理,看懂了之后看代码应该就容易理解了. 我这里不实现JDK的java.util.concurrent.locks.Lock接口,而是自定义一个,因为JDK的有个newCondition方法我这里暂时没实现.这个Lock提供了5个lock方法的变体,可以

  • 详解Java的编译执行与解释执行

    一.前言 编程语言分为低级语言和高级语言,机器语言.汇编语言是低级语言,C.C++.java.python等是高级语言. 机器语言是最底层的语言,能够直接执行.而我们编写的源代码是人类语言,计算机只能识别某些特定的二进制指令,在程序真正运行之前必须将源代码转换成二进制指令.汇编语言通过汇编器翻译成机器指令后执行,一条汇编指令,对应着一条机器指令. 高级语言编程的程序有三种执行方式: 1.一种是编译执行,源程序先通过编译器(负责将源程序翻译成目标机器指令)翻译成机器指令,通过编译-->链接-->

  • 详解Java中的 枚举与泛型

    详解Java中的 枚举与泛型 一:首先从枚举开始说起 枚举类型是JDK5.0的新特征.Sun引进了一个全新的关键字enum来定义一个枚举类.下面就是一个典型枚举类型的定义: public enum Color{ RED,BLUE,BLACK,YELLOW,GREEN } 显然,enum很像特殊的class,实际上enum声明定义的类型就是一个类. 而这些类都是类库中Enum类的子类(Java.lang.Enum).它们继承了这个Enum中的许多有用的方法.我们对代码编译之后发现,编译器将 enu

  • 详解JAVA类加载机制

    1.一段简单的代码 首先来一段代码,这个是单例模式,可能有的人不知道什么是单例模式,我就简单说一下 单例模式是指一个类有且只有一种对象实例.这里用的是饿汉式,还有懒汉式,双检锁等等.... 写这个是为了给大家看一个现象 class SingleTon{ public static int count1; public static int count2=0; private static SingleTon instance=new SingleTon(); public SingleTon()

  • 详解JAVA中static的作用

    1.深度总结 引用一位网友的话,说的非常好,如果别人问你static的作用:如果你说静态修饰 类的属性 和 类的方法 别人认为你是合格的:如果是说 可以构成 静态代码块,那别人认为你还可以: 如果你说可以构成 静态内部类, 那别人认为你不错:如果你说了静态导包,那别人认为你很OK: 那我们就先在这几方面一一对static进行总结:然后说一些模糊的地方,以及一些面试中容易问道的地方: 1)static方法 static方法一般称作静态方法,由于静态方法不依赖于任何对象就可以进行访问,因此对于静态方

  • 详解JAVA 常量池

    前言 对常量池的理解之前,需要熟悉的是一些术语: 字面量 在计算机科学中,字面量(literal)是用于表达源代码中一个固定值的表示法(notation). 几乎所有计算机编程语言都具有对基本值的字面量表示,诸如:整数.浮点数以及字符串:而有很多也对布尔类型和字符类型的值也支持字面量表示: 还有一些甚至对枚举类型的元素以及像数组.记录和对象等复合类型的值也支持字面量表示法.C语言关于复合字面量的介绍可参考: [1]  . 百度也给了一个例子: 这个object-c 的例子,容易理解. #incl

  • 详解Java 中的 AutoCloseable 接口

    一.前言 最近用到了 JDK 7 中的新特性 try-with-resources 语法,感觉到代码相对简洁了很多,于是花了点时间详细学习了下,下面分享给大家我的学习成果. 二.简单了解并使用 try-with-resources语法比较容易使用,一般随便搜索看下示例代码就能用起来了.JDK 对这个语法的支持是为了更好的管理资源,准确说是资源的释放. 当一个资源类实现了该接口close方法,在使用try-with-resources语法创建的资源抛出异常后,JVM会自动调用close 方法进行资

随机推荐