Java 线程(5)- 性能测试

Java 线程(5)- 性能测试_第1张图片
Ottawa, Canada by @marcojodoin

Avoid premature optimization. First make it right, then make it fast - if it is not already fast enough

衡量性能的指标

  • 响应时间(Responsiveness),程序或服务对一个相关请求的相应时间
  • 吞吐量(Throughout),程序或服务单位时间内可以完成的工作量
  • 伸缩性(Scalability),当增加计算资源(如: CPUs, memory, storage, or I/O bandwidth)后, 可以提高多少吞吐量

Amdahl定理

Amdahl's law describes how much a program can theoretically be sped up by additional computing resources, based on the proportion of parallelizable and serial components.

  • :通过添加计算资源(如CPUs, memory),程序或应用可获得的加速速率
  • :程序或应用中不可并行化的部分所占的比率
  • :计算资源的数量

当 无限大时, 与 成反比, 也就是如果程序或应用中大部分是不可并行执行的,即使增加再多的计算资源, 程序的性能也不可能获得很大的提升。相反,如果程序中的任务完全可并行执行,即 (现实世界中,这样的程序是不存在的, 任何一个程序中都含有一些不可并行化的部分),那么 ,即可通过增加计算资源,来获得很大的性能提升。因此, 仔细的分析程序中不可并行化的部分(如一些隐含的不可并行的部分:从任务队列中取任务,计算结果的汇集,等)对性能提升是很重要的。

影响线程开销的因素

  • 上下文切换(Context switching),线程的上下文切换是线程状态由 blocked 变为 running 或由 running 变为 blocked 的引起的,主要来自操作系统或JVM 对进程或线程调度的开销,过度的上下文切换会导致程序性能的低下,其中 IO 操作最容易引起上下文切换,因此应尽量分离 IO 操作,一个好的经验法则是,上下文切换的时间应该在 0.5 ms 到几微秒之间
  • 内存同步(Memory synchronization),为了使内存同步,常用的方法要么串行化程序,而过度的串行化会影响程序的吞吐量,要么使用锁,而不当的加锁(不要担心正常的使用锁,JVM 对此已经优化的足够好了),可能带来锁竞争的问题,该问题不但影响程序的吞吐,同时还会影响程序的性能。影响锁竞争的因素包括:锁被请求的频率和锁被持有的时间,根据 Little 定律如果两者的乘积足够小,那么程序的性能会得到很好的提高,因此,可以改善程序的方法包括:
  1. 减小锁被持有的时间,即缩小锁的范围,快进快出,尽可能的缩小 synchronized 块的范围,尤其是在保证程序正确的情况下, 尝试把计算和阻塞(如I/O)操作移出 synchronized
  2. 减小锁被请求的频率,如对不相关的独立状态变量,用不同的锁进行守护、尝试将一个容器(如:ConcurrentHashMap)进行拆分,然后使用不同的锁对拆分后的片段进行守护, 但该技术有一定的副作用,如:很难对整个容器进行加锁,因此可能在一定程度上带来可见性的问题、避免在频繁调用的代码上加锁和避免使用排他锁(Exclusive Locks)
  • 阻塞(Blocking),主要来自对资源、锁或 I/O 的竞争

测试

The goal of testing is not so much to find errors as it is to increase confidence that the code works as expected

制定测试计划

  • What do you mean by "faster"? (预期)
  • Under what conditions will this approach actually be faster? Under light or heavy load? With large or small data sets? Can you support your answer with measurements? (条件)
  • How often are these conditions likely to arise in your situation? Can you support your answer with measurements? (测量)
  • Is this code likely to be used in other situations where the conditions may be different? (环境)
  • What hidden costs, such as increased development or maintenance risk, are you trading for this improved performance? Is this a good trade off? (隐含的代价)

测试的方法

  • 基本的单元测试(保证基本的功能正确)
  • 测试阻塞操作(设定等待时间->中断->Thread.join())
  • 构造测试程序与数据(问题集中在如何验证测试数据? 如何提高测试程序的并发性? 如何跟踪线程)
  • 测试内存泄露(可以使用 profile 工具)
  • 性能测试

避免测试中的陷阱

  1. GC,要求运行测试的时间足够长,以保证 JVM 有 GC 的发生
  2. 动态编译,同样要求运行测试的时间足够长,即 JVM 的预热,并且应该舍去第一运行的数据
  3. 关注 JVM 对代码的优化,主要集中在两个方面,一是测试程序对代码的覆盖率,另一个是避免 JVM 将一些测试基准当做 dead code 优化掉, 要求测试总在 -server 下运行
  4. 尽量的模仿实际情况

分析测试结果

  • CPU 是否被均匀的使用,如:一些 CPU 负载高,而另一些负载低,这说明大多数计算集中在某一(某些)线程(集合)中, 因此需要加强程序的并行化
  • CPU 是否被充分的使用,如:负载不够,I/O 限制,外部限制(如:数据库,web service等),锁竞争,可以使用 profiler 来分析

你可能感兴趣的:(Java 线程(5)- 性能测试)