性能优化的理念

粗略地划分,代码可分为 cpu consuming 和 io consuming 两种类型,即耗 CPU 的和耗 IO 的代码。如果当前CPU已经能够接近100%的利用率, 并且代码业务逻辑无法再简化, 那么说明该系统的已经达到了性能最大化, 如果再想提高性能, 只能增加处理器(增加更多的机器或者安装更多的CPU)。
而耗 IO 的代码,一般体现为请求某种资源,这可以是访问数据库,或者访问网络对端。

评价程序写得好不好,要看随着访问压力的上升,CPU 使用率的变化,好的代码,随着访问压力的上升,CPU 的使用率最终能趋近100%,而坏的代码,使用率始终无法趋近 100%,有可能在 70% 就已经上不去了。好的代码应该在代码本身效率足够高的情况下,通过使用并发等手段,让 CPU 的尽量地忙起来。随着访问压力的上升,CPU 使用率也上升,并且 CPU 所跑的代码都是已经无法再进行逻辑优化或者效率提升的代码,这是最理想的状态。

常见性能瓶颈

多余的同步

不相关的两个函数, 共用了一个锁,或者不同的共享变量共用了同一个锁, 无谓地制造出了资源争用,如下代码所示:

class MyClass {
  Object sharedObj;
  synchronized void fun1() {...} //访问共享变量sharedObj
  synchronized void fun2() {...} //访问共享变量sharedObj
  synchronized void fun3() {...} //不访问共享变量sharedObj
  synchronized void fun4() {...} //不访问共享变量sharedObj
  synchronized void fun5() {...} //不访问共享变量sharedObj
}

上面的代码将sychronized加在类的每一个方法上面, 违背了保护什么锁什么的原则。对于无共享资源的两个方法, 使用了同一个锁, 人为造成了不必要的锁等待。 上述的代码可作如下修改:

class MyClass {
  Object sharedObj;
  synchronized void fun1() {...} //访问共享变量sharedObj
  synchronized void fun2() {...} //访问共享变量sharedObj
  void fun3() {...} //不访问共享变量sharedObj
  void fun4() {...} //不访问共享变量sharedObj
  void fun5() {...} //不访问共享变量sharedObj
}

锁粒度过大

对共享资源访问完成后, 没有将后续的代码放在synchronized同步代码块之外。 这样会导致当前线程长时间无谓的占有该锁, 其它争用该锁的线程只能等待, 最终导致性能受到极大影响。如下代码所示:

void fun1() {
    synchronized(lock){
    ... ... //正在访问共享资源
    ... ... //做其它耗时操作,但这些耗时操作与共享资源无关
  }
}

上面的代码, 会导致一个线程过长地占有锁, 而在这么长的时间里其它线程只能等待。应将上述代码作如下修改,在多 CPU 的环境中,可获得性能的提升:

void fun1() {
  synchronized(lock) {
    ... ... //正在访问共享资源
  }
  ... ... //其它耗时操作代码拿到synchronized代码外面
}

字符串连接的滥用

String c = new String("abc") + new String("efg") + new String("12345");

每一次+操作都会产生一个临时对象, 并伴随着数据拷贝, 这个对性能是一个极大的消耗。 这个写法常常成为系统的瓶颈, 如果这个地方恰好是一个性能瓶颈, 修改成StringBuffer之后, 性能会有大幅的提升。

不恰当的线程模型

在多线程场合下, 如果线程模型不恰当, 也会使性能低下,在网络IO的场合, 使用消息发送队列和消息接收队列来进行异步IO,性能会有显著的提升。


不恰当的 GC 参数

不恰当的GC参数设置会导致严重的性能问题,比如堆内存设置过小导致大量的 CPU 时间片被用来做垃圾回收

03-05 23:18