Java 的基本数据类型(int、double、 char)都不是对象。但由于很多Java代码需要处理的是对象(Object),Java给所有基本类型提供了包装类(Integer、Double、Character)。有了自动装箱,你可以写如下的代码

1
2
Character boxed = 'a';
charunboxed = boxed;

编译器自动将它转换为

1
2
Character boxed = Character.valueOf('a');
charunboxed = boxed.charValue();

然而,Java虚拟机不是每次都能理解这类过程,因此要想得到好的系统性能,避免不必要的装箱很关键。这也是 OptionalInt 和 IntStream 等特殊类型存在的原因。在这篇文章中,我将概述JVM很难消除自动装箱的一个原因。

实例

例如,我们想要计算任意一类数据的编辑距离(Levenshtein距离),只要这些数据可以被看作一个序列:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
publicclass Levenshtein{
privatefinal Function> asList;
publicLevenshtein(Function> asList) {
this.asList = asList;
}
publicint distance(T a, T b) {
// Wagner-Fischer algorithm, with two active rows
List aList = asList.apply(a);
List bList = asList.apply(b);
intbSize = bList.size();
int[] row0 = newint[bSize + 1];
int[] row1 = newint[bSize + 1];
for(inti = 0; i row0[i] = i;
}
for(inti = 0; i < bSize; ++i) {
U ua = aList.get(i);
row1[0] = row0[0] + 1;
for(intj = 0; j < bSize; ++j) {
U ub = bList.get(j);
intsubCost = row0[j] + (ua.equals(ub) ? 0: 1);
intdelCost = row0[j + 1] + 1;
intinsCost = row1[j] + 1;
row1[j + 1] = Math.min(subCost, Math.min(delCost, insCost));
}
int[] temp = row0;
row0 = row1;
row1 = temp;
}
returnrow0[bSize];
}
}

只要两个对象可以被看作List,这个类就可以计算它们的编辑距离。如果想计算String类型的距离,那么就需要把String转变为List类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
publicclass StringAsList extendsAbstractList{
privatefinal String str;
publicStringAsList(String str) {
this.str = str;
}
@Override
publicCharacter get(intindex) {
returnstr.charAt(index); // Autoboxing! }
@Override
publicint size() {
returnstr.length();
}
}
...
Levenshteinlev = newLevenshtein<>(StringAsList::new);
lev.distance("autoboxing is fast","autoboxing is slow");// 4

由于Java泛型的实现方式,不能有List类型,所以要提供List和装箱操作。(注:Java10中,这个限制也许会被取消。)

基准测试

为了测试 distance() 方法的性能,需要做基准测试。Java中微基准测试很难保证准确,但幸好OpenJDK提供了JMH(Java Microbenchmark Harness),它可以帮我们解决大部分难题。如果感兴趣的话,推荐大家阅读文档和实例;它会很吸引你。以下是基准测试:

1
2
3
4
5
6
7
8
9
10
11
@State(Scope.Benchmark)
publicclass MyBenchmark {
privateLevenshtein lev = newLevenshtein<>(StringAsList::new);
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
publicint timeLevenshtein() {
returnlev.distance("autoboxing is fast","autoboxing is slow");
}
}

(返回方法的结果,这样JMH就可以做一些操作让系统认为返回值会被使用到,防止冗余代码消除影响了结果。)

以下是结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
$ java -jar target/benchmarks.jar -f 1-wi 8-i 8
# JMH 1.10.2(released 3days ago)
# VM invoker: /usr/lib/jvm/java-8-openjdk/jre/bin/java
# VM options:
# Warmup: 8iterations, 1s each
# Measurement: 8iterations, 1s each
# Timeout: 10min per iteration
# Threads: 1thread, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: com.tavianator.boxperf.MyBenchmark.timeLevenshtein
# Run progress: 0.00% complete, ETA 00:00:16
# Fork: 1of 1
# Warmup Iteration 1:1517.495ns/op
# Warmup Iteration 2:1503.096ns/op
# Warmup Iteration 3:1402.069ns/op
# Warmup Iteration 4:1480.584ns/op
# Warmup Iteration 5:1385.345ns/op
# Warmup Iteration 6:1474.657ns/op
# Warmup Iteration 7:1436.749ns/op
# Warmup Iteration 8:1463.526ns/op
Iteration1:1446.033ns/op
Iteration2:1420.199ns/op
Iteration3:1383.017ns/op
Iteration4:1443.775ns/op
Iteration5:1393.142ns/op
Iteration6:1393.313ns/op
Iteration7:1459.974ns/op
Iteration8:1456.233ns/op
Result"timeLevenshtein":
1424.461±(99.9%)59.574ns/op [Average]
(min, avg, max) = (1383.017,1424.461,1459.974), stdev = 31.158
CI (99.9%): [1364.887,1484.034] (assumes normal distribution)
# Run complete. Total time: 00:00:16
Benchmark Mode Cnt Score Error Units
MyBenchmark.timeLevenshtein avgt 81424.461 ± 59.574ns/op

分析

为了查看代码热路径(hot path)上的结果,JMH集成了Linux工具perf,可以查看最热代码块的JIT编译结果。(要想查看汇编代码,需要安装hsdis插件。我在AUR上提供了下载,Arch用户可以直接获取。)在JMH命令行添加 -prof perfasm 命令,就可以看到结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ java -jar target/benchmarks.jar -f 1-wi 8-i 8-prof perfasm
...
cmp $0x7f,%eax
jg0x00007fde989a6148;*if_icmpgt
; - java.lang.Character::valueOf@3(line 4570)
; - com.tavianator.boxperf.StringAsList::get@8(line 14)
; - com.tavianator.boxperf.StringAsList::get@2; (line 5)
; - com.tavianator.boxperf.Levenshtein::distance@121(line 32)
cmp $0x80,%eax
jae0x00007fde989a6103;*aaload
; - java.lang.Character::valueOf @ 10(line 4571)
; - com.tavianator.boxperf.StringAsList::get@8(line 14)
; - com.tavianator.boxperf.StringAsList::get @ 2(line 5)
; - com.tavianator.boxperf.Levenshtein::distance@121(line 32)
...

输出内容很多,但上面的一点内容就说明装箱没有被优化。为什么要和0x7f/0×80的内容做比较呢?原因在于Character.valueOf()的取值来源:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
privatestatic class CharacterCache {
privateCharacterCache(){}
staticfinal Character cache[] = newCharacter[127+ 1];
static{
for(inti = 0; i < cache.length; i++)
cache[i] = newCharacter((char)i);
}
}
publicstatic Character valueOf(charc) {
if(c returnCharacterCache.cache[(int)c];
}
returnnew Character(c);
}

可以看出,Java语法标准规定前127个char的Character对象放在缓冲池中,Character.valueOf()的结果在其中时,直接返回缓冲池的对象。这样做的目的是减少内存分配和垃圾回收,但在我看来这是过早的优化。而且它妨碍了其他优化。JVM无法确定 Character.valueOf(c).charValue() == c,因为它不知道缓冲池的内容。所以JVM从缓冲池中取了一个Character对象并读取它的值,结果得到的就是和 c 一样的内容。

解决方法

解决方法很简单:

1
2
3
4
5
6
7
8
9
@ @ -11,7+11,7@ @ publicclass StringAsList extendsAbstractList {
@Override
publicCharacter get(intindex) {
-returnstr.charAt(index); // Autoboxing!
+returnnew Character(str.charAt(index));
}
@Override

用显式的装箱代替自动装箱,就避免了调用Character.valueOf(),这样JVM就很容易理解代码:

1
2
3
4
5
6
7
8
9
privatefinal char value;
publicCharacter(charvalue) {
this.value = value;
}
publicchar charValue() {
returnvalue;
}

虽然代码中加了一个内存分配,但JVM能理解代码的意义,会直接从String中获取char字符。性能提升很明显:

1
2
3
4
5
6
$ java -jar target/benchmarks.jar -f 1-wi 8-i 8
...
# Run complete. Total time: 00:00:16
Benchmark Mode Cnt Score Error Units
MyBenchmark.timeLevenshtein avgt 81221.151 ± 58.878ns/op

速度提升了14%。用 -prof perfasm 命令可以显示,改进以后是直接从String中拿到char值并在寄存器中比较的:

1
2
3
4
5
6
7
8
9
movzwl0x10(%rsi,%rdx,2),%r11d ;*caload
; - java.lang.String::charAt@27(line 648)
; - com.tavianator.boxperf.StringAsList::get@9(line 14)
; - com.tavianator.boxperf.StringAsList::get @ 2(line 5)
; - com.tavianator.boxperf.Levenshtein::distance@121(line 32)
cmp %r11d,%r10d
je0x00007faa8d404792;*if_icmpne
; - java.lang.Character::equals@18(line 4621)
; - com.tavianator.boxperf.Levenshtein::distance@137(line 33)

总结

装箱是HotSpot的一个弱项,希望它能做到越来越好。它应该多利用装箱类型的语义,消除装箱操作,这样以上的解决办法就没有必要了。

以上的基准测试代码都可以在GitHub上访问。

原文链接:  tavianator  翻译:  ImportNew.com  -  rainsbaby
译文链接:  http://www.importnew.com/16737.html

Java 自动装箱性能相关推荐

  1. java自动装箱性能

    2019独角兽企业重金招聘Python工程师标准>>> Java 的基本数据类型(int.double. char)都不是对象.但由于很多Java代码需要处理的是对象(Object) ...

  2. java 自动装箱自动拆箱,java自动装箱、自动拆箱和正常情况性能比较

    自动装箱和自动拆箱是java5.0版本引入的,能自动将基本类型转换为对应的基本类型包装对象,那么我们比较一下他们的性能情况. package com.wmmad.test; import junit. ...

  3. Java自动拆装箱面试_跟王老师学泛型(二):Java自动装箱与拆箱

    Java 自动装箱与拆箱(Autoboxing and unboxing) 主讲教师:王少华 QQ群:483773664 学习目标: 掌握Java 基本数据对应的包装类 掌握Java 自动装箱与拆箱 ...

  4. 【转】java 自动装箱与拆箱

    java 自动装箱与拆箱 这个是jdk1.5以后才引入的新的内容,作为秉承发表是最好的记忆,毅然决定还是用一篇博客来代替我的记忆: java语言规范中说道:在许多情况下包装与解包装是由编译器自行完成的 ...

  5. Java 自动装箱与拆箱

    Java 自动装箱与拆箱 装箱就是自动将基本数据类型转换为包装器类型(int–>Integer):调用方法:Integer 的 valueOf(int) 方法 拆箱就是自动将包装器类型转换为基本 ...

  6. java 自动装箱自动拆箱

    1.Java数据类型 在介绍Java的自动装箱和拆箱之前,我们先来了解一下Java的基本数据类型. 在Java中,数据类型可以分为两大种,Primitive Type(基本类型)和Reference ...

  7. java自动装箱拆箱原理

    java自动装箱拆箱原理 看了很多博主都没写原理,只是浅显地说了自动装箱拆箱的含义,我就把这个必须知道的知识写一下吧 1.自动装箱 以int -> Integer为例,Integer integ ...

  8. java自动装箱和自动拆箱

    Java自动装箱和自动拆箱是Java语言的一颗语法糖. 下面说一下java的自动装箱和拆箱的例子 1. 深入理解java虚拟机上的一个例子 public static void testJvmBook ...

  9. java的自动装箱_详解Java 自动装箱与拆箱的实现原理

    详解Java 自动装箱与拆箱的实现原理 发布于 2020-7-4| 复制链接 本篇文章主要介绍了详解Java 自动装箱与拆箱的实现原理,小妖觉得挺不错的,现在分享给大家,也给大家做个参考.一起跟随小妖 ...

最新文章

  1. Generator-ing Values
  2. html 访问 php 变量
  3. python只能使用内置数据库_隐藏彩蛋:你知道python有一个内置的数据库吗?
  4. 在Win7中怎样打开摄像头?
  5. 关键字restrict简介
  6. java 格式化日期(DateFormat)
  7. ES6——函数参数默认值
  8. dump排查内存异常 java_如何排查Java应用内存泄漏问题
  9. matlab求解集合覆盖问题,Set Cover Problem (集合覆盖问题)
  10. 【UWA 学堂】部分渲染课程的调价通知
  11. 拍一拍身上的土(2009-12-12 01:17:43韩寒博客)
  12. 2018年第九届蓝桥杯真题C/C++B组
  13. AI智能语音系统源码搭建
  14. 基于spring cloud + nacos + gateway + ssm+的学生管理系统
  15. 计算机 高级软件工程师考试试题,高级软件工程师8月考试试题A卷带答案.doc
  16. 灰度测试或 AB 测试
  17. tp-link与台式计算机连接教程,台式电脑连接到无线路由器上网教程
  18. 零基础爬取堆糖网图片(一)
  19. MyBatis-Plus DQL与其他知识点
  20. 实验十三:PCF8591模数传感器-数模转换实验

热门文章

  1. 支付系统路由系统设计
  2. 独家 | 大数据与AI技术在金融科技的应用
  3. oracle19c怎么创建Scott,Oracle db-sample-schema-19c安装(scott hr oe pm ix sh bi用户创建部署)...
  4. MySQL - 无索引行锁升级为表锁
  5. RocketMQ-初体验RocketMQ(06)-使用API操作RocketMQ ,理解RocketMQ的存储结构
  6. java gc种类_Java GC系列(3):垃圾回收器种类
  7. beautifulsoup解析动态页面div未展开_两个资讯爬虫解析库的用法与对比
  8. Java中的nextInt()和next()与nextLine()区别详解
  9. 坡道行驶电动小车_事发红绿灯路口!东莞一女子骑电动滑板车被撞致颅内出血…...
  10. 计算机科学与技术的深度研究,专业深度分析--计算机科学与技术.docx