JMH-分析equals的优化空间

  • 使用了 JMH之后,对于每一个小方法,都可以做一个非常深入的研究,对比,不一样的写法,不一样的风格,到底有没有区别,到底谁最好。

本次深入研究一下 equals

  • 代码来源是 查看 阿里开源包中的工具包中的代码,看到之后,眼前一亮,然后进入了深思。

     /**
         * check whether two components are equal<br/>
         *
         * @param src    source component
         * @param target target component
         * @param <E>    component type
         * @return <br/>
         * (null, null)    == true
         * (1L,2L)         == false
         * (1L,1L)         == true
         * ("abc",null)    == false
         * (null,"abc")    == false
         */
        public static <E> boolean isEquals(E src, E target) {
            return null == src
                    && null == target
                    || null != src
                    && null != target
                    && src.equals(target);
        }
    
  • 对于 equals的使用,常规会有两种方式:

    • 1) 常量.equals(obj)

      这判断,没有问题,有些经验的程序员,都知道把常量放在前面,避免空指针

      1. obj1.equals(obj2)

      这样使用,会有 Java.lang.NullPointerException 的风险 ,所以常规,会有一个 if(null != str1){}作为前提。

  • 阿里将其包装了一下:

      1. 好处1:写成一个工具类,直接使用即可,避免新人犯错误的成本。
      1. 好处2:这个工具类,包裹住 非空的判断, 让使用的代码,简洁。
    • 3)疑问:这样包装,是否会有性能的提升空间?

下面开始进行性能的详细分析

1)以往常规的测试方法,会写一个 main方法,进行测试:
  public static void main(String[] args) {
        int[] count = {10000,100000,1000000,10000000};
        for(int num : count){
            System.out.println("------------执行"+num+"次------------");
            testEqu(num);
        }
    }
    public static void testEqu(int count){
        Affect affect = new Affect();
        String a = "1";
        String b = "2";
        int num = count;
        int temp = 0;
        while (num-- > 0) {
            if (a != null && a.equals(b)) {
                temp++;
            }
            temp++;
        }
        System.out.println("  a.equals(b):"+affect.cost());
        affect = new Affect();
        num = count;
        temp = 0;
        while (num-- > 0) {
            if (isEquals(a, b)) {
                temp++;
            }
            temp++;
        }
        System.out.println("isEquals(a, b):"+affect.cost());
    }

运行结果:

------------执行10000次------------
  a.equals(b):1
isEquals(a, b):0
------------执行100000次------------
  a.equals(b):5
isEquals(a, b):2
------------执行1000000次------------
  a.equals(b):3
isEquals(a, b):18
------------执行10000000次------------
  a.equals(b):29
isEquals(a, b):28

看到这个结果,小伙子们,是不是会觉得非常奇怪,为什么呢?

看到这个结果,小伙子们,是不是会觉得非常奇怪,为什么呢?

看到这个结果,小伙子们,是不是会觉得非常奇怪,为什么呢?

  • 然后我们来改造一下代码:
public static void main(String[] args) throws InterruptedException {
        int[] count = {10000,100000,1000000,10000000};
        for(int num : count){
            System.out.println("------------执行"+num+"次------------");
            testEqu(num);
            Thread.sleep(2000); // -------------------------加了这里
        }
    }
    public static void testEqu(int count) throws InterruptedException {
        Affect affect = new Affect();
        String a = "1";
        String b = "2";
        int num = count;
        int temp = 0;
        while (num-- > 0) {
            if (a != null && a.equals(b)) {
                temp++;
            }
            temp++;
        }
        System.out.println("  a.equals(b):"+affect.cost());
        Thread.sleep(2000);// -------------------------加了这里
        affect = new Affect();
        num = count;
        temp = 0;
        while (num-- > 0) {
            if (isEquals(a, b)) {
                temp++;
            }
            temp++;
        }
        System.out.println("isEquals(a, b):"+affect.cost());
    }

结果是:

------------执行10000次------------
  a.equals(b):0
isEquals(a, b):0
------------执行100000次------------
  a.equals(b):7
isEquals(a, b):2
------------执行1000000次------------
  a.equals(b):17
isEquals(a, b):3
------------执行10000000次------------
  a.equals(b):45
isEquals(a, b):34

这样看,就比较明白了,但是这是为什么呢?

  • 最主要的原因是GC的垃圾回收导致,这样写测试代码,是不准确的,变量都在一个类里,当对象的生命周期结束了,GC的过程,会影响其他代码的运作。
  • 还有一个问题,就是 JIT , 运行次数没有到一定的程度,无法进入 JIT,但是静态方法块,先天就有优势,提前进入了JIT,所以也有可能不准确

so ,引出了 JMH , 见下面

2)使用 JMH 看一下情况

代码如下:

package org.openjdk.jmh.samples;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import static org.openjdk.jmh.samples.ArthasCheckUtils.isEquals;

@State(Scope.Thread) // 每个测试线程一个实例
public class JMHDemo01 {
    @Benchmark
    public String stringConcat() {
        String a = "1";
        String b = "2";
        int f = 0;
        if (a != null) {
            if (a.equals(b)) {
                f++;
            }
        }
        return "";
    }
    @Benchmark
    public String stringConcatIsEquals() {
        String a = "1";
        String b = "2";
        int f = 0;
        if(isEquals(a, b)) {
            f++;
        }
        return "";
    }
}

测试 main 方法

public static void main(String[] args) throws RunnerException {
    Options opt = new OptionsBuilder()
            .include(JMHDemo01.class.getSimpleName())
            .forks(1)
            .build();

    new Runner(opt).run();
}

看看结果:

Benchmark                        Mode  Cnt          Score        Error  Units
JMHDemo01.stringConcat          thrpt    5  132834643.560 ± 443289.509  ops/s
JMHDemo01.stringConcatIsEquals  thrpt    5  132995301.389 ±  52712.796  ops/s

  • 结论:

    • 使用了JMH 之后, 发现 结果非常的接近,包装了 equals 之后,性能还是提高了满多的,十几万次ops, 但是没有之前的测试的这么大的差距,只相差 0.12%.

    • 确定了 之前 考虑的 JIT的问题,他们其实是一样的方法,一样都到热区后,理论上的性能,应该是一致的。

    • 但是对于这样优化的必要性而言,还是非常有必要的,其他的优势依旧存在:

      • 1)代码整洁美观的提升
      • 2)编码质量的提升
      • 3)提前进入热区
      • 4)可以从结果中看出,优化后的方法,更加稳定

测试代码,有写的不对的地方,请指出,转载,请标明出处。

有问题,可以给我留言。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容