通常的做法是将常量空数组返回值提取为静态常量.像这儿:
public class NoopParser implements Parser { private static final String[] EMPTY_ARRAY = new String[0]; @Override public String[] supportedSchemas() { return EMPTY_ARRAY; } // ... }
据推测这是出于性能原因,因为每次调用该方法时直接返回新的String [0]会创建一个新的数组对象 – 但它真的会吗?
我一直想知道这样做是否真的有可衡量的性能优势,或者它是否只是过时的民间智慧.空数组是不可变的. VM是否无法将所有空String组件都卷成一个? VM可以基本上免费使用新的String [0]吗?
将此练习与返回空字符串进行对比:我们通常非常乐意写回“”;而不是返回EMPTY_STRING;.
解决方法
我用
JMH对它进行了基准测试:
private static final String[] EMPTY_STRING_ARRAY = new String[0]; @Benchmark public void testStatic(Blackhole blackhole) { blackhole.consume(EMPTY_STRING_ARRAY); } @Benchmark @Fork(jvmArgs = "-XX:-EliminateAllocations") public void testStaticEliminate(Blackhole blackhole) { blackhole.consume(EMPTY_STRING_ARRAY); } @Benchmark public void testNew(Blackhole blackhole) { blackhole.consume(new String[0]); } @Benchmark @Fork(jvmArgs = "-XX:-EliminateAllocations") public void testNewEliminate(Blackhole blackhole) { blackhole.consume(new String[0]); } @Benchmark public void noop(Blackhole blackhole) { }
环境(在java -jar target / benchmarks.jar -f 1之后看到):
# JMH 1.11.2 (released 51 days ago) # VM version: JDK 1.7.0_75,VM 24.75-b04 # VM invoker: /usr/lib/jvm/java-7-openjdk-amd64/jre/bin/java # VM options: <none> # Warmup: 20 iterations,1 s each # Measurement: 20 iterations,1 s each # Timeout: 10 min per iteration # Threads: 1 thread,will synchronize iterations # Benchmark mode: Throughput,ops/time
默认情况下,EliminateAllocations处于启用状态(见于java -XX:PrintFlagsFinal -version | grep EliminateAllocations).
结果:
Benchmark Mode Cnt score Error Units MyBenchmark.testNewEliminate thrpt 20 95912464.879 ± 3260948.335 ops/s MyBenchmark.testNew thrpt 20 103980230.952 ± 3772243.160 ops/s MyBenchmark.testStaticEliminate thrpt 20 206849985.523 ± 4920788.341 ops/s MyBenchmark.testStatic thrpt 20 219735906.550 ± 6162025.973 ops/s MyBenchmark.noop thrpt 20 1126421653.717 ± 8938999.666 ops/s
使用常数几乎快两倍.
关闭EliminateAllocations减慢了一点点.