背景
我一直在运行一个代码(发布在底部)来测量显式 Java 向下转换的性能,并且我遇到了我觉得有点异常的情况……或者可能是两个异常情况。
我已经看过这个关于 Java 转换开销的线程,但它似乎只谈论一般的转换,而不是这种特殊现象。这个线程涵盖了类似的主题,我真的不需要关于过早优化的建议——我正在调整我的应用程序的部分以获得最佳性能,所以这是合乎逻辑的步骤。
测试
我基本上想测试向下转换的性能与对象的.toString()
方法,对象是String
s,但类型为Object
s。因此,我创建了具有相同内容的 aString a
和 an Object b
,运行了三个循环,并对它们进行了计时。
- 循环 1 是
((String) b).toLowerCase()
; - 循环 2 是
b.toString().toLowerCase()
; - 和循环 3 是
a.toLowerCase()
。
试验结果
(以毫秒为单位的测量值。)
iters | Test Round | Loop 1 | Loop 2 | Loop 3
-----------|--------------|----------|----------|----------
50,000,000 | 1 | 3367 | 3166 | 3186
Test A | 2 | 3543 | 3158 | 3156
| 3 | 3365 | 3155 | 3169
-----------|--------------|----------|----------|----------
5,000,000 | 1 | 373 | 348 | 369
Test B | 2 | 373 | 348 | 370
| 3 | 399 | 334 | 371
-----------|--------------|----------|----------|----------
500,000 | 1 | 66 | 36 | 33
Test C | 2 | 71 | 36 | 41
| 3 | 66 | 35 | 34
-----------|--------------|----------|----------|----------
50,000 | 1 | 27 | 5 | 5
Test D | 2 | 27 | 6 | 5
| 3 | 26 | 5 | 5
-----------|--------------|----------|----------|----------
用于测试的代码
long t, iters = ...;
String a = "String", c;
Object b = "String";
t = System.currentTimeMillis();
for (int i = 0; i < iters; i++) {
c = ((String) b).toLowerCase();
}
System.out.println(System.currentTimeMillis() - t);
t = System.currentTimeMillis();
for (int i = 0; i < iters; i++) {
c = b.toString().toLowerCase();
}
System.out.println(System.currentTimeMillis() - t);
t = System.currentTimeMillis();
for (int i = 0; i < iters; i++) {
c = a.toLowerCase();
}
System.out.println(System.currentTimeMillis() - t);
最后,问题
我发现最令人着迷的是循环 2 ( .toString()
) 似乎在三个循环中表现最好(尤其是在测试 B 中)——这在直觉上没有意义。为什么调用.toString()
会比已经拥有一个String
对象更快?
困扰我的另一件事是它无法扩展。如果我们比较测试 A 和 D,它们在相互比较时相差 9 倍(27 * 1000 = 27000,而不是 3000);为什么随着迭代次数的增加会出现如此巨大的差异?
谁能解释为什么这两个异常被证明是真的?
(奇怪的)现实
更新:根据Bruno Reis的解决方案的建议,我再次使用一些编译器输出运行我的基准测试。第一个循环塞满了初始化的东西,所以我放入了一个“垃圾”循环来执行此操作。完成后,结果更接近预期。
这是使用 5,000,000 次迭代的控制台的完整输出(由我评论):
50 1 java.lang.String::toLowerCase (472 bytes)
50 2 java.lang.CharacterData::of (120 bytes)
53 3 java.lang.CharacterDataLatin1::getProperties (11 bytes)
53 4 java.lang.Character::toLowerCase (9 bytes)
54 5 java.lang.CharacterDataLatin1::toLowerCase (39 bytes)
67 6 n java.lang.System::arraycopy (0 bytes) (static)
68 7 java.lang.Math::min (11 bytes)
68 8 java.util.Arrays::copyOfRange (63 bytes)
69 9 java.lang.String::toLowerCase (8 bytes)
69 10 java.util.Locale::getDefault (13 bytes)
70 1 % Main::main @ 14 (175 bytes)
[GC 49088K->360K(188032K), 0.0007670 secs]
[GC 49448K->360K(188032K), 0.0024814 secs]
[GC 49448K->328K(188032K), 0.0005422 secs]
[GC 49416K->328K(237120K), 0.0007519 secs]
[GC 98504K->352K(237120K), 0.0122388 secs]
[GC 98528K->352K(327552K), 0.0005734 secs]
595 1 % Main::main @ -2 (175 bytes) made not entrant
548 /****** Junk Loop ******/
597 2 % Main::main @ 61 (175 bytes)
[GC 196704K->356K(327552K), 0.0008460 secs]
[GC 196708K->388K(523968K), 0.0005100 secs]
343 /****** Loop 1 ******/
939 2 % Main::main @ -2 (175 bytes) made not entrant
940 11 java.lang.String::toString (2 bytes)
940 3 % Main::main @ 103 (175 bytes)
[GC 393092K->356K(523968K), 0.0036496 secs]
377 /****** Loop 2 ******/
1316 3 % Main::main @ -2 (175 bytes) made not entrant
1317 4 % Main::main @ 145 (175 bytes)
[GC 393060K->332K(759680K), 0.0008326 secs]
320 /****** Loop 3 ******/