>>分享Java编程技术,对《Java面向对象编程》等书籍提供技术支持 书籍支持  卫琴直播  品书摘要  在线测试  资源下载  联系我们
发表一个新主题 开启一个新投票 回复文章 您是本文章第 25432 个阅读者 刷新本主题
 * 贴子主题:  优化Java中的多态代码 回复文章 点赞(0)  收藏  
作者:sunshine    发表时间:2016-10-30 20:09:03     消息  查看  搜索  好友  邮件  复制  引用

原文链接: lemire 翻译: ImportNew.com - 进林
译文链接: http://www.importnew.com/14393.html

Oracle的Java是一个门快速的语言,有时候它可以和C++一样快。编写Java代码时,我们通常使用接口、继承或者包装类(wrapper class)来实现多态,使软件更加灵活。不幸的是,多态会引入更多的调用,让Java的性能变得糟糕。部分问题是,Java不建议使用完全的内联代码,即使它是非常安全的。(这个问题可能会在最新的Java版本里得到缓解,请看文章后面的更新部分)

考虑下这种情况,我们要用接口抽象出一个整型数组:

public interface Array {
    public int get(int i);
    public void set(int i, int x);
    public int size();
}

你为什么要这样做?可能是因为你的数据是保存在数据库里、网络上、磁盘上或者在其他的数据结构里。你想一次编码后就不用关心数组的具体实现。

编写一个与标准Java数组一样高效率的类并不难,不同之处在于它实现了这个接口:

public final class NaiveArray implements Array {
    protected int[] array;

    public NaiveArray(int cap) {
        array = new int[cap];
    }

    public int get(int i) {
        return array[i];
    }

    public void set(int i, int x) {
        array[i] = x;
    }

    public int size() {
        return array.length;
    }
}

至少在理论上,NaiveArray类不会出现任何的性能问题。这个类是final的,所有的方法都很简短。

不幸的是,在一个简单的benchmark类里,当使用NavieArray作为数组实例时,你会发现NavieArray比标准数组慢5倍以上。就像这个例子:

public int compute() {
   for(int k = 0; k < array.size(); ++k)
      array.set(k,k);
   int sum = 0;
   for(int k = 0; k < array.size(); ++k)
      sum += array.get(k);
   return sum;
}

你可以通过使用NavieArray作为NavieArray的一个实例来稍微减缓性能问题(避免使用多态)。不幸的是,它依然会慢3倍多。而你仅是放弃了多态的好处。

那么,强制使用内联函数调用会怎样?

一个可行的解决方法是手动实现内联函数。你可以使用 instanceof 关键字来提供优化实现,否则你只会得到一个普通(更慢)的实现。例如,如果你使用下面的代码,NavieArray就会变得和标准数组一样快:

public int compute() {
     if(array instanceof NaiveArray) {
        int[] back = ((NaiveArray) array).array;
        for(int k = 0; k < back.length; ++k)
           back[k] = k;
        int sum = 0;
        for(int k = 0; k < back.length; ++k)
           sum += back[k];
        return sum;
     }
     //...
}

当然,我也会介绍一个维护问题作为需要实现不止一次的同类算法…… 当出现性能问题时,这是一个可接受的替代。

和往常一样,我的benchmarking代码可以在网上获取到。

总结

一些Java版本可能不完全支持频繁的内联函数调用,即使它可以并且应该支持。这会造成严重的性能问题。
把类声明为 final 看起来不会缓解性能问题。
对于消耗大的函数,可行的解决方法是自己手动优化多态和实现内联函数调用。使用 instanceof 关键字,你可以为一些特定的类编写代码并且(因此)保留多态的灵活性。

更新

Erich Schubert使用 double 数组运行简单的benchmark类发现他的运行结果与我的结果相矛盾,而且我们的变量实现都是一样的。我通过更新到最新版本的OpenJDK证明了他的结果。下面的表格给出了处理10百万整数需要的纳秒时间:

Function      Oracle JDK 8u11 OpenJDK 1.8.0_40 OpenJDK 1.7.0_65
straight arrays 0.92    0.71               0.87
with interface 5.9    0.70               6.3
with manual inlining 0.98    0.71               0.93

正如我们看到的,最新版本的OpenJDK十分智能,并且消除了多态的性能开销(1.8.0_40)。如果你足够幸运地在使用这个JDK,你不需要担心这 篇文章所说的性能问题。但是,这个总体思想依然值得应用在更复杂的场景里。例如,JDK优化可能依然达不到你期待的性能要求。




程序猿的技术大观园:www.javathinker.net
  Java面向对象编程-->输入与输出(下)
  JavaWeb开发-->JSP中使用JavaBean(Ⅱ)
  JSP与Hibernate开发-->使用JPA和注解
  Java网络编程-->Socket用法详解
  精通Spring-->绑定表单
  Vue3开发-->通过Axios访问服务器
  java的三种随机数生成方式
  Java设计模式: 里氏替换原则和合成复用原则详解
  18 张图弄懂面试官必问的一致性哈希
  Redis安装、Redis基本数据类型、Jedis、Redis集群搭建
  如何优雅地打印一个Java对象?
  超详细的Java运算符修炼手册(优秀程序员不得不知道的运算技...
  java NIO示例以及流程详解
  Eclipse使用指南:Debug 配置
  常用的正则表达式汇总
  Java入门实用代码:将文件内容复制到另一个文件
  史上最全正则表达式合集(马上收藏)
  判断一个字符是否是汉字
  Java入门实用代码:删除一个文件目录
  Java中用动态代理实现标准的DataSource数据源连接池
  java零基础入门-面向对象篇 抽象类
  更多...
 IPIP: 已设置保密
楼主      
1页 0条记录 当前第1
发表一个新主题 开启一个新投票 回复文章


中文版权所有: JavaThinker技术网站 Copyright 2016-2026 沪ICP备16029593号-2
荟萃Java程序员智慧的结晶,分享交流Java前沿技术。  联系我们
如有技术文章涉及侵权,请与本站管理员联系。