8

在 Java REST 服务性能测试中,我得到了一个意想不到的模式:在每次调用中创建并返回始终相同的值对象的方法比只返回存储在类或对象字段中的值对象的另一个版本运行得更快。

代码:

@POST @Path("inline") public Response inline(String s) { 
    return Response.status(Status.CREATED).build(); 
}    

private static final Response RESP = Response.status(Status.CREATED).build();
@POST @Path("staticfield") public Response static(String s) { 
    return RESP; 
}

private final Response resp = Response.status(Status.CREATED).build();
@POST @Path("field") public Response field(String s) { 
    return resp; 
}

字节码:

  • 内联(更快):getstatic、invokestatic、invokevirtual、areturn
  • 静态字段(较慢):getstatic、areturn
  • 对象字段(较慢):aload、getfield、areturn

性能(使用 Apache AB,单线程,多次运行,结果一致):

  • 内联:17078.29 [#/sec](平均值)
  • 静态场:5242.64 [#/sec](平均值)
  • 对象字段:5417.40 [#/sec](平均值)

环境:RHEL6 + JDK Oracle 1.7.0_60-b19 64bits

JVM是否有可能使用本机代码优化了内联版本,但从未考虑优化其他两个,因为它们已经很小了?

4

1 回答 1

4

正如评论中所指出的,如果不实际查看程序集,很难判断。由于您使用的是 REST 框架,但我认为这很难从程序集中分辨出来,因为有很多代码需要阅读。

相反,我想给您一个有根据的猜测,因为您的代码是应用costant fold的典型示例。当一个值被内联而不是从一个字段中读取时,JVM 可以安全地假设这个值是常量。因此,当 JIT 编译该方法时,常量表达式可以安全地与您的框架代码合并,这可能会导致更少的 JIT 组装,从而提高性能。对于一个字段值,即使是final一个,也不能假定一个常数值,因为字段值可以改变。(只要字段值不是由javac内联的编译时常量、原语或常量。)JVM 因此可能不会常量折叠该值。String

您可以在JMH的教程中阅读有关常量折叠的更多信息,其中注明:

如果 JVM 意识到无论如何计算的结果都是一样的,它就可以巧妙地优化它。在我们的例子中,这意味着我们可以将计算移到内部 JMH 循环之外。这可以通过始终从状态读取输入、根据该状态计算结果并遵循规则来防止 DCE 来防止。

我希望你使用这样的框架。否则,您的性能指标不太可能有效。

通过读取字节码,您通常无法了解很多关于运行时性能的信息,因为 JIT 编译器可以在优化期间将字节码调整为任何内容。字节码布局仅在解释代码时才重要,这通常不是将性能衡量为性能关键的状态,代码始终是 JIT 编译的。

于 2014-07-26T10:18:25.550 回答