免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 3234 | 回复: 4

为JAVA性能而设计(1(转贴) [复制链接]

论坛徽章:
0
发表于 2002-11-25 16:41 |显示全部楼层
为JAVA性能而设计(1)    ggzzkk(转贴)
  
关键字     java 性能
  
出处     http://www.smth.edu.cn/
  

发信人: SuperMMX (笑天子*不再喝可乐)
发信站: BBS 水木清华站

From http://SuperMMX.dhs.org/forum
原文请到此站查看.

          为性能而设计, 第一部分: 接口事宜

From Java World.

在设计 Java 类的时候避免性能上的冒险

概要

许多通常的 Java 性能问题都起源于在设计过程早期中的类设计的思想, 早在许多开发者
开始考虑性能问题之前. 在这个系列中, Brian Goetz 讨论了通常的 Java 性能上的冒险
以及怎么在设计时候避免它们.

By Brian Goetz

翻译 by SuperMMX

许多程序员在开发周期的后期才可是考虑性能管理. 他们常常把性能优化拖延到最后, 希
望能完全避免 -- 有时候这种策略是成功的. 但是早期的设计思想可以影响性能优化的需
求及其成功. 如果性能是你的程序的一个重要指标, 那么性能管理应该从第一天起就和设
开发周期整合在一起.

这个系列探索一些早期的设计思想能够极大影响应用程序性能的方法. 在这篇文章中, 我
专注于最通常的性能问题中的一个: 临时变量的创建. 一个类的对象创建方式常常在设计
时候就确定了的 -- 但不是故意的 --, 就为后来的性能问题种下了种子.

阅读整个的 "为性能而设计" 系列:
第一部分: 接口事宜
第二部分: 减少对象创建
第三部分: 远程接口 (March 23, 2001)

性能问题有各种形式. 最容易调整的是那些你简单地为计算选择了一个错误的算法 --
就象使用使用冒泡算法来对一个大数据集进行排序, 或者在使用一个经常使用的数据项时
不是做缓冲, 而是每次都计算. 你可以使用概要分析来简单地找出这些瓶颈, 一旦找到了,
你可以很容易地改正. 但是, 许多 Java 性能问题来自一个更深的, 更难改正的源头 --
一个程序组件的接口设计.

今天大多数程序是由内部开发的或者外部买来的组件构建而成. 甚至在程序不是很大地依
于已经存在的组件时, 面向对象的设计过程也鼓励应用程序包装成组件, 这样就简化了设
计, 开发和测试过程. 这些优势是不可否认的, 你应该认识到这些组件实现的接口可能
极大地影响使用它们的程序的行为和性能.

在这一点上, 你可能要问什么样的接口和性能相关. 一个类的接口不仅定义了这个类可
以实现那些功能, 也可以定义它的对象创建行为和使用它的方法调用序列. 一个类怎样
定义它的构造函数和方法决定了一个对象是否可以重用, 它的方法是否要创建 -- 或者
要求它的客户端创建 -- 中间对象,  以及一个客户端需要调用多少方法来使用这个类.
这些因素都会影响程序的性能.

注意对象的创建

一个最基本的 Java 性能管理原则就是: 避免大量的对象创建. 这不是说你应该不创建
任何对象而放弃面向对象的好处. 但是你必须在执行性能相关的代码时, 在紧循环中注意
对象的创建. 对象的创建是如此地高代价, 以至于你应该在要求性能的情况下避免不必要
的临时或者中间对象的创建.

String 类是在那些处理文本的程序中对象创建的主要来源. 因为 String 是不可修改的,
每当一个 String 修改或创建, 就必须创建一个新的对象. 结果就是, 关注性能的程序应
该避免大量 String 的使用. 但是, 这通常是不可能的. 甚至当你从你的代码中完全除去
对 String 的依赖, 你常常会发现你自己在使用一些具有根据 String 定义的接口的组件.
所以, 你最后不得不使用 String.

例子: 正规表达式匹配

作为一个例子, 假设你写一个叫做 MailBot 的邮件服务器. MailBot 需要处理 MIME 头格
式 -- 象发送日期或者发送者的 email 地址 -- 在每个信息的顶部. 使用一个匹配正规
表达式的组件来使处理 MIME 头的过程简单一些. MailBot 足够聪明, 不为每个头的行
或者头的元素创建一个 String 对象. 相反, 它用输入的文本填充了一个字符缓冲区, 通
过对缓冲区的索引来确定要处理的头的位置. MailBot 会调用正规表达式匹配器来处理每
个头行, 所以匹配器的性能就非常重要. 我们以一个正规表达式匹配器类的拙劣的接
口作为例子:

public class AwfulRegExpMatcher {
  /** Create a matcher with the given regular expression and which will
      operate on the given input string */
  public AwfulRegExpMatcher(String regExp, String inputText)&#59;
  /** Retrieve the next match of the pattern against the input text,
      returning the matched text if possible or null if not */
  public String getNextMatch()&#59;
}

甚至在这个类实现了一个有效的正规表达式匹配的算法的时候, 任何大量使用它的程序
仍然难以忍受. 既然匹配器对象和输入的文本联系起来, 每一次你调用它, 你必须创建
一个新的匹配器对象. 既然你的目标是减少不必要的对象的创建, 那么使这个匹配器可以赜
将会是一个明显的开始.

下面的类定义演示了你的匹配器的另一个可能的接口, 允许你重用这个匹配器, 但仍然
很坏.

public class BadRegExpMatcher {
  public BadRegExpMatcher(String regExp)&#59;
  /** Attempts to match the specified regular expression against the input
      text, returning the matched text if possible or null if not */
  public String match(String inputText)&#59;
  /** Get the next match against the input text, or return null if no match */
  public String getNextMatch()&#59;
}

忽略正规表达式匹配中的精细点 -- 象返回匹配的子表达式, 这个看起来无害的类定义
会出什么问题呢? 从功能上来看, 没有. 但是从性能的角度来看, 许多. 首先, 匹配器
需要它的调用者创建一个 String 来代表要匹配的文本. MailBot 试图避免创建 String
对象, 但是当它要找到一个要做正规表达式解析的头时, 它不得不创建一个 String 来
满足 BadRegExpMatcher:

BadRegExpMatcher dateMatcher = new BadRegExpMatcher(...)&#59;
while (...) {
  ...
  String headerLine = new String(myBuffer, thisHeaderStart,
  thisHeaderEnd-thisHeaderStart)&#59;
  String result = dateMatcher.match(headerLine)&#59;
  if (result == null) { ... }
}

第二, 匹配器创建了结果字符串甚至当 MailBot 只关心是否匹配了, 不需要匹配的文本时,
这意味着要简单使用 BadRegExpMatcher 来确认一个日期头是否匹配一个特定的格式, 你
必须创建两个 String 对象 -- 匹配器的输入和匹配的结果. 两个对象可能看起来不多,
但是如果你给 MailBot 处理的每个邮件的每个头行都创建两个对象, 这会极大地影响
性能. 错误不在于 MailBot 的设计, 而在于 BadRegExpMatcher 类的设计 -- 或者使用.

注意返回一个轻量型的 Match 对象 -- 可以提供 getOffset(), getLength(),
egetMatchString() 方法 -- 而不是返回一个 String, 这不会很大提高性能. 因为创建
一个 Match 对象可能比创建一个 String 代价要小 -- 包括产生一个 char[] 数组和
复制数据, 你仍然创建了一个中间对象, 对你的调用者来说没有价值.

这已经足够坏了, BadREgExpMatcher 强迫你使用它想看到的输入形式, 而不是你可以
提供的更有效的形式. 但是使用 BadRegExpMathcer 还有另一个危险, 潜在地给 MailBot
的性能带来更大的冒险: 在处理邮件头的时候, 你开始有避免使用 String 的倾向. 但是
既然你被迫创建许多 String 对象来满足 BadRegExpMatcher, 你可能被引诱而放弃这个
目标, 更加自由地使用 String. 现在, 一个组件的糟糕的设计已经影响了使用它的程序.
甚至你后来找到了一个更好的正规表达式的组件, 不需要你提供一个 String, 那时你的
整个程序都会受影响.

一个好一些的接口

你怎样定义 BadRegExpMatcher, 而不引起这样的问题呢? 首先, BadRegExpMatcher 应该
不规定它的输入. 它应该可以接受它的调用者能够有效提供的各种输入格式. 第二, 它
不应该自动给匹配结果产生一个 String&#59; 应该返回足够的信息, 这样调用者如果愿意的
话可以生成它. (为方便着想, 它可以提供一个方法来做这件事, 但不是必须的) 这里
有一个好一些的接口:

class BetterRegExpMatcher {
  public BetterRegExpMatcher(...)&#59;
  /** Provide matchers for multiple formats of input -- String,
      character array, and subset of character array.  Return -1 if no
      match was made&#59; return offset of match start if a match was
      made.  */
  public int match(String inputText)&#59;
  public int match(char[] inputText)&#59;
  public int match(char[] inputText, int offset, int length)&#59;
  /** Get the next match against the input text, if any */
  public int getNextMatch()&#59;
  /** If a match was made, returns the length of the match&#59; between
      the offset and the length, the caller should be able to
      reconstruct the match text from the offset and length */
  public int getMatchLength()&#59;
  /** Convenience routine to get the match string, in the event the
      caller happens to wants a String */
  public String getMatchText()&#59;
}

新的接口减少了调用者把输入转换成匹配器希望的格式这个要求. MailBot 现在可以象
下面这样调用 match():

int resultOffset = dateMatcher.match(myBuffer, thisHeaderStart,
thisHeaderEnd-thisHeaderStart)&#59;
if (resultOffset < 0) { ... }

这就解决了不创建任何新对象的目标. 作为一个附加的奖励, 它的接口设计风格加到了
Java 的 &quot;lots-of-simgle-methos&quot; 设计哲学中.

额外的对象创建给性能的确切的冲击依赖于 matth() 所作的工作量. 你可以通过创建和计时
两个正规表达式匹配器类, 来确定一个性能差别的上限. 在 Sun JDK 1.3 中, 上面的代码
片段在 BetterRegExpMatcher 类中大约比 BadRegExpMatcher 类要快 50 倍左右. 使用一个
简单的字串匹配的实现, BetterRegExpMatcher 比 相对应的 BadRegExpMatcher 要快 5 ?

交换类型

BadRegExpMatcher 强迫 MailBot 把输入文本从字符数组转换成 String, 结果是造成了
一些不必要的对象的创建. 更具讽刺意味的是, BadRegExpMatcher 的许多实现都立即
把 String 转换成一个字符数组, 使它容易对输入文本进行访问. 这样不仅仅申请了另一龆
象, 并且还意味着你做完了所有的工作, 最后的形式和开始时一样. MailBot 和 BadRegExpMatcher
都不想处理 String -- String 只是看起来象是在组件之间传递文本的很明显的格式.

在上面的 BadRegExpMatcher 例子中, String 类是作为一个交换类型的. 一个交换类型
是一种不管是调用者还是被调用者都不想使用或者以它作为数据格式的一种类型, 但是
两个都能很容易地转换它或者从它转换. 以交换类型定义接口在保持灵活性的同时减少了
接口的复杂性, 但是有时简单性导致了高代价的性能.

一个交换类型最典型的例子是 JDBC ResultSet 接口. 它不可能象任何本地数据库提供的
数据集一样提供它的 ResultSet 接口, 但是 JDBC 驱动通过实现一个 ResultSet 可以很
容易地把数据库提供的本地数据表示包装起来. 同样, 客户端程序也不能象这样表示数据
记录, 但是你几乎可以没有困难地把 ResultSet 转换为想要的数据表示. 在 JDBC 的例子中,
你接受了这个层次的花费, 因为它带来了标准化和跨数据库实现的可移植性的好处. 但是,
要注意交换类型带来的性能代价.

这完全不值得, 使用交换类型对性能的冲击不容易度量. 如果你对上面调用 BadRegExpMatcher
的代码片段做测试的话, 它会在运行时创建 MailBot 的输入 String&#59; 但是, String 的产生
只用来满足 BadRegExpMatcher. 如果你想评定一个组件对程序性能的真正的冲击, 你应该不仅
仅度量它的代码的资源使用状况, 还有那些使用它和恢复的代码. 这对于标准的测试工具此
很难完成.

结论

不是所有的程序都关注于性能的, 不是所有的程序都有性能问题. 但是对那些关注这些
的程序, 这篇文章所提到的都很重要, 因为它们不是在最后一分钟就可以修改的. 既然在
你编写写代码使用一个类以后再修改它的接口非常困难, 那么在你的设计时期就花费一点
额外的时间来考虑性能特性.

在第二部分, 我会演示一些利用可修改性和不可修改性来减少不必要的对象创建的方法.


About the author
Brian Goetz is a professional software developer with over 15 years of experience.
He is a principal consultant at Quiotix, a software development and consulting
firm located in Los Altos, Calif.

论坛徽章:
0
发表于 2002-11-25 16:42 |显示全部楼层

为JAVA性能而设计(1(转贴)

为JAVA性能而设计(2)    ggzzkk(转贴)
  
关键字     java 性能
  
出处     http://www.smth.edu.cn/
  

发信人: SuperMMX (笑天子*不再喝可乐)
发信站: BBS 水木清华站

来自 http://SuperMMX.dhs.org/forum
原文请到此站查看.

        为性能而设计, 第二部分: 减少对象创建

From Java World.

在设计 Java 类的时候避免性能上的冒险

概要

许多通常的 Java 性能问题都起源于在设计过程早期中的类设计的思想, 早在许多开发者
开始考虑性能问题之前. 在这个系列中, Brian Goetz 讨论了通常的 Java 性能上的冒险
以及怎么在设计时候避免它们. 在第二部分, 他讨论了减少临时对象创建的一些技术.
(1,700 字)

By Brian Goetz

翻译 by SuperMMX

阅读整个"为性能而设计"系列:

第一部分: 接口事宜
第二部分: 减少对象创建
第三部分: 远程接口 (March 23, 2001)

虽然许多程序员把性能管理一直推迟到开发过程的最后, 性能考虑应该从第一天起就和设
计周期结合在一起. 这个系列探索一些早期的设计思想能够极大影响应用程序性能的方法.
在这篇文章里, 我继续探索大量临时对象创建的问题, 并且提供一些避免它们的一些技术.

临时对象就是一些生命周期比较短的对象, 一般用于保存其他数据而再没有其他用途. 程
序员一般用临时变量向一个方法传递数据或者从一个方法返回数据. 第一部分探讨了临时
对象是怎样给一个程序的性能带来负面的冲击, 以及一些类接口的设计思想怎样提供了临
时对象的创建. 避免了那些接口的创建, 你就能极大地减少那些影响你的程序性能的临时
对象创建的需求,

只是对 String 说不吗?

当它要创建临时变量时, String 类是最大的罪人中的一个. 为了演示, 在第一部分我写了
一个正规表达式匹配的例子, 通过和一个类似的但是经过仔细设计的接口相比较, 演示了
看起来无害的接口是怎样引起大量对象的创建, 而慢上几倍. 这里是原来的和好一些的类
的接口:

BadRegExpMatcher
  1. public class BadRegExpMatcher {
  2.   public BadRegExpMatcher(String regExp);
  3.   /** Attempts to match the specified regular expression against the input
  4.       text, returning the matched text if possible or null if not */
  5.   public String match(String inputText);
  6. }
复制代码

BetterRegExpMatcher
  1. class BetterRegExpMatcher {
  2.   public BetterRegExpMatcher(...);
  3.   /** Provide matchers for multiple formats of input -- String,
  4.       character array, and subset of character array.  Return -1 if no
  5.       match was made; return offset of match start if a match was
  6.       made.  */
  7.   public int match(String inputText);
  8.   public int match(char[] inputText);
  9.   public int match(char[] inputText, int offset, int length);
  10.   /** If a match was made, returns the length of the match; between
  11.       the offset and the length, the caller should be able to
  12.       reconstruct the match text from the offset and length */
  13.   public int getMatchLength();
  14.   /** Convenience routine to get the match string, in the event the
  15.       caller happens to wants a String */
  16.   public String getMatchText();
  17. }
复制代码

大量使用 BadREgExpMatcher 的程序比使用 BtterRegExpMatcher 的要慢好多. 首先,
调用者不得不创建一个 String 传入 match(), 接着 match() 又创建了一个 String 来
返回匹配的文本. 结果是每次调用都有两个对象创建, 看起来不多, 但是如果要经常调用
match(), 这些对象创建带给性能的代价就太打了. BadRegExpMatcher 的性能问题不是在
它的实现中, 而是它的接口; 象它定义的接口, 没有办法避免一些临时变量的创建.

BetterRegExpMatcher 的 match() 用原类型(整数和字符数组)代替了 String 对象; 不需
要创建中间对象来在调用者和 match() 之间传递信息.

既然在设计时候避免性能问题要比写完整个系统以后再修改要容易一些, 你应该注意你的
类中控制对象创建的方法. 在 RegExpMatcher 的例子中, 它的方法要求和返回 String
对象, 就应该为潜在的性能冒险提个警告信号. 因为 String 类是不可变的, 除了最常用
以外, 所有的 String 参数在每次调用处理函数时都需要创建一个新的 String.

不可变性对于性能来说是否很坏?

因为 String 经常和大量的对象创建联系在一起, 一般来说归咎于它的不可变性. 许多
程序员认为不可变的对象与生俱来对性能没有好处. 但是, 事实多少会更复杂一些. 实
际上, 不可变性有时候提供了性能上的优势, 可变性的对象有时候导致性能问题. 不管可
变性对性能来说有帮助或者有害, 依赖于对象是怎么使用的.

程序经常处理和修改文本字符串 -- 和不可变性非常不匹配. 每次你想处理一个 String --
想查找和解析出前缀或者子串, 变小写或者大写, 或者把两个字符串合并 -- 你必须创建
一个新的 String 对象. (在合并的情况下, 编译器也会隐藏地创建一个 StringBuffer()
对象)

另一个方面, 一个不可变的对象的一个引用可以自由共享, 而不用担心被引用的对象要被
修改, 这个比可变对象提供性能优势, 就象下一节例子所说的.

可变的对象有它们自己的临时对象问题.

在 RegExpMatcher 的例子中, 你看见了 当一个方法返回一个 String 类型时, 它通常
需要新建一个 String 对象. BadRegExpMatcher 的一个问题就是 match() 返回一个对
象而不是一个原类型 -- 但是只因为一个方法返回一个对象, 不意味着必须有一个新对
象创建. 考虑一下 java.awt 中的几何类, 象 Point 和 Rectangle. 一个 Rectangle
只是四个整数(x, y, 宽度, 长度)的容器. AWT Component 类存储组件的位置, 通过
getBounds()作为一个Rectangle 返回
  1. public class Component {
  2.   ...
  3.   public Rectangle getBounds();
  4. }
复制代码

在上面的例子中, getBounds() 只是一个存储元 -- 它只使一些 Component 内部的一
些状态信息可用. getBounds() 需要创建它返回的 Rectangle 吗? 可能. 考虑一下下面
getBounds() 可能的实现.
  1. public class Component {
  2.   ...
  3.   protected Rectangle myBounds;
  4.   public Rectangle getBounds()  { return myBounds; }
  5. }
复制代码

当一个调用者调用上面例子中的 getBounds(), 没有新对象创建 -- 因为组件已经知道它
在哪里 -- 所以 getBounds() 效率很高. 但是 Rectangle 的可变性又有了其他问题. 当
一个调用者运行一下程序会发生什么呢?
  1.   Rectangle r = component.getBounds();
  2.   ...
  3.   r.height *= 2;
复制代码

因为 Rectangle 是可变的, 它在 Component 不知道的情况下使 Component 移动. 对象
AWT 这样的 GUI 工具箱来说, 这是个灾难,  因为当一个组件移动以后, 屏幕需要重绘,  
件监听器需要被通知, 等等. 所以上面的实现 Component.getBounds() 的代码看起来很
危险. 一个安全一点的实现就象下面这样:
  1.   public Rectangle getBounds() {
  2.     return new Rectangle(myBounds.x, myBounds.y,
  3.                          myBounds.height, myBounds.width);
  4.   }
复制代码

但是现在, 每一个 getBounds() 的调用都创建一个新对象, 就象 RegExpMatcher 一样.
实际上, 下面的代码片段创建了 4 个临时对象:
  1.   int x = component.getBounds().x;
  2.   int y = component.getBounds().y;
  3.   int h = component.getBounds().height;
  4.   int w = component.getBounds().width;
复制代码

在 String 的情况中, 对象创建是必要的, 因为 String 是不可变的. 但在这个例子中,
对象的创建也是必要的, 因为 Rectangle 是可变的. 我们使用 String 避免了这个问题,
在我们的接口中没有使用对象. 虽然在 RegExpMatcher 的情况下很好, 这个方法不总是
可行的或者是希望的. 幸运的是, 你可以在实际类的时候可以使用一些技巧, 来免除太多
小对象的问题, 而不是完全避免小对象.

减少对象的技巧 1: 加上好的存取函数

在 Swing 工具箱的初始版本中, 对象小对象的临时创建, 象 Point, Rectangle 和 Dimension
极大地阻碍了性能. 把它们放在一个 Point 或者 Rectangle 中来一次返回多个值, 看起
来更有效, 实际上, 对象的创建比多个方法调用代价更高. 在 Swing 的最后发布之前, 通
过给 Component 和其他一些类加一些新的存取方法, 问题就简单地解决了, 就象下面这样:
  1.   public int getX()      { return myBounds.x; }
  2.   public int getY()      { return myBounds.y; }
  3.   public int getHeight() { return myBounds.height; }
  4.   public int getWidth()  { return myBounds.width; }
复制代码

现在一个调用者可以这样获取边界而不用创建对象:
  1.   int x = component.getX();
  2.   int y = component.getY();
  3.   int h = component.getHeight();
  4.   int w = component.getWidth();
复制代码

getBounds() 的旧形式仍然支持; 好的存取方法简单地提供了有效的方法来达到相同的目
的. 结果是, Rectangle 的接口全部在 Component 中使用. 当修改 Swing 包支持和使用
这样的存取函数后, 在许多 Swing 操作中比以前要快到两倍. 这很好, 因为 GUI 代码非
常注意性能 -- 用户等待发生一些事, 希望 UI 操作瞬间完成.

使用这个技术不好的地方就是你的对象提供了更多的方法, 有多于一个的方法来得到相同
的信息, 就使文档更大更复杂, 可能使用户害怕. 但是就象 Swing 的例子显示的, 在关注
性能的情况下, 这样的优化技术是有效的.

技巧 2: 利用可变性

除了给 Component 加上原类型的存储函数 -- 象上面讨论的 getX() 函数 -- 以外,
Java 2 在 AWT 和 Swing 中也使用了另一种技术来减少对象创建, 允许一个调用者把边界
作为一个 Rectangle 得到, 但是不需要任何临时对象的创建.
  1.   public Rectangle getBounds(Rectangle returnVal) {
  2.     returnVal.x = myBounds.x;
  3.     returnVal.y = myBounds.y;
  4.     returnVal.height = myBounds.height;
  5.     returnVal.width = myBounds.width;
  6.     return returnVal;
  7.   }
复制代码

调用者仍然需要创建一个 Rectangle 对象, 但它可以在后来的调用中重用. 如果一个调用
者在一系列的 Component 中循环, 可以只创建一个 Rectangle 对象, 在每个 Component
中重用. 注意这个技术只用于可变性对象; 你不能用这种方法消除 String 的创建.

技巧 3: 得到两个中的最好的.]

一个解决在简单类(象 Point 之类)的对象创建的问题, 更好的方法是使 Point 对象不可?
但是定义一个可变的子类, 就象下面这样:
  1.   public class Point {
  2.     protected int x, y;
  3.     public Point(int x, int y) { this.x = x; this.y = y; }
  4.     public final int getX() { return x; }
  5.     public final int getY() { return y; }
  6.   }
  7.   public class MutablePoint extends Point {
  8.     public final void setX(int x) { this.x = x; }
  9.     public final void setY(int y) { this.y = y; }
  10.   }
  11.   public class Shape {
  12.     private MutablePoint myLocation;
  13.     public Shape(int x, int y) { myLocation = new MutablePoint(x, y); }
  14.     public Point getLocation()       { return (Point) myLocation; }
  15.   }
复制代码

在上面的例子中, Shape 可以安全返回一个 myLocation 的引用, 因为调用者试图修改域
或者调用设置函数会失败. (当然, 调用者仍然可以把 Point 转换为 MutablePoint, 但
这明显不安全, 这样的调用者可能得到他们想要的) C++ 程序员可能注意到了这个技巧很
象 C++ 中返回一个 Rectangle 的常量引用(cong Rectangle& -- 一个 Java 不支持的
特点.

这个技巧 -- 返回一个具有可变的和不可变的类, 只允许读的对象, 而不创建新对象 --
在 Java 1.3 类库 java.math.BigInteger 类中使用. MutableBigInteger 类不可见 --
它是一个只在 java.math 类库中内部使用的私有类型. 但是既然 BigInteger 的一些方
法(象 gcd()) 在许多数学操作中都有, 在一个地方操作比创建上百个临时变量性能提高
非常大.

结论

所有的性能优化的建议中, 值得记住的是有许多程序的性能可以完全接受的情况. 在这
些情况下, 不值得牺牲可读性, 可维护性, 抽象, 或者其他可取的程序属性来获得性能.
但是, 既然许多性能问题的种子在设计时就种下了, 要注意到设计思想潜在地对性能的冲?
当你设计的类在关注性能的情况性爱使用, 你可以有效地使用这里提到的技巧来减少临时
对象的创建,

In Part 3, I'll look at some of the performance issues specific to distributed
applications and show how to spot and eliminate them at design time.

在第三部分中, 我将看看分布式的应用程序中特有的性能问题, 怎样在设计时候找出和消
除它们

About the author
Brian Goetz is a professional software developer with over 15 years of
experience. He is a principal consultant at Quiotix, a software development
and consulting firm located in Los Altos, Calif.

论坛徽章:
0
发表于 2002-11-25 16:42 |显示全部楼层

为JAVA性能而设计(1(转贴)

为JAVA性能而设计(3)    ggzzkk(转贴)
  
关键字     java 性能
  
出处     http://www.smth.edu.cn/
  


发信人: SuperMMX (笑天子*不再喝可乐)      
发信站: BBS 水木清华站

来自 http://SuperMMX.dhs.org/forum
原文请到此站查看.

         为性能而设计, 第三部分: 远程接口

             学习怎样在设计 Java 类的时候避免性能冒险.

概述

许多 Java 的通常性能问题来源于设计过程早期的类设计想法中, 早在开发者开始考虑
性能问题之前. 在这个系列中, Brian Goetz 讨论了一些通常的 Java 性能的冒险, 解
释了怎样在设计时间避免它们. 在这篇文章中, 它检验了远程应用程序中的特定的性能
问题.

By Brian Goetz

翻译 by SuperMMX

这个系列探索一些早期的设计思想对应用程序的性能产生影响的方法. 第一部分, 检查了
一个类的对象创建行为是如何嵌入它的接口中的. 特定的接口实际上要求一个类创建临时
对象, 或者需要它的调用者来创建临时对象, 才能使用这个类. 因为临时对象的创建对 Java
程序来说是一个性能指标, 当你在设计时候, 能再对你的类接口做一些回顾来检查性能冒?
这是值得的.

在第一和第二部分我集中于对象的创建, 因为对许多 Java 程序来说这是一个很大的性能侍?
但是, 在分布式的应用程序中, 象建立在 RMI, CORBA, 或者 COM 之上的程序, 一个完全煌
的性能问题就在眼前了. 这篇文章探索一些针对远程程序的性能问题, 演示你怎样才能通?
单地检查一个类的接口来预测到分布式应用程序中的性能问题.


阅读这个 "为性能而设计" 系列:

第一部分: 接口事宜
第二部分: 减少对象创建
第三部分: 远程接口

远程调用的概观

在分布式的应用程序中, 一个运行在一个系统中的对象可以调用另一个系统中的一个对象
的方法. 这个通过很多使远程对象表现为本地的结构的帮助而实现. 要访问一个远程对象,
你首先要找到它, 可以通过使用目录或者命名服务来实现, 象 RMI 注册, JNDI, 或者 CORBA
命名服务.

当你通过目录服务得到一个远程对象的引用时, 你并没有得到那个对象的实际的引用, 而
是一个实现了和远程对象同样接口的stub对象的引用. 当你调用一个stub对象的方法时, 飧
对象把方法的所有参数汇集起来 -- 把它们转化成一个字节流的表现形式, 类似于序列化
过程. 这个stub对象把汇集的参数通过网络传递给一个skeleton对象, 把参数分解出来, 饔
你想调用的实际的对象的方法. 然后这个方法向skeleton对象返回一个值, skeleton对象逊祷刂祷慵
起来, 把它传送给stub对象, stub对象把它分解出来, 传递给调用者. Phew! 一个单独的椒
调用要做这么多的工作. 很明显, 除去表面的相似性, 一个远程方法调用比本地方法调用?
更大.

以上描述浏览了一些对于程序性能非常重要的细节. 当一个远程方法返回的不是一个原类?
而是一个对象时, 会发生什么? 不一定. 如果返回的对象是一种支持远程方法调用的类型,
它就创建一个中stub对象和一个skeleton对象, 在这种情况下需要在注册表中查找一个远潭韵?
这显然是一个高代价的操作. (远程对象支持一种分布式的垃圾回收的形式, 包括了每一个
参与的 JVM 维护一个线程来和其他 JVM 的维护线程进行通讯, 来回传递引用信息). 如果
返回的对象不支持远程调用, 这个对象所有的域和引用的对象都要汇集起来, 这也是一个
代价的操作.

远程和本地方法调用的性能比较

远程对象访问的性能特征和本地的不一样:
远程对象的创建比本地对象创建代价要高. 不仅仅是当它不存在时要创建它, 而且stub对
和skeleton对象也要创建, 还要互相感知.

远程方法调用还包括网络的传递 -- 汇集起来的参数必须发送到远程系统, 而且响应也需
汇集起来, 在调用程序重新得到控制权之前发送回来. 汇集, 分解, 网络延时, 实际的远
调用所导致的延迟都加在一起; 客户端通常是等待所有这些而步骤完成. 一个远程调用也
大地依赖于底层网络的延时.

不同的数据类型有不同的汇集开支. 汇集原类型相对来说花费少一些; 汇集简单的对象,  
Point 或者 String 要多一些; 汇集远程对象要多得多, 而汇集那些引用非常多的对象的
对象(象 collection 等)要更多. 这和本地调用完全矛盾, 因为传递一个简单对象的引用槐
一个复杂对象的引用花费多.

接口设计是关键

设计不好的远程接口可能完全消除一个程序的性能. 不幸的是, 对本地对象来说好的接口
的特性对远程对象可能不适合. 大量的临时对象创建, 就象在本系列的第一, 二部分讨论?
也能阻碍分布式的应用程序, 但是大量的传递更是一个性能问题. 所以, 调用一个在一个
时对象(比如一个 Point)中返回多个值的方法比多次调用来分别得到它们可能更有效. (注
意, 这和在第二部分给本地对象岢龅慕ㄒ?I 完全相反.)


实际远程应用程序的一些重要的性能指导:

提防不必要的数据传递. 如果一个对象要同时得到几个相关的项, 如果可能的话, 在一个
远程调用中实现可能容易一些.

当调用者可能不必要保持一个远程对象的引用时, 提防返回远程的对象.

当远程对象不需要一个对象的拷贝时, 提防传递复杂对象.

幸运的是, 你可以通过简单查看远程对象的接口来找出所有的问题. 要求做任何高层动作
的方法调用序列可以从类接口中明显看到. 如果你看到一个通常的高层操作需要许多连续
的远程方法调用, 这就是一个警告信号, 可能你需要重新查看一下类接口.


减少远程调用代价的技巧

一个例子, 考虑下面假定的管理一个组织目录的应用程序: 一个远程的 Directory 对象包
含了 DirectoryEntry 对象的引用, 表现了电话簿的入口.
  1. public interface Directory extends Remote {
  2.   DirectoryEntry[] getEntries();
  3.   void addEntry(DirectoryEntry entry);
  4.   void removeEntry(DirectoryEntry entry);
  5. }
  6. public interface DirectoryEntry extends Remote {
  7.   String getName();
  8.   String getPhoneNumber();
  9.   String getEmailAddress();
  10. }
复制代码
现在假设你想在一个 GUI email 程序中使用 Directory 的东西. 程序首先调用
getEntries() 来得到入口的列表, 接着在每个入口中调用 getName(), 计算结果的列表,
当用户选择一个时,  应用程序在相应的入口调用 getEmailAdress() 来得到 email 地址.

在你能够写一封 email 之前有多少远程方法调用必须发生? 你必须调用 getEntries() 一
次, 地址簿中每个入口调用一次 getName(), 一次 getEmailAddress(). 所以如果在地址
中有 N 个入口, 你必须进行 N + 2 次远程调用. 注意你也需要创建 N + 1 个远程对象引
用, 也是一个代价很高的操作. 如果你的地址簿有许多入口的话, 不仅仅是打开 email 窗
口的时候非常慢, 也造成了网络阻塞, 给你的目录服务程序造成高负载, 导致可扩展性的
问题.

现在考虑增强的 Directory 接口:
  1. public interface Directory extends Remote {
  2.   String[] getNames();
  3.   DirectoryEntry[] getEntries();
  4.   DirectoryEntry getEntryByName(String name);
  5.   void addEntry(DirectoryEntry entry);
  6.   void removeEntry(DirectoryEntry entry);
  7. }
复制代码
这将减少多少你的 email 程序所造成的花费呢? 现在你可以调用 Directory.getNames()
一次就可以同时得到所有的名字, 只需要给你想要发送 email 的容器调用 getEntryByName() .
这个过程需要 3 个远程方法调用, 而不是 N + 2, 和两个远程对象, 而不是 N + 1 个.
如果地址簿有再多一点的名字, 这个调用的减少在程序的响应和网络负载和系统负载有很
大的不同.

用来减少远程调用和引用传递的代价的技术叫做使用次要对象标识符. 使用一个对象的标
属性, -- 在这个例子中, 是 name -- 而不是传回一个远程对象, 作为对象的一个轻量级晔斗?
次要标识符包含了它描述的对象足够的信息, 这样你只需要获取你实际需要的远程对象.
在这个目录系统的例子中, 一个人的名字是一个好的次要标识符. 在另一个例子中, 一个
安全皮包管理系统, 一个采购标识号可能是一个好的次要标识符.

另一个减少远程调用数量的技巧是块获取. 你可以进一步给 Directory 接口加个方法, 来一
次获取多个需要的 DirectoryEntry 对象:
  1. public interface Directory extends Remote {
  2.   String[] getNames();
  3.   DirectoryEntry[] getEntries();
  4.   DirectoryEntry getEntryByName(String name);
  5.   DirectoryEntry[] getEntriesByName(String names[]);
  6.   void addEntry(DirectoryEntry entry);
  7.   void removeEntry(DirectoryEntry entry);
  8. }
复制代码
现在你不仅可以得到需要的远程 DirectoryEntry , 也可以用单独一个远程方法调用得到
要的所有的入口. 虽然这并不减少汇集的代价, 但极大地较少了网络往返的次数. 如果网
延迟很重要的话, 就可以产生一个响应更快的系统(也能减少这个网络的使用).

照亮去向 RMI 层次的路径的第三的技巧是不把 DirectoryEntry 作为一个远程对象, 而把它
定义为一个通常的对象, 带有访问 name, address, email address 和其他域的访问函数.
(在 CORBA 系统中, 我可能要使用类似的 object-by-value 机制.) 然后, 当 email 应用程
序调用 getEntryName() 时, 它会获取一个 entry 对象的值 -- 不需要创建一个stub对象或
者skeleton对象, getEmailAddress() 的调用也是一个本地的调用而不是一个远程的.

当然, 所有这些技巧都都依赖于对远程对象实际上是怎样使用的理解上的, 但是对于这个
理解, 你甚至不需要看一看远程类的实现就可以找出一些潜在的严重性能问题.

结论

分布式的应用程序的性能特性本质上和本地程序不同. 许多对于本地程序代价很小的操作
对于远程应用程序来说代价非常高, 设计不好的远程接口导致一个程序有严重的扩展性和
能问题.

幸运的是, 很容易在设计时候, 为那些高代价的操作(象远程调用和远程对象创建), 通过觳橥
常的用例和分析它们, 确定和解决许多通常的分布式的性能问题, 正确使用这里提到的技?
次要的对象标识符, 块获取和 return-by-value -- 可以本质上提高用户响应时间和整个
统的吞吐量.


About the author
Brian Goetz is a professional software developer with more than 15 years of
experience. He is a principal consultant at Quiotix, a software development
and consulting firm located in Los Altos, Calif.


论坛徽章:
0
发表于 2002-11-26 09:24 |显示全部楼层

为JAVA性能而设计(1(转贴)

呵呵,讲的真是细,居然忘了精华
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP