通常的做法是将常量空数组返回值提取为静态常量.像这儿:
public class NoopParser implements Parser { private static final String[] EMPTY_ARRAY = new String[0]; @Override public String[] supportedSchemas() { return EMPTY_ARRAY; } // ... }
可能这是出于性能原因而完成的,因为new String[0]
每次调用该方法时直接返回会创建一个新的数组对象 - 但它真的会吗?
我一直想知道这样做是否真的有可衡量的性能优势,或者它是否只是过时的民间智慧.空数组是不可变的.VM是否无法将所有空String
数组卷成一个?VM new String[0]
基本上没有成本吗?
将这种做法与返回一个空字符串进行对比:我们通常很乐意写return "";
,而不是return 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:# 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
会使速度变慢。