关闭 x
IT技术网
    技 采 号
    ITJS.cn - 技术改变世界
    • 实用工具
    • 菜鸟教程
    IT采购网 中国存储网 科技号 CIO智库

    IT技术网

    IT采购网
    • 首页
    • 行业资讯
    • 系统运维
      • 操作系统
        • Windows
        • Linux
        • Mac OS
      • 数据库
        • MySQL
        • Oracle
        • SQL Server
      • 网站建设
    • 人工智能
    • 半导体芯片
    • 笔记本电脑
    • 智能手机
    • 智能汽车
    • 编程语言
    IT技术网 - ITJS.CN
    首页 » JAVA »Java 自动装箱性能

    Java 自动装箱性能

    2015-09-26 00:00:00 出处:ImportNew
    分享

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

    Character boxed = 'a';
    char unboxed = boxed;

    编译器自动将它转换为

    Character boxed = Character.valueOf('a');
    char unboxed = boxed.charValue();

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

    实例

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

    public class Levenshtein{
    private final Function> asList;
    
    public Levenshtein(Function> asList) {
    this.asList = asList;
    }
    
    public int distance(T a, T b) {
    // Wagner-Fischer algorithm, with two active rows
    
    List aList = asList.apply(a);
    List bList = asList.apply(b);
    
    int bSize = bList.size();
    int[] row0 = new int[bSize + 1];
    int[] row1 = new int[bSize + 1];
    
    for (int i = 0; i row0[i] = i;
    }
    
    for (int i = 0; i < bSize; ++i) {
    U ua = aList.get(i);
    row1[0] = row0[0] + 1;
    
    for (int j = 0; j < bSize; ++j) {
    U ub = bList.get(j);
    int subCost = row0[j] + (ua.equals(ub)   0 : 1);
    int delCost = row0[j + 1] + 1;
    int insCost = row1[j] + 1;
    row1[j + 1] = Math.min(subCost, Math.min(delCost, insCost));
    }
    
    int[] temp = row0;
    row0 = row1;
    row1 = temp;
    }
    
    return row0[bSize];
    }
    }

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

    public class StringAsList extends AbstractList{
    private final String str;
    
    public StringAsList(String str) {
    this.str = str;
    }
    
    @Override
    public Character get(int index) {
    return str.charAt(index); // Autoboxing! }
    
    @Override
    public int size() {
    return str.length();
    }
    }
    
    ...
    
    Levenshteinlev = new Levenshtein<>(StringAsList::new);
    lev.distance("autoboxing is fast", "autoboxing is slow"); // 4

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

    基准测试

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

    @State(Scope.Benchmark)
    public class MyBenchmark {
    private Levenshtein lev = new Levenshtein<>(StringAsList::new);
    
    @Benchmark
    @BenchmarkMode(Mode.AverageTime)
    @OutputTimeUnit(TimeUnit.NANOSECONDS)
    public int timeLevenshtein() {
    return lev.distance("autoboxing is fast", "autoboxing is slow");
    }
    }

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

    以下是结果:

    $ java -jar target/benchmarks.jar -f 1 -wi 8 -i 8
    # JMH 1.10.2 (released 3 days ago)
    # VM invoker: /usr/lib/jvm/java-8-openjdk/jre/bin/java
    # VM options:
    # Warmup: 8 iterations, 1 s each
    # Measurement: 8 iterations, 1 s each
    # Timeout: 10 min per iteration
    # Threads: 1 thread, 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: 1 of 1
    # Warmup Iteration 1: 1517.495 ns/op
    # Warmup Iteration 2: 1503.096 ns/op
    # Warmup Iteration 3: 1402.069 ns/op
    # Warmup Iteration 4: 1480.584 ns/op
    # Warmup Iteration 5: 1385.345 ns/op
    # Warmup Iteration 6: 1474.657 ns/op
    # Warmup Iteration 7: 1436.749 ns/op
    # Warmup Iteration 8: 1463.526 ns/op
    Iteration 1: 1446.033 ns/op
    Iteration 2: 1420.199 ns/op
    Iteration 3: 1383.017 ns/op
    Iteration 4: 1443.775 ns/op
    Iteration 5: 1393.142 ns/op
    Iteration 6: 1393.313 ns/op
    Iteration 7: 1459.974 ns/op
    Iteration 8: 1456.233 ns/op
    
    Result "timeLevenshtein":
    1424.461 ±(99.9%) 59.574 ns/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 8 1424.461 ± 59.574 ns/op

    分析

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

    $ java -jar target/benchmarks.jar -f 1 -wi 8 -i 8 -prof perfasm
    ...
    cmp $0x7f,%eax
    jg 0x00007fde989a6148 ;*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
    jae 0x00007fde989a6103 ;*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()的取值来源:

    private static class CharacterCache {
    private CharacterCache(){}
    
    static final Character cache[] = new Character[127 + 1];
    
    static {
    for (int i = 0; i < cache.length; i++)
    cache[i] = new Character((char)i);
    }
    }
    
    public static Character valueOf(char c) {
    if (c return CharacterCache.cache[(int)c];
    }
    return new Character(c);
    }

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

    解决方法

    解决方法很简单:

    @ @ -11,7 +11,7 @ @ public class StringAsList extends AbstractList {
    
    @Override
    public Character get(int index) {
    - return str.charAt(index); // Autoboxing!
    + return new Character(str.charAt(index));
    }
    
    @Override

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

    private final char value;
    
    public Character(char value) {
    this.value = value;
    }
    
    public char charValue() {
    return value;
    }

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

    $ 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 8 1221.151 ± 58.878 ns/op

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

    movzwl 0x10(%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
    je 0x00007faa8d404792 ;*if_icmpne
    ; - java.lang.Character::equals@18 (line 4621)
    ; - com.tavianator.boxperf.Levenshtein::distance@137 (line 33)

    总结

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

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

    上一篇返回首页 下一篇

    声明: 此文观点不代表本站立场;转载务必保留本文链接;版权疑问请联系我们。

    别人在看

    帝国CMS7.5编辑器上传图片取消宽高的三种方法

    帝国cms如何自动生成缩略图的实现方法

    Windows 12即将到来,将彻底改变人机交互

    帝国CMS 7.5忘记登陆账号密码怎么办?可以phpmyadmin中重置管理员密码

    帝国CMS 7.5 后台编辑器换行,修改回车键br换行为p标签

    Windows 11 版本与 Windows 10比较,新功能一览

    Windows 11激活产品密钥收集及专业版激活方法

    如何从 Windows 11 中完全删除/卸载 OneNote?无解!

    抖音安全与信任开放日:揭秘推荐算法,告别单一标签依赖

    ultraedit编辑器打开文件时,总是提示是否转换为DOS格式,如何关闭?

    IT头条

    华为Pura80系列新机预热,余承东力赞其复杂光线下的视频拍摄实力

    01:28

    阿里千问3开源首战告捷:全球下载破千万,国产AI模型崛起新高度!

    01:22

    DeepSeek R1小版本试升级:网友实测编程能力已达到国际一线水平

    23:15

    NVIDIA 与 Dell 合作,大规模交付 Blackwell AI 系统

    20:52

    Cerebras 以最快的 Llama 4 Maverick 性能引领 LLM 推理竞赛

    20:51

    技术热点

    PHP中的随机性——你觉得自己幸运吗?

    搞定Ubuntu Linux下WPA无线上网

    Java使用内存映射实现大文件的上传

    MySQL安全性指南

    MySQL两项性能的基本测试浅谈

    教您使用UniqueIdentifier选取SQL Server主键

      友情链接:
    • IT采购网
    • 科技号
    • 中国存储网
    • 存储网
    • 半导体联盟
    • 医疗软件网
    • 软件中国
    • ITbrand
    • 采购中国
    • CIO智库
    • 考研题库
    • 法务网
    • AI工具网
    • 电子芯片网
    • 安全库
    • 隐私保护
    • 版权申明
    • 联系我们
    IT技术网 版权所有 © 2020-2025,京ICP备14047533号-20,Power by OK设计网

    在上方输入关键词后,回车键 开始搜索。Esc键 取消该搜索窗口。