优化 Java 中的多态代码

来源:ImportNew - 进林

优化Java中的多态代码

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百万整数需要的纳秒时间:

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

转载声明:本文转载自「ImportNew」

原文发布于微信公众号 - 精讲JAVA(toooooooozi)

原文发表时间:2018-04-12

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏進无尽的文章

设计模式| 行为型模式 (上)

行为型模式共十一种:策略模式、模板方法模式、观察者模式、迭代器模式、解释器模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式。 分两篇文...

1152
来自专栏C语言及其他语言

[每日一题]C语言程序设计教程(第三版)课后习题5.4

题目描述 有三个整数a b c,由键盘输入,输出其中的最大的数。 输入 一行数组,分别为a b c 输出 a b c其中最大的数 样例输入 10 20 30 样...

2874
来自专栏C语言及其他语言

[编程经验]C语言free释放内存后为什么指针里的值不变?竟然还可以输出?

今天你家范儿给大家带来一个的东西——关于C语言为什么释放指针后,指向这块内存的指针的值不变问题的编程经验!!行了,咱们话不多少,直接上主食。 ...

3798
来自专栏吴伟祥

正则表达式在线测试&&生成代码 转

例如,您可能需要搜索整个网站,删除过时的材料,以及替换某些 HTML 格式标记。在这种情况下,可以使用正则表达式来确定在每个文件中是否出现该材料或该 HTML...

844
来自专栏喔家ArchiSelf

一个函数的自白

我是——编程世界的函数,不是数学中的幂,指,对和三角函数等等,但是和f(x)又有着千丝万缕的关系。

955
来自专栏Android知识点总结

去吧!设计模式之模板方法模式

682
来自专栏java一日一条

java提高篇之异常(下)

Java确实给我们提供了非常多的异常,但是异常体系是不可能预见所有的希望加以报告的错误,所以Java允许我们自定义异常来表现程序中可能会遇到的特定问题,总之就是...

743
来自专栏WindCoder

Java设计模式学习笔记—桥接模式

文章最后“Java设计模式笔记示例代码整合”为本系列代码整合,所有代码均为个人手打并运行测试,不定期更新。本节内容位于其Bridge包(package)中。

761
来自专栏嵌入式程序猿

那些你应该记住的字符串函数

在我们移植的基于freeRTOS的webserver源码里,有很多的字符串处理函数,相信仔细研读过的人应该都注意到,那么你对这些字符处理函数都清楚吗?今天我们就...

2805
来自专栏Java架构师学习

十年Java”老兵“浅谈源码的七大设计模式

一个专业的程序员,总是把代码的清晰性,兼容性,可移植性放在很重要的位置。他们总是通过定义大量的宏,来增强代码的清晰度和可读性,而又不增加编译后的代码长度和代码...

36312

扫码关注云+社区