当前位置: 首页 > 科技观察

Java自动装箱性能

时间:2023-03-14 23:07:14 科技观察

Java的原始数据类型(int、double、char)不是对象。但是由于很多Java代码都需要和对象(Object)打交道,所以Java为所有的基本类型提供了包装类(Integer、Double、Character)。使用自动装箱,您可以编写类似Characterboxed='a';charunboxed=boxed;的代码。编译器自动将其转换为Characterboxed=Character.valueOf('a');charunboxed=boxed.charValue();然而,Java虚拟机并不总是理解这种类型的过程,因此避免不必要的装箱对于良好的系统性能至关重要。这就是OptionalInt和IntStream等特殊类型存在的原因。在这篇文章中,我将概述JVM难以消除自动装箱的一个原因。例如,我们要计算任意类型数据的编辑距离(Levenshteindistance),只要数据可以看做是一个序列即可:publicclassLevenshtein{privatefinalFunction>asList;publicLevenshtein(Function>asList){this.asList=asList;}publicintdistance(Ta,Tb){//Wagner-Fischer算法,有两个activerowsListaList=asList.apply(a);ListbList=asList.apply(b);intbSize=bList.size();int[]row0=newint[bSize+1];int[]row1=newint[bSize+1];for(inti=0;irow0[i]=i;}for(inti=0;i(StringAsList::new);lev.distance("autoboxingisfast","autoboxingisslow");//4由于Java泛型的实现方法不能有List类型,所以必须提供List和装箱操作。(注:在Java10中,可能会解除这个限制。)为了在代码热路径(hotpath)上查看结果,JMH集成了Linux工具perf,可以查看最热代码块的JIT编译结果.(查看汇编代码需要安装hsdis插件,我在AUR上提供了下载,Arch用户可以直接获取。)在JMH命令行添加-profperfasm命令,可以看到结果:为了测试distance()方法的性能,需要进行基准测试。Java中微基准测试的准确性很难保证,幸好OpenJDK提供了JMH(JavaMicrobenchmarkHarness),可以帮助我们解决大部分问题。如果有兴趣,我建议阅读文档和示例;它会让你着迷。这是基准:@State(Scope.Benchmark)publicclassMyBenchmark{privateLevenshteinlev=newLevenshtein<>(StringAsList::new);@Benchmark@BenchmarkMode(Mode.AverageTime)@OutputTimeUnit(TimeUnit.NANOSECONDS)publicinttimeLevenshtein(){returnlev.distance("autoboxingisfast","autoboxingisslow");}}(返回方法的结果,让JMH做一些操作让系统认为返回值会被使用,防止冗余代码剔除影响结果。)以下是结果:$java-jartarget/benchmarks.jar-f1-wi8-i8#JMH1.10.2(released3daysago)#VMinvoker:/usr/lib/jvm/java-8-openjdk/jre/bin/java#VMoptions:#Warmup:8iterations,1seach#Measurement:8iterations,1seach#Timeout:10minperiteration#Threads:1thread,willsynchronizeiterations#Benchmarkmode:Averagetime,time/op#Benchmark:com.tavianator.boxperf.MyBenchmark.timeLevenshtein#Runprogress:0.00%完成,ETA00:00:16#Fork:1of1#WarmupIteration1:1517.495ns/op#WarmupIteration2:1503.096ns/op#WarmupIteration3:1402.069ns/op#WarmupIteration4:1480.584ns/op#WarmupIteration5:1385.345ns/op#WarmupIteration6:57ns/op.#WarmupIteration7:1436.749ns/op#WarmupIteration8:1463.526ns/opIteration1:1446.033ns/opIteration2:1420.199ns/opIteration3:1383.017ns/opIteration4:1443.775ns/opIteration5:1393.142ns/opIteration6:1393.313ns/opIteration7:1459.974ns/opIteration8:1456.233ns/opResult"timeLevenshtein":1424.461±(99.9%)59.574ns/op[平均](min,avg,max)=(1383.017,1424.461,1459.974),stdev=31.158CI(99.9%):[1364.887,1484.034](assumesnormaldistribution)#Runcomplete.Totaltime:00:00:16BenchmarkModeCntScoreErrorUnitsMyBenchmark.timeLevenshteinavgthot81424.461±59.574ns/op分析为了查看代码的热路径于是JMH集成了linux工具perf,可以查看最热代码块的JIT编译结果(查看汇编代码需要安装hsdis插件,我在AUR上提供了下载,Arch上提供了下载)用户可直接获取。)在JMH命令中添加-profperfasm命令查看结果:$java-jartarget/benchmarks.jar-f1-wi8-i8-profperfasm...cmp$0x7f,%eaxjg0x00007fde989a6148;*if_icmpgt;-java.lang.Character::valueOf@3(line4570);-com.tavianator.boxperf.StringAsList::get@8(line14);-com.tavianator.boxperf.StringAsList::get@2;(line5);-com.tavianator.boxperf.Levenshtein::distance@121(line32)cmp$0x80,%eaxjae0x00007fde989a6103;*aaload;-java.lang.Character::valueOf@10(line4571);-com.tavianator.boxperf.StringAsList::得到@8(第14行);-com.tavianator.boxperf.StringAsList::get@2(line5);-com.tavianator.boxperf.Levenshtein::distance@121(line32)...输出内容很多,但是上面的内容说明没有装箱优化。为什么要和0x7f/0x80的内容比较呢?原因是Character.valueOf()值的来源:privatestaticclassCharacterCache{privateCharacterCache(){}staticfinalCharactercache[]=newCharacter[127+1];static{for(inti=0;i