这些性能结果是由哪些JVM优化导致的?

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 [#/秒] (平均)
  • 静态字段:5242.64 [#/秒] (平均)
  • 对象字段:5417.40 [#/秒] (平均)

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

可能JVM优化了内联版本的本机代码,但从未考虑优化其他两个版本,因为它们已经足够小了吗?


我认为最有可能的是,除了上面的代码之外,还有一些东西没有按照你想象的方式工作。 - Hot Licks
1
发布一个完整的、可编译的基准测试。只有这样,我们才能深入了解发生了什么。 - tmyklebu
@ThorbjørnRavnAndersen 我已经验证了响应没有被缓存。REST框架(我已经排除以简化)不应该干扰,因为该类在三种方法中完全暴露相同的功能行为。唯一的区别是实现,这只对JVM可见。 - Sebastian
@tmyklebu 感谢您的参与。您能详细说明一下吗?您想要添加什么?我试图尽可能简化问题(因为我认为这是由于JVM优化引起的),但我很乐意添加任何建议。 - Sebastian
2
你能否尝试使用此处提到的技术之一获取上述方法的JIT编译汇编清单?https://dev59.com/53I_5IYBdhLWcg3wJfkM - Alex D
显示剩余4条评论
1个回答

4

正如评论中指出的那样,如果没有实际查看汇编代码,很难确定。但是由于您正在使用REST框架,我认为从汇编中很难判断,因为需要阅读大量代码。

相反,我想给出一个经过教育的猜测,因为您的代码是应用常量折叠的典型示例。当一个值被内联且不从字段中读取时,JVM可以安全地假定该值是常量。在JIT编译方法时,因此可以将常量表达式与您的框架代码安全地合并,这可能会导致更少的JIT汇编代码,从而提高性能。对于字段值,即使是final值,也不能假定为常量值,因为字段值可能会更改。(只要字段值不是编译时常量、原始类型或常量String,这些都由javac内联。)因此,JVM可能无法将该值进行常量折叠。

你可以在教程中阅读更多有关常量折叠的内容,其中提到了JMH

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

我希望你使用这样的框架。否则,你的性能指标可能无效。
从阅读字节码中,你通常无法了解运行时性能,因为JIT编译器可以在优化过程中将字节码调整为任何内容。字节码布局只在代码被解释时才有影响,而这通常不是衡量性能关键的状态,热点代码总是被JIT编译。

到目前为止,这是最佳答案。我已经没有使用任何框架进行了性能测试,而内联方法速度较慢(相反的结果)。因此,无论发生了什么优化,它只会在框架代码处于活动状态时发生。正如你所说,很难确定这是否就是这种情况下真正发生的事情,但是你的解释非常贴切,并且今天我因为你的回应学到了一些新东西。谢谢! - Sebastian

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接