TL;DR 部分
好消息
您的测量确实会显示出真实的效果。
坏消息
它这样做主要是偶然的,因为你的基准测试有很多技术缺陷,它所暴露的影响可能不是你想到的。
当且仅当 HotSpot 的逃逸分析成功证明生成的实例可以安全地分配到堆栈上而不是堆上时,这种方法才会更快。因此,效果并不像您的问题中暗示的那样普遍。new Character()
效果说明
速度更快的原因是引用的局部性:您的实例位于堆栈上,对它的所有访问都是通过 CPU 缓存命中进行的。当您重用缓存的实例时,您必须new Character()
- 访问远程字段;
static
- 将其取消引用到远程阵列中;
- 取消引用数组条目到远程实例中;
Character
- 访问该实例中包含的 。
char
每次取消引用都是潜在的 CPU 缓存未命中。此外,它还会强制将缓存的一部分重定向到这些远程位置,从而导致输入字符串和/或堆栈位置上出现更多缓存未命中。
详
我用以下语言运行此代码:jmh
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@BenchmarkMode(Mode.AverageTime)
public class Chars {
static String string = "12345678901234567890"; static {
for (int i = 0; i < 10; i++) string += string;
}
@GenerateMicroBenchmark
public void newChar() {
int len = string.length();
for (int i = 0; i < len; i++) new Character(string.charAt(i));
}
@GenerateMicroBenchmark
public void justChar() {
int len = string.length();
for (int i = 0; i < len; i++) Character.valueOf(string.charAt(i));
}
}
这保留了代码的本质,但消除了一些系统错误,如预热和编译时间。以下是结果:
Benchmark Mode Thr Cnt Sec Mean Mean error Units
o.s.Chars.justChar avgt 1 3 5 39.062 6.587 usec/op
o.s.Chars.newChar avgt 1 3 5 19.114 0.653 usec/op
这将是我对正在发生的事情的最佳猜测:
更新
为了回应Aleks的批评,我在基准测试中增加了一些方法。主效果保持稳定,但我们得到了有关较小优化效果的更细粒度的细节。
@GenerateMicroBenchmark
public int newCharUsed() {
int len = string.length(), sum = 0;
for (int i = 0; i < len; i++) sum += new Character(string.charAt(i));
return sum;
}
@GenerateMicroBenchmark
public int justCharUsed() {
int len = string.length(), sum = 0;
for (int i = 0; i < len; i++) sum += Character.valueOf(string.charAt(i));
return sum;
}
@GenerateMicroBenchmark
public void newChar() {
int len = string.length();
for (int i = 0; i < len; i++) new Character(string.charAt(i));
}
@GenerateMicroBenchmark
public void justChar() {
int len = string.length();
for (int i = 0; i < len; i++) Character.valueOf(string.charAt(i));
}
@GenerateMicroBenchmark
public void newCharValue() {
int len = string.length();
for (int i = 0; i < len; i++) new Character(string.charAt(i)).charValue();
}
@GenerateMicroBenchmark
public void justCharValue() {
int len = string.length();
for (int i = 0; i < len; i++) Character.valueOf(string.charAt(i)).charValue();
}
描述:
- 基本版本是和
justChar
newChar
;
-
...Value
方法将调用添加到基本版本;charValue
-
...Used
方法添加两个调用(隐式),并使用该值来排除任何死代码消除。charValue
结果:
Benchmark Mode Thr Cnt Sec Mean Mean error Units
o.s.Chars.justChar avgt 1 3 1 246.847 5.969 usec/op
o.s.Chars.justCharUsed avgt 1 3 1 370.031 26.057 usec/op
o.s.Chars.justCharValue avgt 1 3 1 296.342 60.705 usec/op
o.s.Chars.newChar avgt 1 3 1 123.302 10.596 usec/op
o.s.Chars.newCharUsed avgt 1 3 1 172.721 9.055 usec/op
o.s.Chars.newCharValue avgt 1 3 1 123.040 5.095 usec/op
- 有证据表明一些死码消除(DCE)在和变体中,但它只是部分;
justChar
newChar
- 对于变体,添加没有效果,所以显然它是DCE的;
newChar
charValue
- 与 ,确实有效果,所以似乎没有被消除;
justChar
charValue
- DCE具有较小的整体影响,正如 和 之间的稳定差异所证明的那样。
newCharUsed
justCharUsed