当CPU 进入多核时代之后软件的性能调优就不再是一件简单的事情没有并行化的程序在新的硬件上可能会运行得比从前更慢当 CPU 数目增加的时候芯片制造商为了取得最佳的性能/功耗比降低 CPU 的运行频率是一件非常明智的事情相比 C/C++ 程序员而言 利用 Java 编写多线程应用已经简单了很多然而多线程程序想要达到高性能仍然不是一件容易的事情对于软件开发人员而言 如果在测试时发现并行程序并不比串行程序快那不是一件值得惊讶的事情毕竟在多核时代之前 受到广泛认可的并行软件开发准则通常过于简单和武断
在本文中我们将介绍使提高Java 多线程应用性能的一般步骤 通过运用本文提供的一些简单规则我们就能获得具有高性能的可扩展的应用程序
为什么性能没有增长?
多核能带来性能的大幅增长这很容易通过简单的一些测试来观察到如果我们写一个多线程程序并在每个线程中对一个本地变量进行累加我们可以很容易的看到多核和并行带来的成倍的性能提升这非常容易做到不是吗?在 参考资源 里我们给出了一个例子然而与我们的测试相反我们很少在实际软件应用中看到这样完美的可扩展性阻碍我们获得完美的可扩展性有两方面的因素存在首先我们面临着理论上的限制其次软件开发过程中也经常出现实现上的问题让我们看看 图 中的三条性能曲线
图 性能曲线
作为追求完美的软件工程师我们希望看到随着线程数目的增长程序的性能获得线性的增长也就是图 中的蓝色直线而我们最不希望看到的是绿色的曲线不管投入多少新的 CPU性能也没有丝毫增长(随着 CPU 增长而性能下降的曲线在实际项目中也存在)而图中的红色线条则说明通常的 法则并不适用于可扩展性方面假设程序中有 % 的计算只能串行进行那么其扩展性曲线如红线所示由图可见当 % 的代码可以完美的并行时在 个 CPU 存在的情况下我们也只能获得大约 倍的性能如果任务中具有无法并行的部分那么在现实世界我们的性能曲线大致上会位于图 中的灰色区域
在这篇文章中我们不会试图挑战理论极限我们希望能解释一个 Java 程序员如何能够尽可能的接近极限这已经不是一个容易的任务
是什么造成了糟糕的可扩展性?
可扩展性糟糕的原因有很多其中最为显着的是锁的滥用这没有办法我们就是这样被教育的想要多线程安全吗?那就加一个锁吧想想 Python 中臭名昭着的 Global Intepreter Lock还有 Java 中的 CollectionssynchronizedXXXX() 系列方法跟随巨人的做法有什么不好吗?是的用锁来保护关键区域非常方便也较容易保证正确性然而锁也意味着只有一个进程能进入关键区域而其他的进程都在等待!如果观察到 CPU 空闲而软件执行缓慢那么检察一下锁的使用是一个明智的做法
对于 Java 程序而言Performance Inspector 中的 Java Lock Monitor 是一个不错的开源工具