14. >java -server -XX:+DoEscapeAnalysis EATest
thread unsafe: 933 ms.
thread safe: 1119 ms.
Thread safety overhead: 119%
逸出分析的数目在 Windows 和 Linux 上都能看到。然而在 Mac OS X 上,即使有额外未被使用的变量也不会有
任何影响,任何版本的基准测试的结果都是 120%。这让我不由地相信在 Mac OS X 上有效性的范围比其他系统
更广泛。我猜测这是由于它的实现比较保守,根据不同条件(比如锁对象数据大小和其他 OS 特定的特性)及早
地关掉了它。
结论
当我刚开始这个实验,解释应用各种锁优化的 Hotspot 的有效性的时候,我估计它将花费我几个小时的时间,最
终这会丰富我的 blog 的内容。但是就像其他的基准测试一样,对结果进行验证和解释的过程最终耗费了几周的时
间。同样,我也与很多专家进行合作,他们分别花费了大量时间检查结果,并发表他们的见解。即使在这些工作
完成以后,仍然很难说哪些优化起作用了,而哪些没有起作用。尽管这篇文章引述了一组测试结果,但它们是特
定我的硬件和系统的。大家可以考虑是否能在自己的系统上看到相同类型的测试结果。另外,我最初认为这不过
是个小规模基准测试,但是后来它逐渐既要满足我,也要满足所有审核代码的人,而且去掉了 Hotspot 不必要的
优化。总之,这个实验的复杂度远远地超出了我的预期。
如果你需要在多核机器上运行多线程的应用程序,并且关心性能,那么很明显,你需要不断地更新所使用的 JDK
到最新版本。很多(但不是全部)前面的版本的优化都可以在最新的版本中获得兼容。你必须保证所有的线程优
化都是激活的。在 JDK 6.0 中,它们默认是激活的。但是在 JDK 5.0 中,你需要在命令行中显式地设置它们。如
果你在多核机器上运行单线程的应用程序,就要禁用除第一个核以外所有核的优化,这样会使应用程序运行得更
快。
在更低级的层面上,单核系统上锁的开销远远低于双核处理器。不同核之间的协调,比如存储关卡语义,通过关
掉一个核运行的测试结果看,很明显会带来系统开销。我们的确需要线程优化,以此降低这一开销。幸运的是,
锁粗化和(尤其是)偏向锁对于基准测试的性能确实有明显的影响。我也希望逸出分析与锁省略一起更能够做到
更好,产生更多的影响。这项技术会起作用,可只是在很少的情况下。客观地说,逸出分析仍然还处于它的初级
阶段,还需要大量的时间才能变得成熟。
最后的结论是,最权威的基准测试是让你的应用程序运行在自己的系统上。当你的多线程应用的性能没有符合你
的期望的时候,这篇文章能够为你提供了一些思考问题的启示。而这就是此文最大的价值所在。
关于 Jeroen Borgers
Jeroen Borger 是 Xebia 的资深咨询师。Xebia 是一家国际 IT 咨询与项目组织公司,专注于企业级 Java 和敏捷
开发。Jeroen 帮助他的客户攻克企业级 Java 系统的性能问题,他同时还是 Java 性能调试课程的讲师。他在从
1996 年开始就可以在不同的 Java 项目中工作,担任过开发者、架构师、团队 lead、质量负责人、顾问、审核
员、性能测试和调试员。他从 2005 年开始专注于性能问题。
鸣谢
没有其他人的鼎力相助,是不会有这篇文章的。特别感谢下面的朋友:
Dr. Cliff Click,原 Sun 公司的 Server VM 主要架构师,现工作在 Azul System;他帮我分析,并提供了很多
宝贵的资源。
15. Kirk Pepperdine,性能问题的权威,帮助我编辑文章。
David Dagastine,Sun JVM 性能组的 lead,他为我解释了很多问题,并把我引领到正确的方向。
我的很多 Xebia 的同事帮我进行了基准测试。
资源
Java concurrency in practice, Brian Goetz et all.
Java theory and practice: Synchronization optimizations in Mustang,
Did escape analysis escape from Java 6
Dave Dice's Weblog
Java SE 6 Performance White Paper
清单 1.
public class LockTest {
private static final int MAX = 20000000; // 20 million
public static void main(String[] args) throws InterruptedException {
// warm up the method cache
for (int i = 0; i < MAX; i++) {
concatBuffer("Josh", "James", "Duke");
concatBuilder("Josh", "James", "Duke");
}
System.gc();
Thread.sleep(1000);
long start = System.currentTimeMillis();
for (int i = 0; i < MAX; i++) {
concatBuffer("Josh", "James", "Duke");
}
long bufferCost = System.currentTimeMillis() - start;
17. StringBuilder sb = new StringBuilder();
sb.append(s1);
sb.append(s2);
sb.append(s3);
return sb.toString();
}
}
清单 2.
public class EATest {
private static final int MAX = 200000000; // 200 million
public static final void main(String[] args) throws InterruptedException {
// warm up the method cache
sumThreadUnsafe();
sumThreadSafe();
sumThreadUnsafe();
sumThreadSafe();
System.out.println("Starting test");
long start;
start = System.currentTimeMillis();
sumThreadUnsafe();
long unsafeCost = System.currentTimeMillis() - start;
System.out.println(" thread unsafe: " + unsafeCost + " ms.");
18. start = System.currentTimeMillis();
sumThreadSafe();
long safeCost = System.currentTimeMillis() - start;
System.out.println(" thread safe: " + safeCost + " ms.");
System.out.println("Thread safety overhead: "
+ ((safeCost * 10000 / (unsafeCost * 100)) - 100) + "%n");
}
public static int sumThreadSafe() {
String[] names = new String[] { "Josh", "James", "Duke", "B" };
ThreadSafeObject ts = new ThreadSafeObject();
int sum = 0;
for (int i = 0; i < MAX; i++) {
sum += ts.test(names[i % 4]);
}
return sum;
}
public static int sumThreadUnsafe() {
String[] names = new String[] { "Josh", "James", "Duke", "B" };
ThreadUnsafeObject tus = new ThreadUnsafeObject();
int sum = 0;
for (int i = 0; i < MAX; i++) {
sum += tus.test(names[i % 4]);
}
return sum;
}
19. }
final class ThreadUnsafeObject {
// private int index = 0;
private int count = 0;
private char[] value = new char[1];
public int test(String str) {
value[0] = str.charAt(0);
count = str.length();
return count;
}
}
final class ThreadSafeObject {
private int index = 0; // remove this line, or just the '= 0' and it will go faster!!!
private int count = 0;
private char[] value = new char[1];
public synchronized int test(String str) {
value[0] = str.charAt(0);
count = str.length();
return count;
}
}