我对来自C ++世界的Java还是很陌生。 我正在运行一些服务器代码,该代码运行一个方法foo(),该方法每秒被调用数百万次。这是对延迟敏感的代码,该方法还显示在探查器中,按进程消耗了总体CPU使用量的20%。
int foo_old() {
if (Float.isNan(this.x)) { // shows up in profiling
res = do some computation; // some floating point comparison, doesn't show up in profiling;
return res;
} else {
// Happens 99% of the time;
res = do something else; // some floating point comparison, doesn't show up in profiling;
return res;
}
}
有没有一种简便的方法可以测试我的方法foo是否将内联?我可以从探查器堆栈中正在运行的服务器中的跟踪信息中得知吗?
我通过尝试简化方法foo()进行了一些优化。基本上,在foo中有一个float.isNan检查,它也显示在探查器中,惊讶地发现nan检查速度较慢。相较于其他布尔运算(小于,大于浮点比较)更快。
我尝试的一种方法是删除nan check,即因为我在编译时知道某个对象是否需要nan check,所以我尝试存储功能接口(成员变量)并分配此功能接口foo_old(具有nan check的功能) )或根据创建对象时已知的对象属性foo_optimized(不进行nan检查)(在对象的构造函数中,我为此接口分配正确的方法引用。
class A {
final FuncIf test; // Functional interface with same signature as foo_old, foo_new
public A(bool optimize) {
test = optimize ? this::foo_optimized : this::foo_old;
}
// same as the original foo mentioned above
int foo_old() {
...
}
// No nan check
int foo_optimized() {
res = do some computation;
return res;
}
}
现在,当我创建对象时,我知道在编译时/对象构造时要使用哪个版本的foo。所以我将接口变量分配给foo的正确版本。部署后,我发现延迟延迟实际上增加了<10%。即使现在许多对象实际上将使用foo的优化版本。
之所以这样,是因为之前的foo是直接方法调用,并且一旦我使用接口引用,调度虚拟foo的额外间接操作就是我在等待时间中看到的开销(接口方法调用的开销比Nan检查要大得多)本身??)? jvm编译器不能内联此接口方法吗?
答案 0 :(得分:1)
唯一受过教育的 猜测是测量其bytecode
。为此使用javap
。基本上JVM
有两个编译器C1
和C2
;两者都可以内联该方法。
JVM
在内联时会关心三个参数(嗯,这是我所知道的,我也知道还有很多 ):
-XX:MaxInlineSize (35 by default)
-XX:FreqInlineSize (325 by default)
-XX:MinInliningThreshold (250 by default)
如果调用的方法少于MinInliningThreshold
(250),则它遵循MaxInlineSize
规则,这意味着如果它小于35个字节,它将被内联。如果再多调用它,它将比FreqInlineSize
服从,它是325个字节(更多)。
您还可以通过一些参数打印内联或不内联的内容:
-XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining
运行这些命令后,您会看到以下消息:
callee is too large
这是由C1
打印的,它告诉您该编译方法超出了MaxInlineSize
。或者:
too big
超过C2
时,由MaxInlineSize
编译器打印。或者:
hot method too big
超过C2
时由FreqInlineSize
打印。