【软件构造】8.5 面向性能优化的代码调优

1.代码调优的策略与过程

代码调优不是为了修复bug,而是对正确的代码进行修改,以提高其性能。
代码调优通常是针对小规模的变化,比如针对类,某条代码执行的路径,或者更简单的,几行代码的变化。
代码调优是最后做的工作,其他方面都无法再优化时,再考虑代码调优。
1.调优不会减少代码行数
代码行数与性能之间没有必然的联系
2.调优时不应猜测怎样会提高性能,而应该有明确的目标并衡量结果
通过度量发现热点和瓶颈,代码调优建立在对程序性能的精确度量基础上。 当程序做过某些调整之后,要重新profiling并重新了解需要优化的性能瓶颈,微小的变化能导致优化方向大不相同。在一个环境下提高性能的调优在另一个环境下可能降低性能。
3.不要边写程序边调优
在没有完整程序之前,无法获知性能瓶颈
在开发阶段进行调优,容易忽视其他更重要的质量指标
4.性能从不是追求的第一目标,正确性比性能更重要
调优不是代码性能优化的第一选择。

代码调优的过程:
备份→通过度量发现热点瓶颈→分析原因,评判代码调优的必要性→调优→每次调优后都要重新度量→若无效果或负效果,则回滚

2.低效的来源

很多。I/O,paging,operators,object creation,GC…

3.从创建与重复使用对象的角度调优

3.1 Singleton Pattern 单例模式

强制client只能创建一个object实例,避免因为new操作所带来的时空性能(尤其是GC)的损失,也便于复用。
优点:只能获取到一个实例
减小命名空间(?)
通过类中封装的代码确保对象的重复使用,客户端易于操作
重复使用意味着更好的性能
节约了创建新对象的时间,节省内存

单例模式:
设置静态变量来存储单一实例对象
将构造器设置为private,从而client无法new
在构造器中new新实例
提供静态方法来获取单一实例对象

在这里插入图片描述

public class Singleton { 
	private static final Singleton instance = new Singleton(); 
	private Singleton() {...}
	public static Singleton getInstance() { return instance; } 
	// other operations and data
}

在装载到程序时创建类

Lazy Load:在需要的时候再new,而非提前构造出来

public class Singleton { 
	private static final Singleton instance = null; 
	private Singleton() {...} 
	public static Singleton getInstance() { 
		if (instance == null) 
				instance = new Singleton(); 
		return instance; 
	} 
	// other operations and data 
}

3.2 Flyweight Pattern 轻量模式

在应用中不同部分共享使用objects,降低大量objects带来的时空代价。
每个对象有内部特征和外部特征,外部特征需要在不同场合分别指派/计算其值。
在这里插入图片描述
在这里插入图片描述
ConcreteFlyweight里存储的状态是内部特征
FlyweightFactory:创建,管理flyweight对象。客户端请求flyweight对象时,FlyweightFactory返回或创建一个实例。
例:
在这里插入图片描述
定义“外特征”:

public enum Color {Red, Green, Blank, Blue, Yellow}

可共享对象的抽象接口:

public interface IAlien { 
	String Shape = null;           //intrinsic state 
	String getShape(); 
	Color getColor(int madLevel);  //extrinsic state 
}

两种具有不同内特征的共享对象:

class LargeAlien implements IAlien{ 
	private String shape = "Large Shape";  
	public String getShape() { return shape; } 
	public Color getColor(int madLevel) { 
		if (madLevel == 0) 
			return Color.Green; 
		else if (madLevel == 1) 
			return Color.Red; 
		else return Color.Blue; 
	}
}
class LittleAlien implements IAlien { 
	private String shape = "Little Shape";  
	public String getShape() { return shape; } 
	public Color getColor(int madLevel) { 
		if (madLevel == 0) 
			return Color.Red; 
		else if (madLevel == 1) 
			return Color.Blue; 
		else 
			return Color.Green; 
	}
}
public class AlienFactory { 
	private Map<String, IAlien> list = new HashMap<>(); 
	public void SaveAlien(String index, IAlien alien) { 
		list.put(index,alien); 
	} 
	public IAlien GetAlien(String index) { 
		return list.get(index); 
	} 
}

client:

AlienFactory factory = new AlienFactory(); 
factory.SaveAlien("LargeAlien", new LargeAlien()); 
factory.SaveAlien("LittleAlien", new LittleAlien());
IAlien a = factory.GetAlien("LargeAlien"); 
IAlien b = factory.GetAlien("LittleAlien");
System.out.println("Showing intrinsic states...");            
System.out.println("Alien of type LargeAlien is " + a.getShape()); 
System.out.println("Alien of type LittleAlien is " + b.getShape());
System.out.println("Showing extrinsic states..."); 
System.out.println("Alien of type LargeAlien is " + a.getColor(0).toString()); 
System.out.println("Alien of type LargeAlien is " + a.getColor(1).toString()); 
System.out.println("Alien of type LittleAlien is " + b.getColor(0).toString()); 
System.out.println("Alien of type LittleAlien is " + b.getColor(1).toString());
FlyweightSingleton
同一事物有不同表现形式统一用一个实例表示
immutablemutable

3.3 Prototype Pattern 原型模式

当直接new对象的时空代价高时,通过clone而不是new来创建object
对于普通ADT,clone不会带来性能的提升。
在这里插入图片描述
需要override clone()方法,并将可见性设为public
Object.clone()是protected:它可以被同包(java.lang)下以及它(java.lang.Object)的子类访问。
自定义类无法直接使用Object.clone():没有访问权限(invisible),故需要override。 (??)
如果在没有实现Cloneable 接口的实例上调用 Object 的 clone 方法,则会导致抛出 CloneNotSupportedException 异常。
在这里插入图片描述

Shape clonedShape1 = (Shape) rectangle.clone(); 
Shape clonedShape2 = (Shape) square.clone(); 

clone()方法返回的是object类型的对象,需要强转
拷贝:
引用拷贝:两个reference变量指向同一个对象
对象拷贝:两个reference变量指向不同对象,包括深拷贝和浅拷贝
浅拷贝:使用一个已知实例的成员变量对新创建实例的成员变量逐个赋值(缺省)
在这里插入图片描述
深拷贝:复制对象的所有非引用成员变量值,并为引用类型的成员变量创建新的实例,并且初始化为原对象的值。
在这里插入图片描述

3.4 Object Pool Pattern 对象池模式

对象复用
把一组已初始化的可用对象作为pool,而不是需要时创建对象,不需要时再回收对象。
客户端从pool中请求对象,在返回的对象上进行操作
操作结束后,将对象返回到pool中,而不是丢弃它
在这里插入图片描述
代价:原本可被GC的对象,现在要留在pool中,导致内存浪费——用空间换时间
Singleton和Flyweight本质也都是Object Pool

3.5 Canonicalizing Objects 规范化

不保留对象的多个拷贝,仅保留少量Object
字符串常量池
例:只用到少量integer对象的情况:将对象规范化

public class IntegerManager { 
	public static final Integer ZERO = new Integer(0); 
	public static final Integer ONE = new Integer(1); 
	public static final Integer TWO = new Integer(2); 
	…… 
	public static final Integer NINE = new Integer(9); 
	public static final Integer TEN = new Integer(10); 
}

如果用普通方式创建Integer对象,会重复创建很多对象,而且比较的时候要用integerValue()比较
规范化之后减少了new对象的次数和垃圾回收的次数,且可以通过地址比较

规范化的技术也常用于用int取代其他常量object
例:用这样的枚举类型取代字符串"male",“female”

public interface GENDER { 
	public static final int FEMALE=1; 
	public static final int MALE=2;
} 

枚举类型的时空性能更好:占内存小,通过地址比较,比较块

3.6 Avoiding Garbage Collection 规避垃圾回收

canonicalization techniques:减少创建object的数量,避免GC的代价。
pooling technique:通过复用对象减少了创建对象,从而减少GC;持续使用已分配内存的对象,减少内存释放。
另一项技术:避免使用不需要的对象

String string = “55”;  在String pool中分配,无需GC 
int theInt = new Integer(string).intValue(); 创建一个Integer 
int theInt = Integer.parseInt(string); 无需创建对象

尽可能使用简单数据类型,对类的成员变量也是如此。
内存中基本数据类型的变量也需要回收,但回收代价低:它和它所属于的对象同时被回收,所以影响小
例如:如果一个对象有一个int类型的成员变量,回收该对象的实例时GC一次;如果该对象有一个Integer类型的成员变量,回收该对象的实例时GC两次(Integer和这个对象本身)。
局部的简单数据类型的变量在栈中存储,不需GC。
使用简单数据类型替代复杂数据类型:
能用int存储诸如"1492", "1997"的数据就不要用String
用int/long代替Date对象 (但转换需要的计算可能花费更多时间)
在这里插入图片描述
其他建议:
1.减少临时对象的使用,尤其是在循环里
2.优先使用StringBuffer而不是用“+”连接字符串
3.分辨哪些方法直接在对象上操作,而哪些方法返回的是对象的复制
4.使用基本数据类型时,避免使用处理Object的类,例如,没有必要把ints包装成Integers,再使用Vector,而是可以实现一个直接存储ints的IntVector类

3.7 Object Initialization 对象初始化

在这里插入图片描述
(前三点没有看懂,哪位读者朋友看懂了麻烦联系我一下给我讲讲)
对于无法避免创建大量对象的情况,可以把创建对象的时间转移到程序中有空闲的时候,并储存这些对象直到它们被使用。
Late (Lazy) Initialization:在第一次使用对象时才创建对象
通过检查该对象是否为空来判断它是否被初始化

public getSomething(){ 
	if (something = = null) 
		something = defaultSomething( ); 
	return something; 
}

4.字符串的代码调优

字符串常量池是堆中的一块存储区域,保存对字符串对象的引用。
创建String对象的方式:
1.String s = “java”;
JVM在字符串常量池中查找“java”,如果找到,取它的引用,如果没找到,在字符串常量池中创建“java”。
2.String s = new String(“java”);
在堆中创建对象。

String s1 = "java"; 
String s2 = "java"; 
String s3 = new String("java");

在这里插入图片描述
如果字符串可以在编译阶段确定,使用“+”效率更高
如果字符串在运行阶段确定,使用StringBuffer效率更高
使用不复制字符串中字符的,效率比较高的方法
避免使用使用复制字符串中字符的,效率比较低的方法

5.逻辑,循环,数据类型,表达式和方法的代码调优

1.在能得出答案时自动停止判断
(1)short-circuit evaluation:如“if ( x>5 && x<10 )”。也是条件操作的一部分,相当于:

if (x>5) 
	{ if (x <10 ) { …} 
}

(2)循环中,如果已经得出结果,跳出循环
2.用查表替代复杂的逻辑判断
在这里插入图片描述
3.使用Lazy Evalution: avoids doing any work until the work is needed
4.将判断外提:循环里的分支判断会在循环每次执行时都执行,如果循环过程中分支判断不变,可以把判断提到循环外面
在这里插入图片描述
5.减少数组维度

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值