通常情况下,对参与到实际业务计算的值提前检测对提升性能是有帮助的,但是假如用户大多数输入的值是合理的,那么提前检查数据的有效性显得冗余了。所以编写核心逻辑代码的时候,我们建议只针对一般情况做处理,保证执行的效率的高效性。假设访问一个 collection 对象时,每一次能够节省几毫秒的话,那么在多次的访问情况下会对性能的提升产生重大的意义。
public class Test1 {
private volatile double l;
private int nLoops;
private int[] input;
private Test1(int n) {
nLoops = n;
input = new int[nLoops];
Random random = new Random();
for (int i = 0; i < nLoops; i++) {
input[i] = random.nextInt(50);
}
}
public void doTest(boolean isWarmup) {
long then = System.currentTimeMillis();
for (int i = 0; i < nLoops; i++) {
try {
l = compute(input[i]);
} catch (IllegalArgumentException iae) {
}
if (!isWarmup) {
long now = System.currentTimeMillis();
System.out.println("Elapsed time:" + (now - then));
}
}
}
private double compute(int n) {
if (n < 0)
throw new IllegalArgumentException("Must be > 0");
if (n == 0)
return 0d;
if (n == 1)
return 1d;
double d = compute(n - 2) + compute(n - 1);
if (Double.isInfinite(d))
throw new ArithmeticException("Overflow");
return d;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Test1 test1 = new Test1(Integer.parseInt("10");));
test1.doTest(true);
test1.doTest(false);
}
}
  总得说来,微观基准作用是有限的,在频繁调用的方法中使用微观基准的度量方法会帮助我们检测代码的性能,如果用在不会被频繁调用的方法中是不合适的,应当考虑其它方法。

  宏观基准,当我们测量应用程序性能时,应当纵览整个系统,影响应用程序性能的原因可能是多方面的,不能片面的认为性能瓶颈只会在程序本身上。通过下面这个例子我们将探讨离开宏观基准的性能测试是不可能找到影响应用程序性能真正的瓶颈。
  上图数据来自客户实体,触发应用程序的核心业务计算方法,该方法从数据库加载数据,并传导给核心业务中的计算方法,得到结果保存到数据库,终响应客户的请求。每个图形中的数字分别代表了这个模块所能处理客户请求的数量。核心业务模块的优化多数情况是受限于业务的要求。假设我们优化这些核心模块,使其可以处理 200 RPS 时,我们发现加载数据的模块依然只能处理 100 RPS,也是说整个系统的吞吐能力其实仍然为 100 RPS,终对应用程序整体的性能提升是没有任何帮助的。从这个例子我们得知,我们花费再多的精力在核心业务上的优化意义并不大,我们应当从整体运行情况来看,发现真正影响性能的瓶颈来解决问题,这是宏观基准原则的意义。
  折衷基准,相比微观基准和宏观基准,一个单独功能模块的性能测试,或者一系列特定操作的性能测试被称为折衷基准。它是介于微观基准和宏观基准之间的折衷方案。基于微观基准测试的正确性是较难把握的,性能瓶颈的判断绝不能仅仅依赖于此。如果我们要使用微观基准作为性能的测量方法,那么不妨在此之前先尝试基于宏观基准的测试。它可以帮助我们了解系统以及代码是如何工作的,从而形成一个系统整体逻辑结构图。接下来可以考虑基于折衷基准的测试,来真正发现潜在的性能瓶颈。需要明确的是折衷基准的测试方法并不是完整应用程序测试的替代方法,更多情况下我们认为它更适用于一个功能模块的自动测试。
  批量,吞吐量和响应时间的测量方法
  性能测试中的第二个重要的原则是引入多样的测量方法来分析程序的性能。
  批量执行所用时间的测量方法(耗时法),这是种简单而快速有效的方法,通过测量完成特定任务所消耗的时间来测量整体性能。但是需要特别注意,假如所测试的应用程序中使用缓存数据技术来为了获得更好的性能表现时,多次循环使用该方法可能无法完全反应性能问题。那么可以尝试在初始状态开始时应用耗时法做一次性能的评估,然后当缓存建立后,再次尝试此方法。
  吞吐量的测量方法,在一段时间内考察完成任务的数量的能力,被称为吞吐量测量方法。在测试客户服务器的应用程序时,吞吐量的测量意味着客户端发送请求到服务器是没有任何延迟的,当客户端接收到响应后,应当立即发出新的请求,直到终结束,统计客户端完成任务的总数。这种相对理想的测试方法通常称之为“Zero-think-time”。可是通常情况下,客户端可能会有多个线程做同一件事情,吞吐量则意味着每秒钟内所有客户端的操作数,而不是测量的某一个时段内的所有操作总数。这种测量经常称为每秒事务/(TPS),每秒请求 (RPS),或每秒操作数 (OPS)。
  测试所有基于客户端和服务器端应用程序都存在一种风险,客户端不能以足够快的速度发送数据到服务器端,这种情况的发生可能是由于客户端此时没有足够的 CPU 资源去运行需要数量的线程,或者客户端必须耗用更长的时间来处理当前的请求。这种情况下,实际上测量的是客户端的性能,而非服务器的性能,与吞吐量测量方法是背道而驰的。其实这种风险是由每个客户端线程处理任务的数量和硬件配置决定的。“Zero-think-time”在吞吐量测试中可能经常会遇见以上的情况,由于每个客户端线程都需要处理大量的任务,因此吞吐量测试通常被应用于较少的客户端线程程序。吞吐量测量方法也同样适应用于带有缓存技术的应用程序,尤其是当测试的数据是一个并不固定的情况下。
  响应时间的测量方法,响应时间的测量方法是指客户端发出一个请求后直到接收到服务器的响应返回后的时间消耗。响应时间测量方法不同于吞吐量测量方法,在响应时间测试过程中,客户端线程可能会在操作的过程中某一时刻休眠,这引出“think- time”这个关键词,当“think- time”被引入到测试过程中,也是意味着待处理任务量是固定的,测量的是服务器响应请求的速率是怎样的。大多数情况下,响应时间的测量方法用来模拟用户真实操作,从而测量应用程序的性能。
  多变性
  性能测试的第三个原则是理解测试结果如何随时间改变,即使每一次测试使用同样的数据,可能获得的结果也是不同的。一些客观因素,比如后台运行的进程,网络的负载情况,这些都可能带来测试结果的不同,所以在测试过程中存在着一些随机性的因素。这产生了一个问题: 当比较两次运行得到的测试结果时,它们之间的差异是由回归测试产生的,还是是随机变化而导致的呢?
  我们不能简单的通过测量多次运行回归测试的平均结果来评判性能的差异。这时我们可以使用统计分析的方法,假设两种情况的平均值是一样的,然后通过概率来判断这样的假设是成立的。如果假设不成立,那么说明有很高的概率证明平均数存在差异。
  在回归测试中原始代码被视为基线,新增加的代码称为样本。三次运行基线和样本,产生时间如表 1:

  看起来样本的平均值显示有 25%的提升,可事实证明样本和基线有相同性能的概率是 43%。也是说 57%的概率存在性能上的不同。43%是基于 T 检验所得到的结果,T 检验主要用于样本含量较小(例如 n<30),总体标准差σ未知的正态分布资料。t 检验是用 t 分布理论来推论差异发生的概率,从而比较两个平均数的差异是否显著。它与 z 检验、卡方检验并列。现在的 T 检验结果告诉我们这样一个信息::57%概率显示样本和基线存在性能差异,差异大值是 25%。也可以理解为性能差有 57%的置信度向理想发现发展,结果有 25%的改善。
  在考量回归测试的结果时,离开了统计分析的方法,而只关注平均值来做出判断,含糊的理解这些数字的含义是不可取的。性能工程师的工作是看数据,理解这些概率,基于所有可用的数据确定在何处花时间。
  尽早测试,经常测试
  第四个原则是工程师应该视性能测试是整个开发过程必要的部分,尽早进行性能测试,经常进行性能的测试,是一个好的工程师应该做到的。在代码提交到代码库之前,应当做性能测试,因为性能问题也会导致回归测试失败。所以提早发现问题会提高整个项目的质量,减小交付的风险性。
  在一个典型的项目开发周期过程中,项目计划常常是建立一个功能提交的时间表,所有功能的开发必须要在某一个时间点全部提交到代码库中,在项目发布之前,所有的精力都致力于解决功能上的 Bug,那么很有可能在这个过程中发现性能问题,这会导致两个问题产生:
  开发人员在时间的约束下不得不提交代码以满足时间表,一旦发现出严重的性能问题他们会非常畏惧,所以开发人员在测试开始的早期解决性能问题能够产生 1%的回归测试代价,而如果开发人员一直在等待晚上的冻结功能开发的时候才开始检查代码将会导致 20%的回归测试的代价。
  任何为解决性能做出的修改都有可能带来巨大的成本,有时不仅仅是代码的修改,更有可能是软件架构的修改。所以好在软件设计之时充分的考虑到未来可能带来的性能问题。
  尽早测试性能有以下四点可作为指导:
  提早准备测试用户以及测试环境的设计和创建;
  性能测试应该考虑尽量用脚本来完成;
  通过性能监控工具尽量收集有可能得到的运行信息,为将来分析提供便利;
  一定要在一个能真实模拟多数用户的机器环境下进行性能测试。
  总结
  后,基于我们讲过的方法作为基础,构建一个自动化的测试系统来收集测试过程中产生的各种信息,能够很好的帮助我们分析发现性能瓶颈。