JAVA程序实例:请不要再说Java中final方法比非final性能更好了
小职 2018-01-10 来源 :网络 阅读 654 评论 0

摘要:JAVA程序实例之请不要再说Java中final方法比非final性能更好了!

无继承

JAVA程序实例之请不要再说Java中final方法比非final性能更好了!

有 static 修饰

static final

// 生成随机数字和字母,public static final String getStringRandomFinal(int length) {

    String val = "";

    Random random = new Random();

    // 参数length,表示生成几位随机数

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

        String charOrNum = random.nextInt(2) % 2 == 0 ? "char" : "num";

        // 输出字母还是数字

        if ("char".equalsIgnoreCase(charOrNum)) {

            // 输出是大写字母还是小写字母

            // int temp = random.nextInt(2) % 2 == 0 ? 65 : 97;

            val += (char) (random.nextInt(26) + 97);

        } else if ("num".equalsIgnoreCase(charOrNum)) {

            val += String.valueOf(random.nextInt(10));

        }

    }

    return val;

}

static 非 final

// 生成随机数字和字母,public static String getStringRandom(int length) {

    String val = "";

    Random random = new Random();

    // 参数length,表示生成几位随机数

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

        String charOrNum = random.nextInt(2) % 2 == 0 ? "char" : "num";

        // 输出字母还是数字

        if ("char".equalsIgnoreCase(charOrNum)) {

            // 输出是大写字母还是小写字母

            // int temp = random.nextInt(2) % 2 == 0 ? 65 : 97;

            val += (char) (random.nextInt(26) + 97);

        } else if ("num".equalsIgnoreCase(charOrNum)) {

            val += String.valueOf(random.nextInt(10));

        }

    }

    return val;

}

结果

这里使用了 OpenJDK 的 JMH 基准测试工具来测试的,结果如下:

# JMH 1.4.1 (released 903 days ago, please consider updating!)# VM invoker: /srv/jdk1.8.0_92/jre/bin/java# VM options: <none># Warmup: 20 iterations, 1 s each# Measurement: 20 iterations, 1 s each# Timeout: 10 min per iteration# Threads: 1 thread, will synchronize iterations# Benchmark mode: Throughput, ops/time# Benchmark: org.agoncal.sample.jmh.Main.benchmark

中间忽略了预热及测试过程,这里只显示结果

Result: 206924.113 ±(99.9%) 7746.446 ops/s [Average]

  Statistics: (min, avg, max) = (132107.466, 206924.113, 267265.397), stdev = 32798.937

  Confidence interval (99.9%): [199177.667, 214670.559]# JMH 1.4.1 (released 903 days ago, please consider updating!)# VM invoker: /srv/jdk1.8.0_92/jre/bin/java# VM options: <none># Warmup: 20 iterations, 1 s each# Measurement: 20 iterations, 1 s each# Timeout: 10 min per iteration# Threads: 1 thread, will synchronize iterations# Benchmark mode: Throughput, ops/time# Benchmark: org.agoncal.sample.jmh.Main.benchmarkFinal

中间忽略了预热及测试过程,这里只显示结果

Result: 210111.568 ±(99.9%) 8486.176 ops/s [Average]

  Statistics: (min, avg, max) = (133813.368, 210111.568, 267525.228), stdev = 35931.001

  Confidence interval (99.9%): [201625.392, 218597.744]

# Run complete. Total time: 00:13:54

Benchmark                       Mode  Samples       Score      Error  Units

o.a.s.j.Main.benchmark         thrpt      200  206924.113 ± 7746.446  ops/s

o.a.s.j.Main.benchmarkFinal    thrpt      200  210111.568 ± 8486.176  ops/s

总结:你说final的性能比非final有没有提升呢?可以说有,但几乎可以忽略不计。如果单纯地追求性能,而将所有的方法修改为 final 的话,我认为这样子是不可取的。而且这性能的差别,远远也没有网上有些人说的提升 50% 这么恐怖(有可能他们使用的是10年前的JVM来测试的吧^_^,比如 《35+ 个 Java 代码性能优化总结》这篇文章。雷总:不服?咱们来跑个分!)

分析

字节码级别的差别

StringKit.java
StringKitFinal.java

它们在字节码上的差别:

[18:52:08] emacsist:target $ diff /tmp/stringkit.log /tmp/stringkit-final.log1,5c1,5

< Classfile /Users/emacsist/Documents/idea/logging/target/classes/org/agoncal/sample/jmh/StringKit.class

<   Last modified 2017-6-15; size 1098 bytes

<   MD5 checksum fe1ccdde26107e4037afc54c780f2c95

<   Compiled from "StringKit.java"

< public class org.agoncal.sample.jmh.StringKit

---

> Classfile /Users/emacsist/Documents/idea/logging/target/classes/org/agoncal/sample/jmh/StringKitFinal.class

>   Last modified 2017-6-15; size 1118 bytes

>   MD5 checksum 410f8bf0eb723b794e4754c6eb8b9829

>   Compiled from "StringKitFinal.java"

> public class org.agoncal.sample.jmh.StringKitFinal24c24

<   #15 = Class              #52            // org/agoncal/sample/jmh/StringKit

---

>   #15 = Class              #52            // org/agoncal/sample/jmh/StringKitFinal32,33c32,33

<   #23 = Utf8               Lorg/agoncal/sample/jmh/StringKit;

<   #24 = Utf8               getStringRandom

---

>   #23 = Utf8               Lorg/agoncal/sample/jmh/StringKitFinal;

>   #24 = Utf8               getStringRandomFinal47c47

<   #38 = Utf8               StringKit.java

---

>   #38 = Utf8               StringKitFinal.java61c61

<   #52 = Utf8               org/agoncal/sample/jmh/StringKit

---

>   #52 = Utf8               org/agoncal/sample/jmh/StringKitFinal75c75

<   public org.agoncal.sample.jmh.StringKit();

---

>   public org.agoncal.sample.jmh.StringKitFinal();87c87

<             0       5     0  this   Lorg/agoncal/sample/jmh/StringKit;

---

>             0       5     0  this   Lorg/agoncal/sample/jmh/StringKitFinal;89c89

<   public static java.lang.String getStringRandom(int);

---

>   public static final java.lang.String getStringRandomFinal(int);91c91

<     flags: ACC_PUBLIC, ACC_STATIC

---

>     flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL187c187

< SourceFile: "StringKit.java"

---

> SourceFile: "StringKitFinal.java"

可以看到除了方法名和方法修饰符不同之外,其他的没有什么区别了。

在调用者上面的字节码差别

public void benchmark();

  descriptor: ()V

  flags: ACC_PUBLIC

  Code:

    stack=1, locals=1, args_size=1

       0: bipush        32

       2: invokestatic  #2                  // Method org/agoncal/sample/jmh/StringKit.getStringRandom:(I)Ljava/lang/String;

       5: pop

       6: return

    LineNumberTable:

      line 21: 0

      line 22: 6

    LocalVariableTable:

      Start  Length  Slot  Name   Signature

          0       7     0  this   Lorg/agoncal/sample/jmh/Main;

  RuntimeVisibleAnnotations:

    0: #26()public void benchmarkFinal();

  descriptor: ()V

  flags: ACC_PUBLIC

  Code:

    stack=1, locals=1, args_size=1

       0: bipush        32

       2: invokestatic  #3                  // Method org/agoncal/sample/jmh/StringKitFinal.getStringRandomFinal:(I)Ljava/lang/String;

       5: pop

       6: return

    LineNumberTable:

      line 26: 0

      line 27: 6

    LocalVariableTable:

      Start  Length  Slot  Name   Signature

          0       7     0  this   Lorg/agoncal/sample/jmh/Main;

  RuntimeVisibleAnnotations:

    0: #26()

可以看到,它们在调用者上面的字节码也没有什么区别,只是方法名不一样之外。

对于 JVM 来说,它是只认字节码的,既然字节码除了方法名和修饰符一样,其他都一样,那就可以大概推测它们的性能几乎可以忽略不计了。因为调用 static final 和 static 非 final 的JVM指令是一样。

无 static 修饰

方法体是一样的,只是将它们删除了 static 的修饰。

结果

# JMH version: 1.19# VM version: JDK 1.8.0_92, VM 25.92-b14# VM invoker: /srv/jdk1.8.0_92/jre/bin/java# VM options: <none># Warmup: 20 iterations, 1 s each# Measurement: 20 iterations, 1 s each# Timeout: 10 min per iteration# Threads: 1 thread, will synchronize iterations# Benchmark mode: Throughput, ops/time# Benchmark: org.agoncal.sample.jmh.Main.benchmark

中间忽略了预热及测试过程,这里只显示结果

Result "org.agoncal.sample.jmh.Main.benchmark":

  201306.770 ±(99.9%) 8184.423 ops/s [Average]

  (min, avg, max) = (131889.934, 201306.770, 259928.172), stdev = 34653.361

  CI (99.9%): [193122.347, 209491.193] (assumes normal distribution)# JMH version: 1.19# VM version: JDK 1.8.0_92, VM 25.92-b14# VM invoker: /srv/jdk1.8.0_92/jre/bin/java# VM options: <none># Warmup: 20 iterations, 1 s each# Measurement: 20 iterations, 1 s each# Timeout: 10 min per iteration# Threads: 1 thread, will synchronize iterations# Benchmark mode: Throughput, ops/time# Benchmark: org.agoncal.sample.jmh.Main.benchmarkFinal

中间忽略了预热及测试过程,这里只显示结果

Result "org.agoncal.sample.jmh.Main.benchmarkFinal":

  196871.022 ±(99.9%) 8595.719 ops/s [Average]

  (min, avg, max) = (131182.268, 196871.022, 265522.769), stdev = 36394.814

  CI (99.9%): [188275.302, 205466.741] (assumes normal distribution)

# Run complete. Total time: 00:13:35

Benchmark             Mode  Cnt       Score      Error  Units

Main.benchmark       thrpt  200  201306.770 ± 8184.423  ops/s

Main.benchmarkFinal  thrpt  200  196871.022 ± 8595.719  ops/s

分析

字节码级别的差别

[19:20:17] emacsist:target $ diff /tmp/stringkit.log /tmp/stringkit-final.log1,5c1,5

< Classfile /Users/emacsist/Documents/idea/logging/target/classes/org/agoncal/sample/jmh/StringKit.class

<   Last modified 2017-6-15; size 1110 bytes

<   MD5 checksum f61144e86f7c17dc5d5f2b2d35fac36d

<   Compiled from "StringKit.java"

< public class org.agoncal.sample.jmh.StringKit

---

> Classfile /Users/emacsist/Documents/idea/logging/target/classes/org/agoncal/sample/jmh/StringKitFinal.class

>   Last modified 2017-6-15; size 1130 bytes

>   MD5 checksum 15ce17ee17fdb5f4721f0921977b1e69

>   Compiled from "StringKitFinal.java"

> public class org.agoncal.sample.jmh.StringKitFinal24c24

<   #15 = Class              #52            // org/agoncal/sample/jmh/StringKit

---

>   #15 = Class              #52            // org/agoncal/sample/jmh/StringKitFinal32,33c32,33

<   #23 = Utf8               Lorg/agoncal/sample/jmh/StringKit;

<   #24 = Utf8               getStringRandom

---

>   #23 = Utf8               Lorg/agoncal/sample/jmh/StringKitFinal;

>   #24 = Utf8               getStringRandomFinal47c47

<   #38 = Utf8               StringKit.java

---

>   #38 = Utf8               StringKitFinal.java61c61

<   #52 = Utf8               org/agoncal/sample/jmh/StringKit

---

>   #52 = Utf8               org/agoncal/sample/jmh/StringKitFinal75c75

<   public org.agoncal.sample.jmh.StringKit();

---

>   public org.agoncal.sample.jmh.StringKitFinal();87c87

<             0       5     0  this   Lorg/agoncal/sample/jmh/StringKit;

---

>             0       5     0  this   Lorg/agoncal/sample/jmh/StringKitFinal;89c89

<   public java.lang.String getStringRandom(int);

---

>   public final java.lang.String getStringRandomFinal(int);91c91

<     flags: ACC_PUBLIC

---

>     flags: ACC_PUBLIC, ACC_FINAL169c169

<             0     125     0  this   Lorg/agoncal/sample/jmh/StringKit;

---

>             0     125     0  this   Lorg/agoncal/sample/jmh/StringKitFinal;188c188

< SourceFile: "StringKit.java"

---

> SourceFile: "StringKitFinal.java"

可以看到,字节码上除了名字和 final 修饰符差别外,其余的是一样的。

在调用者上面的字节码差别

public void benchmark();

  descriptor: ()V

  flags: ACC_PUBLIC

  Code:

    stack=2, locals=1, args_size=1

       0: new           #2                  // class org/agoncal/sample/jmh/StringKit

       3: dup

       4: invokespecial #3                  // Method org/agoncal/sample/jmh/StringKit."<init>":()V

       7: bipush        32

       9: invokevirtual #4                  // Method org/agoncal/sample/jmh/StringKit.getStringRandom:(I)Ljava/lang/String;

      12: pop

      13: return

    LineNumberTable:

      line 21: 0

      line 22: 13

    LocalVariableTable:

      Start  Length  Slot  Name   Signature

          0      14     0  this   Lorg/agoncal/sample/jmh/Main;

  RuntimeVisibleAnnotations:

    0: #30()public void benchmarkFinal();

  descriptor: ()V

  flags: ACC_PUBLIC

  Code:

    stack=2, locals=1, args_size=1

       0: new           #5                  // class org/agoncal/sample/jmh/StringKitFinal

       3: dup

       4: invokespecial #6                  // Method org/agoncal/sample/jmh/StringKitFinal."<init>":()V

       7: bipush        32

       9: invokevirtual #7                  // Method org/agoncal/sample/jmh/StringKitFinal.getStringRandomFinal:(I)Ljava/lang/String;

      12: pop

      13: return

    LineNumberTable:

      line 26: 0

      line 27: 13

    LocalVariableTable:

      Start  Length  Slot  Name   Signature

          0      14     0  this   Lorg/agoncal/sample/jmh/Main;

  RuntimeVisibleAnnotations:

    0: #30()

可以看到,它们除了名字不同之外,其他的JVM指令都是一样的。

总结

对于是否有 final 修饰的方法,对性能的影响可以忽略不计。因为它们生成的字节码除了 flags 标志位是否有 final 修饰不同之外,其他所有的JVM指令,都是一样的(对于方法本身,以及调用者本身的字节码都一样)。对于JVM来说,它执行的就是字节码,如果字节码都一样的话,那对于JVM来说,它就是同一样东西的了。

有继承

无 final 修饰

package org.agoncal.sample.jmh;import java.util.Random;/**

 * Created by emacsist on 2017/6/15.

 */public abstract class StringKitAbs {

    // 生成随机数字和字母,

    public String getStringRandom(int length) {

        String val = "";

        Random random = new Random();

        // 参数length,表示生成几位随机数

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

            String charOrNum = random.nextInt(2) % 2 == 0 ? "char" : "num";

            // 输出字母还是数字

            if ("char".equalsIgnoreCase(charOrNum)) {

                // 输出是大写字母还是小写字母

                // int temp = random.nextInt(2) % 2 == 0 ? 65 : 97;

                val += (char) (random.nextInt(26) + 97);

            } else if ("num".equalsIgnoreCase(charOrNum)) {

                val += String.valueOf(random.nextInt(10));

            }

        }

        return val;

    }

}

有 final 修饰

package org.agoncal.sample.jmh;import java.util.Random;/**

 * Created by emacsist on 2017/6/15.

 */public abstract class StringKitAbsFinal {

    // 生成随机数字和字母,

    public final String getStringRandomFinal(int length) {

        String val = "";

        Random random = new Random();

        // 参数length,表示生成几位随机数

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

            String charOrNum = random.nextInt(2) % 2 == 0 ? "char" : "num";

            // 输出字母还是数字

            if ("char".equalsIgnoreCase(charOrNum)) {

                // 输出是大写字母还是小写字母

                // int temp = random.nextInt(2) % 2 == 0 ? 65 : 97;

                val += (char) (random.nextInt(26) + 97);

            } else if ("num".equalsIgnoreCase(charOrNum)) {

                val += String.valueOf(random.nextInt(10));

            }

        }

        return val;

    }

}

测试代码

写一个类来继承上面的抽象类,以此来测试在继承中 final 有否对多态中的影响

package org.agoncal.sample.jmh;/**

 * Created by emacsist on 2017/6/15.

 */public class StringKitFinal extends StringKitAbsFinal {

}

package org.agoncal.sample.jmh;/**

 * Created by emacsist on 2017/6/15.

 */public class StringKit extends StringKitAbs {

}

然后在基准测试中:

@Benchmarkpublic void benchmark() {

    new StringKit().getStringRandom(32);

}@Benchmarkpublic void benchmarkFinal() {

    new StringKitFinal().getStringRandomFinal(32);

}

测试结果

非 final 结果

# JMH version: 1.19# VM version: JDK 1.8.0_92, VM 25.92-b14# VM invoker: /srv/jdk1.8.0_92/jre/bin/java# VM options: <none># Warmup: 20 iterations, 1 s each# Measurement: 20 iterations, 1 s each# Timeout: 10 min per iteration# Threads: 1 thread, will synchronize iterations# Benchmark mode: Throughput, ops/time# Benchmark: org.agoncal.sample.jmh.Main.benchmark

中间忽略了预热及测试过程

Result "org.agoncal.sample.jmh.Main.benchmark":

  213462.677 ±(99.9%) 8670.164 ops/s [Average]

  (min, avg, max) = (135751.428, 213462.677, 264182.887), stdev = 36710.017

  CI (99.9%): [204792.513, 222132.841] (assumes normal distribution)

有 final 结果

# JMH version: 1.19# VM version: JDK 1.8.0_92, VM 25.92-b14# VM invoker: /srv/jdk1.8.0_92/jre/bin/java# VM options: <none># Warmup: 20 iterations, 1 s each# Measurement: 20 iterations, 1 s each# Timeout: 10 min per iteration# Threads: 1 thread, will synchronize iterations# Benchmark mode: Throughput, ops/time# Benchmark: org.agoncal.sample.jmh.Main.benchmarkFinal

中间忽略了预热及测试过程

Result "org.agoncal.sample.jmh.Main.benchmarkFinal":

  213684.585 ±(99.9%) 8571.512 ops/s [Average]

  (min, avg, max) = (133472.162, 213684.585, 267742.236), stdev = 36292.318

  CI (99.9%): [205113.073, 222256.097] (assumes normal distribution)

总对比

# Run complete. Total time: 00:13:35Benchmark             Mode  Cnt       Score      Error  Units

Main.benchmark       thrpt  200  213462.677 ± 8670.164  ops/s

Main.benchmarkFinal  thrpt  200  213684.585 ± 8571.512  ops/s

它们字节码的区别

[12:12:19] emacsist:classes $ diff /tmp/StringKit.log /tmp/StringKitFinal.log1,5c1,5

< Classfile /Users/emacsist/Documents/idea/logging/target/classes/org/agoncal/sample/jmh/StringKit.class

<   Last modified 2017-6-16; size 317 bytes

<   MD5 checksum 7f9b024adc7f39345215e3e8490cafe4

<   Compiled from "StringKit.java"

< public class org.agoncal.sample.jmh.StringKit extends org.agoncal.sample.jmh.StringKitAbs

---

> Classfile /Users/emacsist/Documents/idea/logging/target/classes/org/agoncal/sample/jmh/StringKitFinal.class

>   Last modified 2017-6-16; size 337 bytes

>   MD5 checksum f54eadc79a90675d97e95f766ef88a87

>   Compiled from "StringKitFinal.java"

> public class org.agoncal.sample.jmh.StringKitFinal extends org.agoncal.sample.jmh.StringKitAbsFinal10,12c10,12

<    #1 = Methodref          #3.#13         // org/agoncal/sample/jmh/StringKitAbs."<init>":()V

<    #2 = Class              #14            // org/agoncal/sample/jmh/StringKit

<    #3 = Class              #15            // org/agoncal/sample/jmh/StringKitAbs

---

>    #1 = Methodref          #3.#13         // org/agoncal/sample/jmh/StringKitAbsFinal."<init>":()V

>    #2 = Class              #14            // org/agoncal/sample/jmh/StringKitFinal

>    #3 = Class              #15            // org/agoncal/sample/jmh/StringKitAbsFinal19c19

<   #10 = Utf8               Lorg/agoncal/sample/jmh/StringKit;

---

>   #10 = Utf8               Lorg/agoncal/sample/jmh/StringKitFinal;21c21

<   #12 = Utf8               StringKit.java

---

>   #12 = Utf8               StringKitFinal.java23,24c23,24

<   #14 = Utf8               org/agoncal/sample/jmh/StringKit

<   #15 = Utf8               org/agoncal/sample/jmh/StringKitAbs

---

>   #14 = Utf8               org/agoncal/sample/jmh/StringKitFinal

>   #15 = Utf8               org/agoncal/sample/jmh/StringKitAbsFinal26c26

<   public org.agoncal.sample.jmh.StringKit();

---

>   public org.agoncal.sample.jmh.StringKitFinal();32c32

<          1: invokespecial #1                  // Method org/agoncal/sample/jmh/StringKitAbs."<init>":()V

---

>          1: invokespecial #1                  // Method org/agoncal/sample/jmh/StringKitAbsFinal."<init>":()V38c38

<             0       5     0  this   Lorg/agoncal/sample/jmh/StringKit;

---

>             0       5     0  this   Lorg/agoncal/sample/jmh/StringKitFinal;40c40

< SourceFile: "StringKit.java"

---

> SourceFile: "StringKitFinal.java"

可以看到,除了它们的方法签名和方法名字不同之外其他的都是一样的,包括JVM调用指令也完全是一样的。

总结

可以看到它们几乎是一样的。

总结

基于上面的基准测试结论,我认为滥用或刻意为了所谓的提升性能,而去为每一个方法尽可能添加 final 的关键字是不可取的。使用 final ,更多的应该是根据Java对 final 的语义来定义,而不是只想着为了提升性能(而且这影响可以忽略不计)而刻意用 final.

使用 final 的情况:

final 变量: 表示只读(只初始化一次,但可多次读取)
final 方法:表示子类不可以重写。(网上认为 final 比非 final 快,就是认为它是在编译的时候已经静态绑定了,不需要在运行时再动态绑定。这个可能以前的JVM上是正确的,但在现代的JVM上,这个可以认为没什么影响,至少我在基准测试里是这样子)
final 类: 它们不能被继承,而且final类的方法,默认也是 final 的。

关于这个 final 的性能问题,我也Google了下,发现 stackoverflow 上,也有类似的问题:stackoverflow

 

希望这篇文章可以帮助到你。总之,同学们,你想要的职坐标IT频道都能找到!

 


本文由 @小职 发布于职坐标。未经许可,禁止转载。
喜欢 | 1 不喜欢 | 0
看完这篇文章有何感觉?已经有1人表态,100%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved

208小时内训课程