小师妹学JVM之:cache line对代码性能的影响

目录

简介

读万卷书不如行万里路,讲了这么多assembly和JVM的原理与优化,今天我们来点不一样的实战。探索一下怎么使用assembly来明白我们之前不能明白的问题。

一个新鲜的征象

小师妹:F师兄,之前你讲了那么多JVM中JIT在编译中的性能优化,讲真的,在事情中我们真的需要知道这些器械吗?知道这些器械对我们的事情有什么利益吗?

um…这个问题问得好,知道了JIT的编译原理和优化偏向,我们简直可以在写代码的时刻稍微注重一下,写出性能加倍优异的代码,然则这只是微观上了。

若是将代码上升到企业级应用,一个硬件的提升,一个缓存的加入或者一种架构的改变都可能比小小的代码优化要有用得多。

就像是,若是我们的项目遇到了性能问题,我们第一反应是去找架构上面有没有什么缺陷,有没有什么优化点,很少或者说基本上不会去深入到代码层面,看你的这个代码到底有没有可优化空间。

第一,只要代码的营业逻辑不差,运行起来速率也不会太慢。

第二,代码的优化带来的收益实在太小了,而事情量又异常重大。

以是说,对于这种类似于鸡肋的优化,真的有需要存在吗?

实在这和我学习物理化学数学知识是一样的,你学了那么多知识,实在在一样平常生涯中真的用不到。然则为什么要学习呢?

我以为有两个缘故原由,第一是让你对这个天下有加倍本质的熟悉,知道这个天下是怎么运行的。第二是磨炼自己的思维习惯,学会解决问题的方式。

就想算法,现在写个程序真的需要用到算法吗?不见得,然则算法真的很主要,由于它可以影响你的思维习惯。

以是,领会JVM的原理,甚至是Assembly的使用,并不是要你用他们来让你的代码优化的若何好,而是让你知道,哦,原来代码是这样事情的。在未来的某一个,或许我就可能用到。

好了,言归正传。今天给小师妹先容一个很新鲜的例子:

private static int[] array = new int[64 * 1024 * 1024];

    @Benchmark
    public void test1() {
        int length = array.length;
        for (int i = 0; i < length; i=i+1)
            array[i] ++;
    }
    @Benchmark
    public void test2() {
        int length = array.length;
        for (int i = 0; i < length; i=i+2)
            array[i] ++;
    }

小师妹,上面的例子,你以为哪一个运行的更快呢?

小师妹:当然是第二个啦,第二个每次加2,遍历的次数更少,一定执行得更快。

好,我们先持保留意见。

第二个例子,上面我们是划分+1和+2,若是后面再继续+3,+4,一直加到128,你以为运行时间是怎么样的呢?

小师妹:一定是线性削减的。

好,两个问题问完了,接下来让我们来揭晓谜底吧。

两个问题的谜底

我们再次使用JMH来测试我们的代码。代码很长,这里就不列出来了,有兴趣的同伙可以到本文下面的代码链接下载运行代码。

我们直接上运行效果:

Benchmark               Mode  Cnt   Score   Error  Units
CachelineUsage.test1    avgt    5  27.499 ± 4.538  ms/op
CachelineUsage.test2    avgt    5  31.062 ± 1.697  ms/op
CachelineUsage.test3    avgt    5  27.187 ± 1.530  ms/op
CachelineUsage.test4    avgt    5  25.719 ± 1.051  ms/op
CachelineUsage.test8    avgt    5  25.945 ± 1.053  ms/op
CachelineUsage.test16   avgt    5  28.804 ± 0.772  ms/op
CachelineUsage.test32   avgt    5  21.191 ± 6.582  ms/op
CachelineUsage.test64   avgt    5  13.554 ± 1.981  ms/op
CachelineUsage.test128  avgt    5   7.813 ± 0.302  ms/op

好吧,不够直观,我们用一个图表来示意:

小师妹学JVM之:cache line对代码性能的影响

从图表可以看出,步长在1到16之间的时刻,执行速率都还相对对照平稳,在25左右,然后就随着步长的增进而下降。

Java日期和时间

CPU cache line

那么我们先回覆第二个问题的谜底,执行时间是先平稳再下降的。

为什么会在16步长之内很平稳呢?

CPU的处置速率是有限的,为了提升CPU的处置速率,现代CPU都有一个叫做CPU缓存的器械。

而这个CPU缓存又可以分为L1缓存,L2缓存甚至L3缓存。

其中L1缓存是每个CPU核单独享有的。在L1缓存中,又有一个叫做Cache line的器械。为了提升处置速率,CPU每次处置都是读取一个Cache line巨细的数据。

怎么查看这个Cache line的巨细呢?

在mac上,我们可以执行:sysctl machdep.cpu

小师妹学JVM之:cache line对代码性能的影响

从图中我们可以获得,机子的CPU cache line是64byte,而cpu的一级缓存巨细是256byte。

好了,现在回到为什么1-16步长执行速率差不多的问题。

我们知道一个int占用4bytes,那么16个int恰好占用64bytes。以是我们可以大略的以为,1-16步长,每次CPU取出来的数据是一样的,都是一个cache line。以是,他们的执行速率实在是差不多的。

inc 和 add

小师妹:F师兄,上面的注释虽然有点完美了,然则似乎另有一个破绽。既然1-16使用的是同一个cache line,那么他们的执行时间,应该是逐步下降才对,为什么2比1执行时间还要长呢?

这真的是一个好问题,光看代码和cache line似乎都注释不了,那么我们就从Assembly的角度再来看看。

照样使用JMH,打开PrintAssembly选项,我们看看输出效果。

先看下test1方式的输出:

小师妹学JVM之:cache line对代码性能的影响

再看下test2方式的输出:

小师妹学JVM之:cache line对代码性能的影响

两个有什么区别呢?

基本上的结构都是一样的,只不外test1使用的是inc,而test2方式使用的add。

本人对汇编语言不太熟,不外我猜两者执行时间的差异在于inc和add的差异,add可能会执行慢一点,由于它多了一个分外的参数。

总结

Assembly虽然没太大用处,然则在注释某些神秘征象的时刻,照样挺好用的。

本文的例子https://github.com/ddean2009/learn-java-base-9-to-20

本文作者:flydean程序那些事

本文链接:http://www.flydean.com/jvm-jit-cacheline/

本文泉源:flydean的博客

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

原创文章,作者:admin,如若转载,请注明出处:https://www.2lxm.com/archives/20857.html