|Practical Java| Chapter 4 Performance (性能) (一)
Java正变得越来越快, 这得益于 JIT 代码生成器 (code generators) / garabage collection
以及其他运行期优化技术 (Runtime optimizations)的进步. 然后程序适当的设计一直
是不容忽视的.
优化的事项:
A> 除非证实必须优化,否则不要优化你的代码。验明这一点的惟一方法
用你自己的计时技术或运用某个执行评测器(execution profiler)。
B> 如果优化工作进行得不够小心细致,可能会将臭虫引入代码中。记住
活的慢郎总好过不干活的急惊风。
C> 实施优化之后,应该再次进行评测,以验证是否达到了预期效果。
D> [适用于所有侧s J的成本模型[c。d咖deI)是不存在的。因此在某个JVM
中令代码更快捷的技术,未必在另一个JVM中也能得到相同效果。
代码性能特征(performance characteristics )计时技术:
long start = System.currentTimeMillis();
//code to time
long end = System.currentTimeMillis();
long Time = end -start;
28> 先把焦点放在设计 / 数据结构 和算法上.
We should forget about small efficiencies, about 97% of the Time.
Premature optimization is the root of all evil.
产生快捷代码的一个规则是,只优化必要的部分。花费时间将代码优化,却未能给
程序带来实质性的性能影响,就是在浪费时间. 典型情况下 80% ~90% 的程序执行
时间花费在10% ~ 20% 的代码上面(译注:80 - 20法则)。称最好通过性能评测器
(performance profilers)找出这需要改善10% ~ 20% 的代码。
29> 不要依赖编译期 (compile - Time )优化技术
Java编译器在优化方面着墨甚少. Sun的 Javac 及其他大部分complier只支持少量
优化技术: 简单的常量合并(constant folding) / 无用码删除(dead code elimination)
(文件宣称[实际毫无意义]: javac -o 可以为运行期产生出优化代码. || javap -c 反汇编)
30> 理解运行期 (Runtime) 代码优化技术
JIT的目的是将 bytecode 在运行期转换为本机二进制码 (native binary code).
JIT的运行也需要时间的, 如果JIT 的运行时间多, 即意味着程序启动的时间也长.
31> 如果进行字符串结合, StringBuffer 优于 String
String 用来表示那些建立后就不再改变的字符串 (Character strings), 即String 对象
是只读且不可变的.(immutable)
StringBuffer 用来表示内容可变的字符序列, 提供一些函数用来修改底部(underlying)
字符序列的内容.
String 和StringBuffer 之间的关键差异在于, 执行单纯的字符串合并(concatenation)时,
后者比之前者快很多.
Ex: Javap -c 查看一下代码的bytecode:
public class PP{
public static void main(String []args){
String str = new String ("Practical ");
str +="Java";
//StringBuffer str2 = new StringBuffer("Practical ");
//str2.append("Java");
}
}
0 new #7 <Class java lang.String>
3 dup
4 ldc #2 <String "Practical ">
6 invokespecial #12 <Method java.lang.String (java.lang.String>
9 astore_1
10 new #8 <Class java.lang.StringBuffer>
13 dup
14 aload_1
15 invokestatic #23 <Method java.lang.String valueof(java.lang.Object)>
18 invokespecial #13 <Method java.lang.StringBuffer(java.lang.String)>
21 ldc #1 <String "Java">
23 invokevirtual #15 <Method java.lang.StringBuffer append(java.lang.String)>
26 invoidvirtual #22 <Method java.lang.String toString()>
29 astore_1
// 0~9 String str = new String ("Practical ");
// 10 因为String 是不可变的,为了实现合并必须建立一个StringBuffer对象;
// 26 合并之后必须将其转换回String, 此处调用toString() 完成. 这个方法根据
// [StringBuffer 临时对象]建立一个新的String对象. ---- 这里临时对象的
// 建立及转型为String, 代价不菲.
上述程序main 产生了5个对象:
位置 0 / 4 产生2个String 对象;
位置 10 产生一个StringBuffer 对象;
位置 21 / 26 产生2个String 对象;
//StringBuffer str2 = new StringBuffer("Practical ");
//str2.append("Java");
采用StringBuffer的 bytecode:
0 new #7 <Class java lang.StringBuffer>
3 dup
4 ldc #2 <String "Practical ">
6 invokespecial #12 <Method java.lang.StringBuffer (java.lang.String>
9 astore_1
10 aload_1
11 ldc #1 <String "Java">
13 invokevirtual #15 <Method java.lang.StringBuffer append(java.lang.String)>
16 pop
这里的两句代码产生3个对象; ( 位置0 --- StringBuffer, 4 / 11 --- String)
32> 将对象的创建成本 (creation cost) 降至最小
对象的构建 (construction) 不仅仅是[分配内存 + 初始化一些 值域(fields)]这么简单!!
对象构建过程中,一定会以固定顺序发生的东东:
A> 从Heap 中分配内存, 用以存放全部的instance 变量以及这个对象连同其
superclasses的实现专属数据(implementation-specific data). -- 即包括指向
"class and method data" 的指针.
B> 对象的instance 变量被初始化为其相应的缺省值.
C> 调用 most derived class(最深层派生类)的 Constructor ---
//事实上, 构造函数被.class 文件中的 initialization method 替换了. im函数是名为
//<init> 的特殊函数, 由Java 编译期安放在.class文件里. 其中包含 [构造函数代码]
//[instance 变量的初始化代码] / [调用superclass im]的代码.
构造函数做的第一件事就是调用superclass的 construct .这个程序一直反
复持续到java.lang.Object构造函数被调用为止.(jlo是一切o的base class);
D> 在构造函数本身执行之前,所有instance 变量的initializers 和初始化区段
(initialization blocks)先获得执行, 然后才执行构造函数本身. 于是base
class 的construct 最先执行, most derived class 的construct最后执行.这使
得任何class的construct都放心大胆的使用其任何superclasses的instance变量.
Ex: [ (lightweight)对象的建立比一个重型(heavyweight)对象快很多]
Light lgt = new Light(5);
1> 从heap 分配内存, 用来存放Light class的instance变量;
2> class的instance变量被初始化为相应缺省值;
3> 调用Light construct Method;
4> LS 调用其superclass construct;
5> 第4>部返回后, LS 对instance 变量设定初值;
6> LS 结束, Object reference lgt指向heap 中刚建立完成的Light 对象;
以下情况会增加对象创建的成本: (creation cost)
A> construct 有大量代码;
B> 内含数量众多或体积庞大的对象 ----她们的初始化将是construct的一部分;
C> 太深的继承层次(inheritance hierarchy);
// 记住, 每一个被创建的对象,也是垃圾回收器跟踪和可能释放的目标,
// 所以garbage collention正确管理她们的存储也需要代价;
33> 谨慎未用上的对象 (unused objects)
对象的创建是需要代价的, 确保所create 对象在使用上的质量.
34> 将同步化 (synchronization) 降至最低
synchronized 函数比之 non-synchronized 函数慢很多: (两者的bytecode是一样的)
------>
当函数被提供了synchronized 修饰符后, lock的获取(acquistion) 和随后的释放
(release)就不再通过monitorenter 和monitorexit 操作码(opcodes)来进行. 取而代
之的是, 当JVM调用一个函数时, 会检验 ACC_SYNCHRONIZED属性标记.如
果存在这个标记(property flag), 当前线程就取得一个lock 并调用之, 且在函数
返回时释放lock .
synchronized 不仅降低代码执行速度,视其使用方式的不通, 还可能增加代码体积.
比较下面两个功能相当的函数:
public synchronized int top1(){ return intarr[0] }
pubic int top top2(){ synchronized(this){ return intarr[0];} }
//top1() 并不会生成额外的代码, top2()则是在函数体中使用synchronized.
//后者将会产生操作码 monitorenter 和 monitorexit 的bytecode , 以及为了
//处理异常而附加的代码.如此top1() 比top2() 性能略高.
//对于 Vector 类就是因为绝大多数的函数都是synchronized, 所以现在不推荐使用,
//现在取而代之的有 ArrayList class. 当然这只是在不需要同步化的情况下.