4.22 错误优化策略

本博客来自我的新书Java系统性能优化(暂定名),也欢迎阅读我的新书 《Spring Boot 2 精髓 》

4.22.1 final无法帮助内联

有一个过时规则,“在java的getter和setter方法中经量使用final关键字,以支持内联“,比如

public final String getUserName(){
	return this.userName;f
}

在Java中,final关键字可以用来修饰类、方法和变量,我们在第三章了解到final在并发的时候,修饰变量得时候具有的语义同volatile一样,内存可见性和禁止重排序。 final修饰的方法的时候,表示子类不能覆盖此方法。早期的Java版本,final关键字还可以提醒虚拟进行内联,但现在是否内联,不再需要使用final关键字。Aleksey在他的论文里,验证了final关键字对于方法是否内联并没有直接联系,The Black Magic of (Java) Method Dispatch,有兴趣的可以自己看一下,同样有一个JMH工程可以自己下载下来测试分析一下

关于内联,参考第8章JIT优化

4.22.2 subString 内存泄露

较早的Java性能优化书指出String.subString容易造成内存泄露,主要原因是JDK版本的subString方法,会复用String的数组,并没有为新的字符串生成一个新的char数组,这样,如果原来的String特别长,那么新返回的String所占用的空间也会特别大。JDK6以后已经不再subString的时候复用char数组了,而是改成重新生成一个数组,我们再第二章看到构造一个String,会重新生成一个字符串数组value

private final char value[];
public String(char value[], int offset, int count) {
  	.....
    this.value = Arrays.copyOfRange(value, offset, offset+count);
}

4.22.3 循环优化

有一种说法,嵌套循环中,嵌套循环应该遵循“外小内大”的原则,这样性能才会高,这种说法给出了俩个例子,第一个是外大内小

stratTime = System.nanoTime();
for (int i = 0; i < 100_000_00; i++) {
	for (int j = 0; j < 10; j++) {

	}
}
endTime = System.nanoTime();
System.out.println("外大内小耗时:"+ (endTime - stratTime));

外小内大

stratTime = System.nanoTime();
for (int i = 0; i <10 ; i++) {
	for (int j = 0; j < 10000000; j++) {
	}
}
endTime = System.nanoTime();
System.out.println("外小内大耗时:"+(endTime - stratTime));

俩个代码片段循环同样次数,但从测试结果看后者远大于前者,也就是嵌套循环外小内大的性能高于外大内小。

这种说法忽略了JIT会做DEAD-CODE消除,JIT判断循环对程序不会有任何影响而消除了循环体, 导致了结果测试不准,如果使用JMH测试,如下,就会发现嵌套循环结果一样

//ForDeadCodeTest.java
@Benchmark
public long test(Blackhole hole) {
  long startTime = System.nanoTime();
  int i=0,j=0;
  for ( i = 0; i < 100_000_00; i++) {
    for ( j = 0; j < 10; j++) {
      hole.consume(j);
    }
    hole.consume(i);
  }

  Long endTime = System.nanoTime();
  return endTime-startTime+i+j;
}


@Benchmark
public long tes2(Blackhole hole) {
  long startTime = System.nanoTime();
  int i=0,j=0;
  for ( i = 0; i <10 ; i++) {
    for ( j = 0; j < 100_000_00; j++) {
      hole.consume(j);
    }
    hole.consume(i);
  }
  Long endTime = System.nanoTime();
  return endTime-startTime+i+j;
}
//测试基准,空函数
@Benchmark
public long base(Blackhole hole) {
  long startTime = System.nanoTime();

  Long endTime = System.nanoTime();
  return endTime-startTime;
}

这里使用了JMH提供的Blackhole函数来防止出现代码消除情况,这种测试最后证明,俩种循环性能没有区别

Benchmark                       Mode     Score    Units              
c.i.c.c.ForDeadCodeTest.base    avgt     0.000    ms/op              
c.i.c.c.ForDeadCodeTest.tes2    avgt    56.007    ms/op              
c.i.c.c.ForDeadCodeTest.test    avgt    50.228    ms/op              

4.22.4 循环中捕捉异常

一种说法是应该在循环体外捕捉异常,而不要在循环体内,理由try catch 代价较大。

for(int i=0;i

如上代码,这种说法建议改成循环体外

try{
  for(int i=0;i

实际上,循环体内使用try catch方式对性能几乎没什么影响,不需要关注在循环体内还需循环体外。按照自己业务需要正确使用try catch就行了。

转载于:https://my.oschina.net/xiandafu/blog/3077158

你可能感兴趣的:(4.22 错误优化策略)