2.1 原则1:测试真实应用
应该在产品实际使用的环境中进行性能测试,这样测试出的数据才有参考意义。
2.1.1 微基准测试
第 1 种是微基准测试。微基准测试用来测量微小代码单元的性能,包括调用同步方法的用 时与非同步方法的用时比较,创建线程的代价与使用线程池的代价,执行某种算法的耗时 与其替代实现的耗时,等等。
微基准测试看起来很好,但要写对却很困难。考虑以下代码,被测的方法是计算出第 50个斐波那契数,这段代码试图用微基准测试来测试不同实现的性能:
public void doTest() { // 主循环
double l; long then = System.currentTimeMillis(); for (int i = 0; i
l = fibImpl1(50); }
} ... private double fibImpl1(int n) {
if (n 0"); if (n == 0) return 0d; if (n == 1) return 1d; double d = fibImpl1(n - 2) + fibImpl(n - 1);
if (Double.isInfinite(d)) throw new ArithmeticException("Overflow");
return d; }
这段代码的最大问题是,实际上它永远都不会改变程序的任何状态。因为斐波那契的计算
结果从来没有被使用,所以编译器可以很放心地去除计算结果。智能的编译器(包括当前
的 这段代码的最大问题是, Java 7 和 Java 8)最终执行的是以下代码:
long then = System.currentTimeMillis();
long now = System.currentTimeMillis();
结果是,无论计算斐波那契的方法如何实现,循环执行了多少次,实际的流逝时间其实只 有几毫秒。循环如何被消除的细节请参见第 4 章。
微基准测试几要素
1. 必须使用被测的结果
2. 不要包括无关的操作
3. 必须输入合理的参数
综合所有因素,正确的微基准测试代码看起来应该是这样:
package net.sdo;
import java.util.Random;
public class FibonacciTest {
private volatile double l;
private int nLoops;
private int[] input;
public static void main(String[] args) {
FibonacciTest ft = new FibonacciTest(Integer.parseInt(args[0]));
ft.doTest(true);
ft.doTest(false);
}
private FibonacciTest(int n) {
nLoops = n;
input = new int[nLoops];
Random r = new Random();
for (int i = 0; i
input[i] = r.nextInt(100);
}
}
private void doTest(boolean isWarmup) {
long then = System.currentTimeMillis();
for (int i = 0; i
l = fibImpl1(input[i]);
}
if (!isWarmup) {
long now = System.currentTimeMillis();
} }
private double fibImpl1(int n) {
if (n 0");
if (n == 0) return 0d;
if (n == 1) return 1d;
double d = fibImpl1(n - 2) + fibImpl(n - 1);
if (Double.isInfinite(d)) throw new ArithmeticException("Overflow");
return d;
}
}
2.1.2 宏基准测试
衡量应用性能最好的事物就是应用自身,以及它所用到的外部资源。如果正常情况下应用 需要调用 LDAP 来检验用户凭证,那应用就应该在这种模式下测试。虽然删空 LDAP 调用 在模块测试中有一定意义,但应用本身必须在完整真实配置的环境中测试。
随着应用规模的增长,上述准则愈加重要也更难达到。复杂系统并不是各个部分的简单加 和,装配之后,各部分的行为会有很大不同。所以,比如你伪装数据库调用,那就意味着 你并不担心数据库的性能——对了,你是 Java 人,为什么要处理其他人的性能问题呢?数 据库连接会因为缓存而消耗大量堆内存,网络也会因为发送大量数据而饱和,代码调用简 单方法(与调用 JDBC 驱动程序的代码相比)时的不同优化,短代码路径因为 CPU 管线和 缓存而比长代码路径更为有效,等等。
2.1.3 介基准测试
我的调优工作包括Java SE和EE,每种都会有一组类似微基准测试的测试。对于Java SE工程师来说,这个术语意思是样本甚至比2.1.1节的还要小:测量很小的东西。Java EE工 程师则将这个术语用于其他地方:测量某方面性能的基准测试,但仍然要执行大量代码。
Java EE微基准测试的例子,测量从应用服务器返回的简单JSP响应。仔细比较处理请求 的代码和传统微基准测试的代码:有许多 socket 管理代码,读取请求、查找(可能需要编 译)JSP、写入响应等代码。从传统角度来看,这不能算微基准测试。
这种测试也不是宏基准测试:没有安全(比如用户不用登录),没有会话管理,也没有大 量使用其他的Java EE特性。因为它只是实际应用的子集,介于两者之间——它是介基准 测试。介基准测试并不局限于Java EE:它是一个术语,我用来表示做一些实际工作,但 不是完整应用的基准测试。
《Java性能权威指南》文字版PDF下载,关注:java技艺,回复:Java性能权威指南
领取专属 10元无门槛券
私享最新 技术干货