Java 为什么字符串转换循环似乎有静态开销? 背景

Java 为什么字符串转换循环似乎有静态开销? 背景,java,performance,casting,Java,Performance,Casting,我一直在运行一段代码(张贴在底部)来测量显式Java向下广播的性能,我遇到了我觉得有点反常的情况。。。或者可能有两种反常现象 我已经研究了Java转换开销,但它似乎只讨论一般的转换,而不是这个特殊的现象。涵盖了类似的主题,我真的不需要关于过早优化的建议——我正在调整应用程序的某些部分以获得最佳性能,因此这是合乎逻辑的步骤 测试 我基本上是想对Strings,但键入为Objects的对象,测试下行广播与.toString()方法的性能。因此,我创建了一个具有等效内容的字符串a和对象b,运行了三个循

我一直在运行一段代码(张贴在底部)来测量显式Java向下广播的性能,我遇到了我觉得有点反常的情况。。。或者可能有两种反常现象

我已经研究了Java转换开销,但它似乎只讨论一般的转换,而不是这个特殊的现象。涵盖了类似的主题,我真的不需要关于过早优化的建议——我正在调整应用程序的某些部分以获得最佳性能,因此这是合乎逻辑的步骤

测试 我基本上是想对
String
s,但键入为
Object
s的对象,测试下行广播与
.toString()
方法的性能。因此,我创建了一个具有等效内容的
字符串a
对象b
,运行了三个循环,并对它们计时

  • 循环1是
    ((字符串)b)
    
  • 循环2是
    b.toString().toLowerCase()
  • 循环3是
    a.toLowerCase()
测试结果 (测量单位为毫秒。)

iters |测试回合|环路1 |环路2 |环路3
-----------|--------------|----------|----------|----------
50,000,000 |      1       |   3367   |   3166   |   3186
测试A | 2 | 3543 | 3158 | 3156
|      3       |   3365   |   3155   |   3169
-----------|--------------|----------|----------|----------
5,000,000 |      1       |    373   |    348   |    369
测试B | 2 | 373 | 348 | 370
|      3       |    399   |    334   |    371
-----------|--------------|----------|----------|----------
500,000  |      1       |    66    |    36    |    33
测试C | 2 | 71 | 36 | 41
|      3       |    66    |    35    |    34
-----------|--------------|----------|----------|----------
50,000   |      1       |    27    |     5    |     5
测试D | 2 | 27 | 6 | 5
|      3       |    26    |     5    |     5
-----------|--------------|----------|----------|----------
用于测试的代码
基准测试是有缺陷的,因为SO和其他地方的大多数问题都与Java代码基准测试有关。您所测量的内容比您想象的要多得多,例如JIT编译方法、热点优化循环等等

检查

此外,服务器虚拟机和客户端虚拟机的行为也不同(JVM在客户端启动更快,但在一段时间内运行较慢,因为它在编译时开始解释字节码,而服务器虚拟机在运行前编译字节码),等等

GC也可能会产生干扰,如果您在基准测试期间获得任何完整GC(通常是每隔一个线程完全暂停一次完整GC),则更是如此。即使是较小的集合也可能有一些影响,因为它们可以使用相当多的CPU来清理循环中可能产生的巨大混乱

要做一个正确的基准测试,您应该“预热”JVM,打开JVM的大量输出,以确定您正在测量什么,等等

在这里检查这个问题,它解决了如何用Java编写基准测试的问题,包括我上面提到的主题以及更详细的内容:

为什么调用.toString()要比已经拥有String对象快

从数字上看,我看不出循环2始终比循环3快。事实上,在某些情况下,速度较慢。测试B中明显的显著差异可能仅仅是GC在循环3的情况下比在循环2的情况下运行一次。这可能只是基准设计的产物


无论如何,如果您真的想知道发生了什么(如果有的话),您需要查看JIT编译器在每种情况下生成的本机指令。(有JVM选项可以这样做…

+1用于有趣文章的有趣链接,+1用于有趣的问题!),但我认为如果您对这个基准实例的缺陷给出一些具体的想法,会有所帮助。我浏览了这篇文章,但并不完全清楚它在这里是如何应用的。例如,本文中的整个同步问题是一个错误,在这里不适用。你认为哪些部分适用于这里?好的,你的一些细节是在我写评论的时候讲出来的。这些帮助。很好的答案,很好的链接。当我使用一些编译器输出重新运行基准测试时,它显示在第一个循环中完成了大量编译器的工作。当我在其中添加一个垃圾循环时,结果比我预期的要多。我已经用控制台输出编辑了我的第一篇博文——非常有趣的东西。试着以不同的顺序运行循环和测试。有什么不同吗?例如,如果在测试D中将循环1放在最后会发生什么?Java非常擅长优化,在编译后,循环行为可能完全不同。@DWright极好的调用。在第一个循环中肯定会有一些事情发生。这个输出听起来像
-XX:+printcomployment
-verbose:gc
,你可以看到很多gc活动(因为字符串是不可变的,循环中的许多调用将生成新字符串,这些字符串将被丢弃——尽管它运行得相当快!)很高兴它工作了。在我离开之前,有一个提示可能会有所帮助:通常在调用方法20k(或10k,取决于客户机/服务器vm)次之后,HotSpot会对这些方法进行优化。因此,为了热身,通常需要在调用方法20k次的循环中复制和粘贴要作为基准的代码
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);
     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 ******/